diff options
author | 2015-08-14 14:26:04 -0700 | |
---|---|---|
committer | 2015-10-16 12:13:08 -0700 | |
commit | 1ab598f46c3ff520a67f9d80194847741f3467ab (patch) | |
tree | 4846790211599fdd7a9bb35ec94df4a6ec4839d6 | |
parent | 547c346bb34878b691fd53e54aa3a88efcc5dc6f (diff) |
AAPT2: Separate out the various steps
An early refactor. Some ideas became clearer as
development continued. Now the various phases are much
clearer and more easily reusable.
Also added a ton of tests!
Change-Id: Ic8f0a70c8222370352e63533b329c40457c0903e
150 files changed, 10330 insertions, 11914 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index e5c42d5f74c1..275476cb2081 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -25,62 +25,71 @@ LOCAL_PATH:= $(call my-dir) main := Main.cpp sources := \ - BigBuffer.cpp \ - BinaryResourceParser.cpp \ - BindingXmlPullParser.cpp \ + compile/IdAssigner.cpp \ + compile/Png.cpp \ + compile/XmlIdCollector.cpp \ + flatten/Archive.cpp \ + flatten/TableFlattener.cpp \ + flatten/XmlFlattener.cpp \ + link/AutoVersioner.cpp \ + link/PrivateAttributeMover.cpp \ + link/ReferenceLinker.cpp \ + link/TableMerger.cpp \ + link/XmlReferenceLinker.cpp \ + process/SymbolTable.cpp \ + unflatten/BinaryResourceParser.cpp \ + unflatten/ResChunkPullParser.cpp \ + util/BigBuffer.cpp \ + util/Files.cpp \ + util/Util.cpp \ ConfigDescription.cpp \ Debug.cpp \ - Files.cpp \ - Flag.cpp \ + Flags.cpp \ JavaClassGenerator.cpp \ - Linker.cpp \ Locale.cpp \ - Logger.cpp \ - ManifestMerger.cpp \ - ManifestParser.cpp \ - ManifestValidator.cpp \ - Png.cpp \ ProguardRules.cpp \ - ResChunkPullParser.cpp \ Resource.cpp \ ResourceParser.cpp \ ResourceTable.cpp \ - ResourceTableResolver.cpp \ + ResourceUtils.cpp \ ResourceValues.cpp \ SdkConstants.cpp \ StringPool.cpp \ - TableFlattener.cpp \ - Util.cpp \ - ScopedXmlPullParser.cpp \ - SourceXmlPullParser.cpp \ - XliffXmlPullParser.cpp \ XmlDom.cpp \ - XmlFlattener.cpp \ - ZipEntry.cpp \ - ZipFile.cpp + XmlPullParser.cpp testSources := \ - BigBuffer_test.cpp \ - BindingXmlPullParser_test.cpp \ - Compat_test.cpp \ + compile/IdAssigner_test.cpp \ + compile/XmlIdCollector_test.cpp \ + flatten/FileExportWriter_test.cpp \ + flatten/TableFlattener_test.cpp \ + flatten/XmlFlattener_test.cpp \ + link/AutoVersioner_test.cpp \ + link/PrivateAttributeMover_test.cpp \ + link/ReferenceLinker_test.cpp \ + link/TableMerger_test.cpp \ + link/XmlReferenceLinker_test.cpp \ + process/SymbolTable_test.cpp \ + unflatten/FileExportHeaderReader_test.cpp \ + util/BigBuffer_test.cpp \ + util/Maybe_test.cpp \ + util/StringPiece_test.cpp \ + util/Util_test.cpp \ ConfigDescription_test.cpp \ JavaClassGenerator_test.cpp \ - Linker_test.cpp \ Locale_test.cpp \ - ManifestMerger_test.cpp \ - ManifestParser_test.cpp \ - Maybe_test.cpp \ - NameMangler_test.cpp \ - ResourceParser_test.cpp \ Resource_test.cpp \ + ResourceParser_test.cpp \ ResourceTable_test.cpp \ - ScopedXmlPullParser_test.cpp \ - StringPiece_test.cpp \ + ResourceUtils_test.cpp \ StringPool_test.cpp \ - Util_test.cpp \ - XliffXmlPullParser_test.cpp \ + ValueVisitor_test.cpp \ XmlDom_test.cpp \ - XmlFlattener_test.cpp + XmlPullParser_test.cpp + +toolSources := \ + compile/Compile.cpp \ + link/Link.cpp hostLdLibs := @@ -101,7 +110,7 @@ else endif cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG -cppFlags := -std=c++11 -Wno-missing-field-initializers -Wno-unused-private-field +cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions # ========================================================== # Build the host static library: libaapt2 @@ -139,7 +148,7 @@ include $(BUILD_HOST_NATIVE_TEST) include $(CLEAR_VARS) LOCAL_MODULE := aapt2 -LOCAL_SRC_FILES := $(main) +LOCAL_SRC_FILES := $(main) $(toolSources) LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs) LOCAL_LDLIBS += $(hostLdLibs) diff --git a/tools/aapt2/BindingXmlPullParser.cpp b/tools/aapt2/BindingXmlPullParser.cpp deleted file mode 100644 index 4b7a656deac6..000000000000 --- a/tools/aapt2/BindingXmlPullParser.cpp +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "BindingXmlPullParser.h" -#include "Util.h" - -#include <iostream> -#include <sstream> -#include <string> -#include <vector> - -namespace aapt { - -constexpr const char16_t* kBindingNamespaceUri = u"http://schemas.android.com/apk/binding"; -constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android"; -constexpr const char16_t* kVariableTagName = u"variable"; -constexpr const char* kBindingTagPrefix = "android:binding_"; - -BindingXmlPullParser::BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) : - mParser(parser), mOverride(false), mNextTagId(0) { -} - -bool BindingXmlPullParser::readVariableDeclaration() { - VarDecl var; - - const auto endAttrIter = mParser->endAttributes(); - for (auto attrIter = mParser->beginAttributes(); attrIter != endAttrIter; ++attrIter) { - if (!attrIter->namespaceUri.empty()) { - continue; - } - - if (attrIter->name == u"name") { - var.name = util::utf16ToUtf8(attrIter->value); - } else if (attrIter->name == u"type") { - var.type = util::utf16ToUtf8(attrIter->value); - } - } - - XmlPullParser::skipCurrentElement(mParser.get()); - - if (var.name.empty()) { - mLastError = "variable declaration missing name"; - return false; - } - - if (var.type.empty()) { - mLastError = "variable declaration missing type"; - return false; - } - - mVarDecls.push_back(std::move(var)); - return true; -} - -bool BindingXmlPullParser::readExpressions() { - mOverride = true; - std::vector<XmlPullParser::Attribute> expressions; - std::string idValue; - - const auto endAttrIter = mParser->endAttributes(); - for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) { - if (attr->namespaceUri == kAndroidNamespaceUri && attr->name == u"id") { - idValue = util::utf16ToUtf8(attr->value); - } else { - StringPiece16 value = util::trimWhitespace(attr->value); - if (util::stringStartsWith<char16_t>(value, u"@{") && - util::stringEndsWith<char16_t>(value, u"}")) { - // This is attribute's value is an expression of the form - // @{expression}. We need to capture the expression inside. - expressions.push_back(XmlPullParser::Attribute{ - attr->namespaceUri, - attr->name, - value.substr(2, value.size() - 3).toString() - }); - } else { - // This is a normal attribute, use as is. - mAttributes.emplace_back(*attr); - } - } - } - - // Check if we have any expressions. - if (!expressions.empty()) { - // We have expressions, so let's assign the target a tag number - // and add it to our targets list. - int32_t targetId = mNextTagId++; - mTargets.push_back(Target{ - util::utf16ToUtf8(mParser->getElementName()), - idValue, - targetId, - std::move(expressions) - }); - - std::stringstream numGen; - numGen << kBindingTagPrefix << targetId; - mAttributes.push_back(XmlPullParser::Attribute{ - std::u16string(kAndroidNamespaceUri), - std::u16string(u"tag"), - util::utf8ToUtf16(numGen.str()) - }); - } - return true; -} - -XmlPullParser::Event BindingXmlPullParser::next() { - // Clear old state in preparation for the next event. - mOverride = false; - mAttributes.clear(); - - while (true) { - Event event = mParser->next(); - if (event == Event::kStartElement) { - if (mParser->getElementNamespace().empty() && - mParser->getElementName() == kVariableTagName) { - // This is a variable tag. Record data from it, and - // then discard the entire element. - if (!readVariableDeclaration()) { - // mLastError is set, so getEvent will return kBadDocument. - return getEvent(); - } - continue; - } else { - // Check for expressions of the form @{} in attribute text. - const auto endAttrIter = mParser->endAttributes(); - for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) { - StringPiece16 value = util::trimWhitespace(attr->value); - if (util::stringStartsWith<char16_t>(value, u"@{") && - util::stringEndsWith<char16_t>(value, u"}")) { - if (!readExpressions()) { - return getEvent(); - } - break; - } - } - } - } else if (event == Event::kStartNamespace || event == Event::kEndNamespace) { - if (mParser->getNamespaceUri() == kBindingNamespaceUri) { - // Skip binding namespace tags. - continue; - } - } - return event; - } - return Event::kBadDocument; -} - -bool BindingXmlPullParser::writeToFile(std::ostream& out) const { - out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; - out << "<Layout directory=\"\" layout=\"\" layoutId=\"\">\n"; - - // Write the variables. - out << " <Variables>\n"; - for (const VarDecl& v : mVarDecls) { - out << " <entries name=\"" << v.name << "\" type=\"" << v.type << "\"/>\n"; - } - out << " </Variables>\n"; - - // Write the imports. - - std::stringstream tagGen; - - // Write the targets. - out << " <Targets>\n"; - for (const Target& t : mTargets) { - tagGen.str({}); - tagGen << kBindingTagPrefix << t.tagId; - out << " <Target boundClass=\"" << t.className << "\" id=\"" << t.id - << "\" tag=\"" << tagGen.str() << "\">\n"; - out << " <Expressions>\n"; - for (const XmlPullParser::Attribute& a : t.expressions) { - out << " <Expression attribute=\"" << a.namespaceUri << ":" << a.name - << "\" text=\"" << a.value << "\"/>\n"; - } - out << " </Expressions>\n"; - out << " </Target>\n"; - } - out << " </Targets>\n"; - - out << "</Layout>\n"; - return bool(out); -} - -XmlPullParser::const_iterator BindingXmlPullParser::beginAttributes() const { - if (mOverride) { - return mAttributes.begin(); - } - return mParser->beginAttributes(); -} - -XmlPullParser::const_iterator BindingXmlPullParser::endAttributes() const { - if (mOverride) { - return mAttributes.end(); - } - return mParser->endAttributes(); -} - -size_t BindingXmlPullParser::getAttributeCount() const { - if (mOverride) { - return mAttributes.size(); - } - return mParser->getAttributeCount(); -} - -XmlPullParser::Event BindingXmlPullParser::getEvent() const { - if (!mLastError.empty()) { - return Event::kBadDocument; - } - return mParser->getEvent(); -} - -const std::string& BindingXmlPullParser::getLastError() const { - if (!mLastError.empty()) { - return mLastError; - } - return mParser->getLastError(); -} - -const std::u16string& BindingXmlPullParser::getComment() const { - return mParser->getComment(); -} - -size_t BindingXmlPullParser::getLineNumber() const { - return mParser->getLineNumber(); -} - -size_t BindingXmlPullParser::getDepth() const { - return mParser->getDepth(); -} - -const std::u16string& BindingXmlPullParser::getText() const { - return mParser->getText(); -} - -const std::u16string& BindingXmlPullParser::getNamespacePrefix() const { - return mParser->getNamespacePrefix(); -} - -const std::u16string& BindingXmlPullParser::getNamespaceUri() const { - return mParser->getNamespaceUri(); -} - -bool BindingXmlPullParser::applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const { - return mParser->applyPackageAlias(package, defaultPackage); -} - -const std::u16string& BindingXmlPullParser::getElementNamespace() const { - return mParser->getElementNamespace(); -} - -const std::u16string& BindingXmlPullParser::getElementName() const { - return mParser->getElementName(); -} - -} // namespace aapt diff --git a/tools/aapt2/BindingXmlPullParser.h b/tools/aapt2/BindingXmlPullParser.h deleted file mode 100644 index cfb16ef477c9..000000000000 --- a/tools/aapt2/BindingXmlPullParser.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AAPT_BINDING_XML_PULL_PARSER_H -#define AAPT_BINDING_XML_PULL_PARSER_H - -#include "XmlPullParser.h" - -#include <iostream> -#include <memory> -#include <string> - -namespace aapt { - -class BindingXmlPullParser : public XmlPullParser { -public: - BindingXmlPullParser(const std::shared_ptr<XmlPullParser>& parser); - BindingXmlPullParser(const BindingXmlPullParser& rhs) = delete; - - Event getEvent() const override; - const std::string& getLastError() const override; - Event next() override; - - const std::u16string& getComment() const override; - size_t getLineNumber() const override; - size_t getDepth() const override; - - const std::u16string& getText() const override; - - const std::u16string& getNamespacePrefix() const override; - const std::u16string& getNamespaceUri() const override; - bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage) - const override; - - const std::u16string& getElementNamespace() const override; - const std::u16string& getElementName() const override; - - const_iterator beginAttributes() const override; - const_iterator endAttributes() const override; - size_t getAttributeCount() const override; - - bool writeToFile(std::ostream& out) const; - -private: - struct VarDecl { - std::string name; - std::string type; - }; - - struct Import { - std::string name; - std::string type; - }; - - struct Target { - std::string className; - std::string id; - int32_t tagId; - - std::vector<XmlPullParser::Attribute> expressions; - }; - - bool readVariableDeclaration(); - bool readExpressions(); - - std::shared_ptr<XmlPullParser> mParser; - std::string mLastError; - bool mOverride; - std::vector<XmlPullParser::Attribute> mAttributes; - std::vector<VarDecl> mVarDecls; - std::vector<Target> mTargets; - int32_t mNextTagId; -}; - -} // namespace aapt - -#endif // AAPT_BINDING_XML_PULL_PARSER_H diff --git a/tools/aapt2/BindingXmlPullParser_test.cpp b/tools/aapt2/BindingXmlPullParser_test.cpp deleted file mode 100644 index 28edcb672840..000000000000 --- a/tools/aapt2/BindingXmlPullParser_test.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SourceXmlPullParser.h" -#include "BindingXmlPullParser.h" - -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -namespace aapt { - -constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android"; - -TEST(BindingXmlPullParserTest, SubstituteBindingExpressionsWithTag) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" - << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" - << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n" - << " android:id=\"@+id/content\">\n" - << " <variable name=\"user\" type=\"com.android.test.User\"/>\n" - << " <TextView android:text=\"@{user.name}\" android:layout_width=\"wrap_content\"\n" - << " android:layout_height=\"wrap_content\"/>\n" - << "</LinearLayout>\n"; - std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input); - BindingXmlPullParser parser(sourceParser); - - ASSERT_EQ(XmlPullParser::Event::kStartNamespace, parser.next()); - EXPECT_EQ(std::u16string(u"http://schemas.android.com/apk/res/android"), - parser.getNamespaceUri()); - - ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.next()); - EXPECT_EQ(std::u16string(u"LinearLayout"), parser.getElementName()); - - while (parser.next() == XmlPullParser::Event::kText) {} - - ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent()); - EXPECT_EQ(std::u16string(u"TextView"), parser.getElementName()); - - ASSERT_EQ(3u, parser.getAttributeCount()); - const auto endAttr = parser.endAttributes(); - EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_width")); - EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_height")); - EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"tag")); - - while (parser.next() == XmlPullParser::Event::kText) {} - - ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent()); - - while (parser.next() == XmlPullParser::Event::kText) {} - - ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent()); - ASSERT_EQ(XmlPullParser::Event::kEndNamespace, parser.next()); -} - -TEST(BindingXmlPullParserTest, GenerateVariableDeclarations) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" - << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" - << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n" - << " android:id=\"@+id/content\">\n" - << " <variable name=\"user\" type=\"com.android.test.User\"/>\n" - << "</LinearLayout>\n"; - std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input); - BindingXmlPullParser parser(sourceParser); - - while (XmlPullParser::isGoodEvent(parser.next())) { - ASSERT_NE(XmlPullParser::Event::kBadDocument, parser.getEvent()); - } - - std::stringstream output; - ASSERT_TRUE(parser.writeToFile(output)); - - std::string result = output.str(); - EXPECT_NE(std::string::npos, - result.find("<entries name=\"user\" type=\"com.android.test.User\"/>")); -} - -TEST(BindingXmlPullParserTest, FailOnMissingNameOrTypeInVariableDeclaration) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" - << "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" - << " xmlns:bind=\"http://schemas.android.com/apk/binding\"\n" - << " android:id=\"@+id/content\">\n" - << " <variable name=\"user\"/>\n" - << "</LinearLayout>\n"; - std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input); - BindingXmlPullParser parser(sourceParser); - - while (XmlPullParser::isGoodEvent(parser.next())) {} - - EXPECT_EQ(XmlPullParser::Event::kBadDocument, parser.getEvent()); - EXPECT_FALSE(parser.getLastError().empty()); -} - - -} // namespace aapt diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index 6ddf94a681b8..8120fa709b3c 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -17,8 +17,8 @@ #include "ConfigDescription.h" #include "Locale.h" #include "SdkConstants.h" -#include "StringPiece.h" -#include "Util.h" +#include "util/StringPiece.h" +#include "util/Util.h" #include <androidfw/ResourceTypes.h> #include <string> diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h index 67b4b75cce0b..4af089dc282a 100644 --- a/tools/aapt2/ConfigDescription.h +++ b/tools/aapt2/ConfigDescription.h @@ -17,7 +17,7 @@ #ifndef AAPT_CONFIG_DESCRIPTION_H #define AAPT_CONFIG_DESCRIPTION_H -#include "StringPiece.h" +#include "util/StringPiece.h" #include <androidfw/ResourceTypes.h> #include <ostream> diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp index c57e35191a76..83708165a6d7 100644 --- a/tools/aapt2/ConfigDescription_test.cpp +++ b/tools/aapt2/ConfigDescription_test.cpp @@ -15,7 +15,7 @@ */ #include "ConfigDescription.h" -#include "StringPiece.h" +#include "util/StringPiece.h" #include <gtest/gtest.h> #include <string> diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index cf222c68de55..84f438520e90 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -17,7 +17,8 @@ #include "Debug.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "Util.h" +#include "util/Util.h" +#include "ValueVisitor.h" #include <algorithm> #include <iostream> @@ -29,102 +30,119 @@ namespace aapt { -struct PrintVisitor : ConstValueVisitor { - void visit(const Attribute& attr, ValueVisitorArgs&) override { +struct PrintVisitor : public ValueVisitor { + using ValueVisitor::visit; + + void visit(Attribute* attr) override { std::cout << "(attr) type="; - attr.printMask(std::cout); + attr->printMask(&std::cout); static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS; - if (attr.typeMask & kMask) { - for (const auto& symbol : attr.symbols) { - std::cout << "\n " - << symbol.symbol.name.entry << " (" << symbol.symbol.id << ") = " - << symbol.value; + if (attr->typeMask & kMask) { + for (const auto& symbol : attr->symbols) { + std::cout << "\n " << symbol.symbol.name.value().entry; + if (symbol.symbol.id) { + std::cout << " (" << symbol.symbol.id.value() << ")"; + } + std::cout << " = " << symbol.value; } } } - void visit(const Style& style, ValueVisitorArgs&) override { + void visit(Style* style) override { std::cout << "(style)"; - if (style.parent.name.isValid() || style.parent.id.isValid()) { + if (style->parent) { std::cout << " parent="; - if (style.parent.name.isValid()) { - std::cout << style.parent.name << " "; + if (style->parent.value().name) { + std::cout << style->parent.value().name.value() << " "; } - if (style.parent.id.isValid()) { - std::cout << style.parent.id; + if (style->parent.value().id) { + std::cout << style->parent.value().id.value(); } } - for (const auto& entry : style.entries) { + for (const auto& entry : style->entries) { std::cout << "\n "; - if (entry.key.name.isValid()) { - std::cout << entry.key.name.package << ":" << entry.key.name.entry; + if (entry.key.name) { + std::cout << entry.key.name.value().package << ":" << entry.key.name.value().entry; } - if (entry.key.id.isValid()) { - std::cout << "(" << entry.key.id << ")"; + if (entry.key.id) { + std::cout << "(" << entry.key.id.value() << ")"; } std::cout << "=" << *entry.value; } } - void visit(const Array& array, ValueVisitorArgs&) override { - array.print(std::cout); + void visit(Array* array) override { + array->print(&std::cout); } - void visit(const Plural& plural, ValueVisitorArgs&) override { - plural.print(std::cout); + void visit(Plural* plural) override { + plural->print(&std::cout); } - void visit(const Styleable& styleable, ValueVisitorArgs&) override { - styleable.print(std::cout); + void visit(Styleable* styleable) override { + styleable->print(&std::cout); } - void visitItem(const Item& item, ValueVisitorArgs& args) override { - item.print(std::cout); + void visitItem(Item* item) override { + item->print(&std::cout); } }; -void Debug::printTable(const std::shared_ptr<ResourceTable>& table) { - std::cout << "Package name=" << table->getPackage(); - if (table->getPackageId() != ResourceTable::kUnsetPackageId) { - std::cout << " id=" << std::hex << table->getPackageId() << std::dec; - } - std::cout << std::endl; - - for (const auto& type : *table) { - std::cout << " type " << type->type; - if (type->typeId != ResourceTableType::kUnsetTypeId) { - std::cout << " id=" << std::hex << type->typeId << std::dec; - } - std::cout << " entryCount=" << type->entries.size() << std::endl; - - std::vector<const ResourceEntry*> sortedEntries; - for (const auto& entry : type->entries) { - auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(), - [](const ResourceEntry* a, const ResourceEntry* b) -> bool { - return a->entryId < b->entryId; - }); - sortedEntries.insert(iter, entry.get()); +void Debug::printTable(ResourceTable* table) { + for (auto& package : table->packages) { + std::cout << "Package name=" << package->name; + if (package->id) { + std::cout << " id=" << std::hex << (int) package->id.value() << std::dec; } + std::cout << std::endl; - for (const ResourceEntry* entry : sortedEntries) { - ResourceId id = { table->getPackageId(), type->typeId, entry->entryId }; - ResourceName name = { table->getPackage(), type->type, entry->name }; - std::cout << " spec resource " << id << " " << name; - if (entry->publicStatus.isPublic) { - std::cout << " PUBLIC"; + for (const auto& type : package->types) { + std::cout << " type " << type->type; + if (type->id) { + std::cout << " id=" << std::hex << (int) type->id.value() << std::dec; + } + std::cout << " entryCount=" << type->entries.size() << std::endl; + + std::vector<const ResourceEntry*> sortedEntries; + for (const auto& entry : type->entries) { + auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(), + [](const ResourceEntry* a, const ResourceEntry* b) -> bool { + if (a->id && b->id) { + return a->id.value() < b->id.value(); + } else if (a->id) { + return true; + } else { + return false; + } + }); + sortedEntries.insert(iter, entry.get()); } - std::cout << std::endl; - PrintVisitor visitor; - for (const auto& value : entry->values) { - std::cout << " (" << value.config << ") "; - value.value->accept(visitor, {}); + for (const ResourceEntry* entry : sortedEntries) { + ResourceId id = { + package->id ? package->id.value() : uint8_t(0), + type->id ? type->id.value() : uint8_t(0), + entry->id ? entry->id.value() : uint16_t(0) + }; + + ResourceName name = { package->name, type->type, entry->name }; + std::cout << " spec resource " << id << " " << name; + if (entry->publicStatus.isPublic) { + std::cout << " PUBLIC"; + } std::cout << std::endl; + + PrintVisitor visitor; + for (const auto& value : entry->values) { + std::cout << " (" << value.config << ") "; + value.value->accept(&visitor); + std::cout << std::endl; + } } } } @@ -136,8 +154,7 @@ static size_t getNodeIndex(const std::vector<ResourceName>& names, const Resourc return std::distance(names.begin(), iter); } -void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table, - const ResourceName& targetStyle) { +void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyle) { std::map<ResourceName, std::set<ResourceName>> graph; std::queue<ResourceName> stylesToVisit; @@ -150,17 +167,16 @@ void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table, continue; } - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = table->findResource(styleName); - if (entry) { + Maybe<ResourceTable::SearchResult> result = table->findResource(styleName); + if (result) { + ResourceEntry* entry = result.value().entry; for (const auto& value : entry->values) { - visitFunc<Style>(*value.value, [&](const Style& style) { - if (style.parent.name.isValid()) { - parents.insert(style.parent.name); - stylesToVisit.push(style.parent.name); + if (Style* style = valueCast<Style>(value.value.get())) { + if (style->parent && style->parent.value().name) { + parents.insert(style->parent.value().name.value()); + stylesToVisit.push(style->parent.value().name.value()); } - }); + } } } } diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h index cdb3dcb6a5d8..5b0d7d69e703 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -20,13 +20,11 @@ #include "Resource.h" #include "ResourceTable.h" -#include <memory> - namespace aapt { struct Debug { - static void printTable(const std::shared_ptr<ResourceTable>& table); - static void printStyleGraph(const std::shared_ptr<ResourceTable>& table, + static void printTable(ResourceTable* table); + static void printStyleGraph(ResourceTable* table, const ResourceName& targetStyle); }; diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h new file mode 100644 index 000000000000..d20ae1b92191 --- /dev/null +++ b/tools/aapt2/Diagnostics.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_DIAGNOSTICS_H +#define AAPT_DIAGNOSTICS_H + +#include "Source.h" + +#include "util/StringPiece.h" +#include "util/Util.h" + +#include <iostream> +#include <sstream> +#include <string> + +namespace aapt { + +struct DiagMessageActual { + Source source; + std::string message; +}; + +struct DiagMessage { +private: + Source mSource; + std::stringstream mMessage; + +public: + DiagMessage() = default; + + DiagMessage(const StringPiece& src) : mSource(src) { + } + + DiagMessage(const Source& src) : mSource(src) { + } + + template <typename T> DiagMessage& operator<<(const T& value) { + mMessage << value; + return *this; + } +/* + template <typename T> DiagMessage& operator<<( + const ::std::function<::std::ostream&(::std::ostream&)>& f) { + f(mMessage); + return *this; + }*/ + + DiagMessageActual build() const { + return DiagMessageActual{ mSource, mMessage.str() }; + } +}; + +struct IDiagnostics { + virtual ~IDiagnostics() = default; + + virtual void error(const DiagMessage& message) = 0; + virtual void warn(const DiagMessage& message) = 0; + virtual void note(const DiagMessage& message) = 0; +}; + +struct StdErrDiagnostics : public IDiagnostics { + void emit(const DiagMessage& msg, const char* tag) { + DiagMessageActual actual = msg.build(); + if (!actual.source.path.empty()) { + std::cerr << actual.source << ": "; + } + std::cerr << tag << actual.message << "." << std::endl; + } + + void error(const DiagMessage& msg) override { + emit(msg, "error: "); + } + + void warn(const DiagMessage& msg) override { + emit(msg, "warn: "); + } + + void note(const DiagMessage& msg) override { + emit(msg, "note: "); + } +}; + +} // namespace aapt + +#endif /* AAPT_DIAGNOSTICS_H */ diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp deleted file mode 100644 index 76985da99912..000000000000 --- a/tools/aapt2/Flag.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "Flag.h" -#include "StringPiece.h" - -#include <functional> -#include <iomanip> -#include <iostream> -#include <string> -#include <vector> - -namespace aapt { -namespace flag { - -struct Flag { - std::string name; - std::string description; - std::function<bool(const StringPiece&, std::string*)> action; - bool required; - bool* flagResult; - bool flagValueWhenSet; - bool parsed; -}; - -static std::vector<Flag> sFlags; -static std::vector<std::string> sArgs; - -static std::function<bool(const StringPiece&, std::string*)> wrap( - const std::function<void(const StringPiece&)>& action) { - return [action](const StringPiece& arg, std::string*) -> bool { - action(arg); - return true; - }; -} - -void optionalFlag(const StringPiece& name, const StringPiece& description, - std::function<void(const StringPiece&)> action) { - sFlags.push_back(Flag{ - name.toString(), description.toString(), wrap(action), - false, nullptr, false, false }); -} - -void requiredFlag(const StringPiece& name, const StringPiece& description, - std::function<void(const StringPiece&)> action) { - sFlags.push_back(Flag{ name.toString(), description.toString(), wrap(action), - true, nullptr, false, false }); -} - -void requiredFlag(const StringPiece& name, const StringPiece& description, - std::function<bool(const StringPiece&, std::string*)> action) { - sFlags.push_back(Flag{ name.toString(), description.toString(), action, - true, nullptr, false, false }); -} - -void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet, - bool* result) { - sFlags.push_back(Flag{ - name.toString(), description.toString(), {}, - false, result, resultWhenSet, false }); -} - -void usageAndDie(const StringPiece& command) { - std::cerr << command << " [options]"; - for (const Flag& flag : sFlags) { - if (flag.required) { - std::cerr << " " << flag.name << " arg"; - } - } - std::cerr << " files..." << std::endl << std::endl << "Options:" << std::endl; - - for (const Flag& flag : sFlags) { - std::string command = flag.name; - if (!flag.flagResult) { - command += " arg "; - } - std::cerr << " " << std::setw(30) << std::left << command - << flag.description << std::endl; - } - exit(1); -} - -void parse(int argc, char** argv, const StringPiece& command) { - std::string errorStr; - for (int i = 0; i < argc; i++) { - const StringPiece arg(argv[i]); - if (*arg.data() != '-') { - sArgs.push_back(arg.toString()); - continue; - } - - bool match = false; - for (Flag& flag : sFlags) { - if (arg == flag.name) { - match = true; - flag.parsed = true; - if (flag.flagResult) { - *flag.flagResult = flag.flagValueWhenSet; - } else { - i++; - if (i >= argc) { - std::cerr << flag.name << " missing argument." << std::endl - << std::endl; - usageAndDie(command); - } - - if (!flag.action(argv[i], &errorStr)) { - std::cerr << errorStr << "." << std::endl << std::endl; - usageAndDie(command); - } - } - break; - } - } - - if (!match) { - std::cerr << "unknown option '" << arg << "'." << std::endl << std::endl; - usageAndDie(command); - } - } - - for (const Flag& flag : sFlags) { - if (flag.required && !flag.parsed) { - std::cerr << "missing required flag " << flag.name << std::endl << std::endl; - usageAndDie(command); - } - } -} - -const std::vector<std::string>& getArgs() { - return sArgs; -} - -} // namespace flag -} // namespace aapt diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h deleted file mode 100644 index e86374283986..000000000000 --- a/tools/aapt2/Flag.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef AAPT_FLAG_H -#define AAPT_FLAG_H - -#include "StringPiece.h" - -#include <functional> -#include <string> -#include <vector> - -namespace aapt { -namespace flag { - -void requiredFlag(const StringPiece& name, const StringPiece& description, - std::function<void(const StringPiece&)> action); - -void requiredFlag(const StringPiece& name, const StringPiece& description, - std::function<bool(const StringPiece&, std::string*)> action); - -void optionalFlag(const StringPiece& name, const StringPiece& description, - std::function<void(const StringPiece&)> action); - -void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet, - bool* result); - -void usageAndDie(const StringPiece& command); - -void parse(int argc, char** argv, const StringPiece& command); - -const std::vector<std::string>& getArgs(); - -} // namespace flag -} // namespace aapt - -#endif // AAPT_FLAG_H diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp new file mode 100644 index 000000000000..6ae5af7bb92a --- /dev/null +++ b/tools/aapt2/Flags.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Flags.h" +#include "util/StringPiece.h" + +#include <iomanip> +#include <iostream> +#include <string> +#include <vector> + +namespace aapt { + +Flags& Flags::requiredFlag(const StringPiece& name, const StringPiece& description, + std::string* value) { + auto func = [value](const StringPiece& arg) -> bool { + *value = arg.toString(); + return true; + }; + + mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false}); + return *this; +} + +Flags& Flags::requiredFlagList(const StringPiece& name, const StringPiece& description, + std::vector<std::string>* value) { + auto func = [value](const StringPiece& arg) -> bool { + value->push_back(arg.toString()); + return true; + }; + + mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false }); + return *this; +} + +Flags& Flags::optionalFlag(const StringPiece& name, const StringPiece& description, + Maybe<std::string>* value) { + auto func = [value](const StringPiece& arg) -> bool { + *value = arg.toString(); + return true; + }; + + mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false }); + return *this; +} + +Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description, + std::vector<std::string>* value) { + auto func = [value](const StringPiece& arg) -> bool { + value->push_back(arg.toString()); + return true; + }; + + mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false }); + return *this; +} + +Flags& Flags::optionalSwitch(const StringPiece& name, const StringPiece& description, + bool* value) { + auto func = [value](const StringPiece& arg) -> bool { + *value = true; + return true; + }; + + mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 0, false }); + return *this; +} + +void Flags::usage(const StringPiece& command, std::ostream* out) { + *out << command << " [options]"; + for (const Flag& flag : mFlags) { + if (flag.required) { + *out << " " << flag.name << " arg"; + } + } + + *out << " files...\n\nOptions:\n"; + + for (const Flag& flag : mFlags) { + std::string argLine = flag.name; + if (flag.numArgs > 0) { + argLine += " arg"; + } + *out << " " << std::setw(30) << std::left << argLine << flag.description << "\n"; + } + *out << " " << std::setw(30) << std::left << "-h" << "Displays this help menu\n"; + out->flush(); +} + +bool Flags::parse(const StringPiece& command, const std::vector<StringPiece>& args, + std::ostream* outError) { + for (size_t i = 0; i < args.size(); i++) { + StringPiece arg = args[i]; + if (*(arg.data()) != '-') { + mArgs.push_back(arg.toString()); + continue; + } + + if (arg == "-h" || arg == "--help") { + usage(command, outError); + return false; + } + + bool match = false; + for (Flag& flag : mFlags) { + if (arg == flag.name) { + if (flag.numArgs > 0) { + i++; + if (i >= args.size()) { + *outError << flag.name << " missing argument.\n\n"; + usage(command, outError); + return false; + } + flag.action(args[i]); + } else { + flag.action({}); + } + flag.parsed = true; + match = true; + break; + } + } + + if (!match) { + *outError << "unknown option '" << arg << "'.\n\n"; + usage(command, outError); + return false; + } + } + + for (const Flag& flag : mFlags) { + if (flag.required && !flag.parsed) { + *outError << "missing required flag " << flag.name << "\n\n"; + usage(command, outError); + return false; + } + } + return true; +} + +const std::vector<std::string>& Flags::getArgs() { + return mArgs; +} + +} // namespace aapt diff --git a/tools/aapt2/Flags.h b/tools/aapt2/Flags.h new file mode 100644 index 000000000000..ce7a4857eb6e --- /dev/null +++ b/tools/aapt2/Flags.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_FLAGS_H +#define AAPT_FLAGS_H + +#include "util/Maybe.h" +#include "util/StringPiece.h" + +#include <functional> +#include <ostream> +#include <string> +#include <vector> + +namespace aapt { + +class Flags { +public: + Flags& requiredFlag(const StringPiece& name, const StringPiece& description, + std::string* value); + Flags& requiredFlagList(const StringPiece& name, const StringPiece& description, + std::vector<std::string>* value); + Flags& optionalFlag(const StringPiece& name, const StringPiece& description, + Maybe<std::string>* value); + Flags& optionalFlagList(const StringPiece& name, const StringPiece& description, + std::vector<std::string>* value); + Flags& optionalSwitch(const StringPiece& name, const StringPiece& description, + bool* value); + + void usage(const StringPiece& command, std::ostream* out); + + bool parse(const StringPiece& command, const std::vector<StringPiece>& args, + std::ostream* outError); + + const std::vector<std::string>& getArgs(); + +private: + struct Flag { + std::string name; + std::string description; + std::function<bool(const StringPiece& value)> action; + bool required; + size_t numArgs; + + bool parsed; + }; + + std::vector<Flag> mFlags; + std::vector<std::string> mArgs; +}; + +} // namespace aapt + +#endif // AAPT_FLAGS_H diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp index e2ffe79c764d..84a412545dd8 100644 --- a/tools/aapt2/JavaClassGenerator.cpp +++ b/tools/aapt2/JavaClassGenerator.cpp @@ -19,7 +19,7 @@ #include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "StringPiece.h" +#include "util/StringPiece.h" #include <algorithm> #include <ostream> @@ -32,21 +32,18 @@ namespace aapt { // The number of attributes to emit per line in a Styleable array. constexpr size_t kAttribsPerLine = 4; -JavaClassGenerator::JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, - Options options) : +JavaClassGenerator::JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options) : mTable(table), mOptions(options) { } -static void generateHeader(std::ostream& out, const StringPiece16& package) { - out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n" - " *\n" - " * This class was automatically generated by the\n" - " * aapt tool from the resource data it found. It\n" - " * should not be modified by hand.\n" - " */\n\n"; - out << "package " << package << ";" - << std::endl - << std::endl; +static void generateHeader(const StringPiece16& packageNameToGenerate, std::ostream* out) { + *out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n" + " *\n" + " * This class was automatically generated by the\n" + " * aapt tool from the resource data it found. It\n" + " * should not be modified by hand.\n" + " */\n\n" + "package " << packageNameToGenerate << ";\n\n"; } static const std::set<StringPiece16> sJavaIdentifiers = { @@ -80,42 +77,32 @@ static std::u16string transform(const StringPiece16& symbol) { return output; } -struct GenArgs : ValueVisitorArgs { - GenArgs(std::ostream* o, const std::u16string* p, std::u16string* e) : - out(o), package(p), entryName(e) { - } - - std::ostream* out; - const std::u16string* package; - std::u16string* entryName; -}; - -void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) { +void JavaClassGenerator::generateStyleable(const StringPiece16& packageNameToGenerate, + const std::u16string& entryName, + const Styleable* styleable, + std::ostream* out) { const StringPiece finalModifier = mOptions.useFinal ? " final" : ""; - std::ostream* out = static_cast<GenArgs&>(a).out; - const std::u16string* package = static_cast<GenArgs&>(a).package; - std::u16string* entryName = static_cast<GenArgs&>(a).entryName; // This must be sorted by resource ID. std::vector<std::pair<ResourceId, ResourceNameRef>> sortedAttributes; - sortedAttributes.reserve(styleable.entries.size()); - for (const auto& attr : styleable.entries) { + sortedAttributes.reserve(styleable->entries.size()); + for (const auto& attr : styleable->entries) { // If we are not encoding final attributes, the styleable entry may have no ID // if we are building a static library. - assert((!mOptions.useFinal || attr.id.isValid()) && "no ID set for Styleable entry"); - assert(attr.name.isValid() && "no name set for Styleable entry"); - sortedAttributes.emplace_back(attr.id, attr.name); + assert((!mOptions.useFinal || attr.id) && "no ID set for Styleable entry"); + assert(attr.name && "no name set for Styleable entry"); + sortedAttributes.emplace_back(attr.id ? attr.id.value() : ResourceId(0), attr.name.value()); } std::sort(sortedAttributes.begin(), sortedAttributes.end()); // First we emit the array containing the IDs of each attribute. *out << " " - << "public static final int[] " << transform(*entryName) << " = {"; + << "public static final int[] " << transform(entryName) << " = {"; const size_t attrCount = sortedAttributes.size(); for (size_t i = 0; i < attrCount; i++) { if (i % kAttribsPerLine == 0) { - *out << std::endl << " "; + *out << "\n "; } *out << sortedAttributes[i].first; @@ -123,44 +110,46 @@ void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) *out << ", "; } } - *out << std::endl << " };" << std::endl; + *out << "\n };\n"; // Now we emit the indices into the array. for (size_t i = 0; i < attrCount; i++) { *out << " " << "public static" << finalModifier - << " int " << transform(*entryName); + << " int " << transform(entryName); // We may reference IDs from other packages, so prefix the entry name with // the package. const ResourceNameRef& itemName = sortedAttributes[i].second; - if (itemName.package != *package) { + if (packageNameToGenerate != itemName.package) { *out << "_" << transform(itemName.package); } - *out << "_" << transform(itemName.entry) << " = " << i << ";" << std::endl; + *out << "_" << transform(itemName.entry) << " = " << i << ";\n"; } } -bool JavaClassGenerator::generateType(const std::u16string& package, size_t packageId, - const ResourceTableType& type, std::ostream& out) { +bool JavaClassGenerator::generateType(const StringPiece16& packageNameToGenerate, + const ResourceTablePackage* package, + const ResourceTableType* type, + std::ostream* out) { const StringPiece finalModifier = mOptions.useFinal ? " final" : ""; std::u16string unmangledPackage; std::u16string unmangledName; - for (const auto& entry : type.entries) { - ResourceId id = { packageId, type.typeId, entry->entryId }; + for (const auto& entry : type->entries) { + ResourceId id = { package->id.value(), type->id.value(), entry->id.value() }; assert(id.isValid()); unmangledName = entry->name; if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) { // The entry name was mangled, and we successfully unmangled it. // Check that we want to emit this symbol. - if (package != unmangledPackage) { + if (package->name != unmangledPackage) { // Skip the entry if it doesn't belong to the package we're writing. continue; } } else { - if (package != mTable->getPackage()) { + if (packageNameToGenerate != package->name) { // We are processing a mangled package name, // but this is a non-mangled resource. continue; @@ -168,40 +157,42 @@ bool JavaClassGenerator::generateType(const std::u16string& package, size_t pack } if (!isValidSymbol(unmangledName)) { - ResourceNameRef resourceName = { package, type.type, unmangledName }; + ResourceNameRef resourceName = { packageNameToGenerate, type->type, unmangledName }; std::stringstream err; err << "invalid symbol name '" << resourceName << "'"; mError = err.str(); return false; } - if (type.type == ResourceType::kStyleable) { + if (type->type == ResourceType::kStyleable) { assert(!entry->values.empty()); - entry->values.front().value->accept(*this, GenArgs{ &out, &package, &unmangledName }); + generateStyleable(packageNameToGenerate, unmangledName, static_cast<const Styleable*>( + entry->values.front().value.get()), out); } else { - out << " " << "public static" << finalModifier - << " int " << transform(unmangledName) << " = " << id << ";" << std::endl; + *out << " " << "public static" << finalModifier + << " int " << transform(unmangledName) << " = " << id << ";\n"; } } return true; } -bool JavaClassGenerator::generate(const std::u16string& package, std::ostream& out) { - const size_t packageId = mTable->getPackageId(); +bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) { + generateHeader(packageNameToGenerate, out); - generateHeader(out, package); + *out << "public final class R {\n"; - out << "public final class R {" << std::endl; - - for (const auto& type : *mTable) { - out << " public static final class " << type->type << " {" << std::endl; - if (!generateType(package, packageId, *type, out)) { - return false; + for (const auto& package : mTable->packages) { + for (const auto& type : package->types) { + *out << " public static final class " << type->type << " {\n"; + if (!generateType(packageNameToGenerate, package.get(), type.get(), out)) { + return false; + } + *out << " }\n"; } - out << " }" << std::endl; } - out << "}" << std::endl; + *out << "}\n"; + out->flush(); return true; } diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h index f8b9ee3f1fc8..682bacfb0db4 100644 --- a/tools/aapt2/JavaClassGenerator.h +++ b/tools/aapt2/JavaClassGenerator.h @@ -20,28 +20,27 @@ #include "ResourceTable.h" #include "ResourceValues.h" +#include "util/StringPiece.h" + #include <ostream> #include <string> namespace aapt { +struct JavaClassGeneratorOptions { + /* + * Specifies whether to use the 'final' modifier + * on resource entries. Default is true. + */ + bool useFinal = true; +}; + /* * Generates the R.java file for a resource table. */ -class JavaClassGenerator : ConstValueVisitor { +class JavaClassGenerator { public: - /* - * A set of options for this JavaClassGenerator. - */ - struct Options { - /* - * Specifies whether to use the 'final' modifier - * on resource entries. Default is true. - */ - bool useFinal = true; - }; - - JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options options); + JavaClassGenerator(ResourceTable* table, JavaClassGeneratorOptions options); /* * Writes the R.java file to `out`. Only symbols belonging to `package` are written. @@ -50,21 +49,23 @@ public: * We need to generate these symbols in a separate file. * Returns true on success. */ - bool generate(const std::u16string& package, std::ostream& out); - - /* - * ConstValueVisitor implementation. - */ - void visit(const Styleable& styleable, ValueVisitorArgs& args); + bool generate(const StringPiece16& package, std::ostream* out); const std::string& getError() const; private: - bool generateType(const std::u16string& package, size_t packageId, - const ResourceTableType& type, std::ostream& out); + bool generateType(const StringPiece16& packageNameToGenerate, + const ResourceTablePackage* package, + const ResourceTableType* type, + std::ostream* out); + + void generateStyleable(const StringPiece16& packageNameToGenerate, + const std::u16string& entryName, + const Styleable* styleable, + std::ostream* out); - std::shared_ptr<const ResourceTable> mTable; - Options mOptions; + ResourceTable* mTable; + JavaClassGeneratorOptions mOptions; std::string mError; }; diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp index b385ff4828e1..48fcf8cb748f 100644 --- a/tools/aapt2/JavaClassGenerator_test.cpp +++ b/tools/aapt2/JavaClassGenerator_test.cpp @@ -15,12 +15,9 @@ */ #include "JavaClassGenerator.h" -#include "Linker.h" -#include "MockResolver.h" -#include "ResourceTable.h" -#include "ResourceTableResolver.h" -#include "ResourceValues.h" -#include "Util.h" +#include "util/Util.h" + +#include "test/Builders.h" #include <gtest/gtest.h> #include <sstream> @@ -28,51 +25,34 @@ namespace aapt { -struct JavaClassGeneratorTest : public ::testing::Test { - virtual void SetUp() override { - mTable = std::make_shared<ResourceTable>(); - mTable->setPackage(u"android"); - mTable->setPackageId(0x01); - } - - bool addResource(const ResourceNameRef& name, ResourceId id) { - return mTable->addResource(name, id, {}, SourceLine{ "test.xml", 21 }, - util::make_unique<Id>()); - } - - std::shared_ptr<ResourceTable> mTable; -}; +TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"android", 0x01) + .addSimple(u"@android:id/class", ResourceId(0x01020000)) + .build(); -TEST_F(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) { - ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"class" }, - ResourceId{ 0x01, 0x02, 0x0000 })); - - JavaClassGenerator generator(mTable, {}); + JavaClassGenerator generator(table.get(), {}); std::stringstream out; - EXPECT_FALSE(generator.generate(mTable->getPackage(), out)); + EXPECT_FALSE(generator.generate(u"android", &out)); } -TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { - ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"hey-man" }, - ResourceId{ 0x01, 0x02, 0x0000 })); - - ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kAttr, u"cool.attr" }, - ResourceId{ 0x01, 0x01, 0x0000 })); - - std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); - Reference ref(ResourceName{ u"android", ResourceType::kAttr, u"cool.attr"}); - ref.id = ResourceId{ 0x01, 0x01, 0x0000 }; - styleable->entries.emplace_back(ref); +TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"android", 0x01) + .addSimple(u"@android:id/hey-man", ResourceId(0x01020000)) + .addSimple(u"@android:attr/cool.attr", ResourceId(0x01010000)) + .addValue(u"@android:styleable/hey.dude", ResourceId(0x01030000), + test::StyleableBuilder() + .addItem(u"@android:attr/cool.attr", ResourceId(0x01010000)) + .build()) + .build(); - ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"hey.dude" }, - ResourceId{ 0x01, 0x03, 0x0000 }, {}, - SourceLine{ "test.xml", 21 }, std::move(styleable))); - - JavaClassGenerator generator(mTable, {}); + JavaClassGenerator generator(table.get(), {}); std::stringstream out; - EXPECT_TRUE(generator.generate(mTable->getPackage(), out)); + EXPECT_TRUE(generator.generate(u"android", &out)); + std::string output = out.str(); EXPECT_NE(std::string::npos, @@ -85,14 +65,15 @@ TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { output.find("public static final int hey_dude_cool_attr = 0;")); } - -TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) { +/* + * TODO(adamlesinski): Re-enable this once we get merging working again. + * TEST(JavaClassGeneratorTest, EmitPackageMangledSymbols) { ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" }, ResourceId{ 0x01, 0x02, 0x0000 })); ResourceTable table; table.setPackage(u"com.lib"); ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {}, - SourceLine{ "lib.xml", 33 }, util::make_unique<Id>())); + Source{ "lib.xml", 33 }, util::make_unique<Id>())); ASSERT_TRUE(mTable->merge(std::move(table))); Linker linker(mTable, @@ -113,34 +94,29 @@ TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) { output = out.str(); EXPECT_NE(std::string::npos, output.find("int test =")); EXPECT_EQ(std::string::npos, output.find("int foo =")); -} - -TEST_F(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { - std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); - styleable->entries.emplace_back(ResourceNameRef{ mTable->getPackage(), - ResourceType::kAttr, - u"bar" }); - styleable->entries.emplace_back(ResourceNameRef{ u"com.lib", ResourceType::kAttr, u"bar" }); - ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"Foo" }, {}, {}, - std::move(styleable))); - - std::shared_ptr<IResolver> resolver = std::make_shared<MockResolver>(mTable, - std::map<ResourceName, ResourceId>({ - { ResourceName{ u"android", ResourceType::kAttr, u"bar" }, - ResourceId{ 0x01, 0x01, 0x0000 } }, - { ResourceName{ u"com.lib", ResourceType::kAttr, u"bar" }, - ResourceId{ 0x02, 0x01, 0x0000 } }})); - - Linker linker(mTable, resolver, {}); - ASSERT_TRUE(linker.linkAndValidate()); - - JavaClassGenerator generator(mTable, {}); +}*/ + +TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"android", 0x01) + .setPackageId(u"com.lib", 0x02) + .addSimple(u"@android:attr/bar", ResourceId(0x01010000)) + .addSimple(u"@com.lib:attr/bar", ResourceId(0x02010000)) + .addValue(u"@android:styleable/foo", ResourceId(0x01030000), + test::StyleableBuilder() + .addItem(u"@android:attr/bar", ResourceId(0x01010000)) + .addItem(u"@com.lib:attr/bar", ResourceId(0x02010000)) + .build()) + .build(); + + JavaClassGenerator generator(table.get(), {}); std::stringstream out; - EXPECT_TRUE(generator.generate(mTable->getPackage(), out)); + EXPECT_TRUE(generator.generate(u"android", &out)); + std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("int Foo_bar =")); - EXPECT_NE(std::string::npos, output.find("int Foo_com_lib_bar =")); + EXPECT_NE(std::string::npos, output.find("int foo_bar =")); + EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar =")); } } // namespace aapt diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp deleted file mode 100644 index c37cc932cd3b..000000000000 --- a/tools/aapt2/Linker.cpp +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Linker.h" -#include "Logger.h" -#include "NameMangler.h" -#include "Resolver.h" -#include "ResourceParser.h" -#include "ResourceTable.h" -#include "ResourceValues.h" -#include "StringPiece.h" -#include "Util.h" - -#include <androidfw/AssetManager.h> -#include <array> -#include <bitset> -#include <iostream> -#include <map> -#include <ostream> -#include <set> -#include <sstream> -#include <tuple> -#include <vector> - -namespace aapt { - -Linker::Args::Args(const ResourceNameRef& r, const SourceLine& s) : referrer(r), source(s) { -} - -Linker::Linker(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver, const Options& options) : - mResolver(resolver), mTable(table), mOptions(options), mError(false) { -} - -bool Linker::linkAndValidate() { - std::bitset<256> usedTypeIds; - std::array<std::set<uint16_t>, 256> usedIds; - usedTypeIds.set(0); - - // Collect which resource IDs are already taken. - for (auto& type : *mTable) { - if (type->typeId != ResourceTableType::kUnsetTypeId) { - // The ID for this type has already been set. We - // mark this ID as taken so we don't re-assign it - // later. - usedTypeIds.set(type->typeId); - } - - for (auto& entry : type->entries) { - if (type->typeId != ResourceTableType::kUnsetTypeId && - entry->entryId != ResourceEntry::kUnsetEntryId) { - // The ID for this entry has already been set. We - // mark this ID as taken so we don't re-assign it - // later. - usedIds[type->typeId].insert(entry->entryId); - } - } - } - - // Assign resource IDs that are available. - size_t nextTypeIndex = 0; - for (auto& type : *mTable) { - if (type->typeId == ResourceTableType::kUnsetTypeId) { - while (nextTypeIndex < usedTypeIds.size() && usedTypeIds[nextTypeIndex]) { - nextTypeIndex++; - } - type->typeId = nextTypeIndex++; - } - - const auto endEntryIter = std::end(usedIds[type->typeId]); - auto nextEntryIter = std::begin(usedIds[type->typeId]); - size_t nextIndex = 0; - for (auto& entry : type->entries) { - if (entry->entryId == ResourceTableType::kUnsetTypeId) { - while (nextEntryIter != endEntryIter && - nextIndex == *nextEntryIter) { - nextIndex++; - ++nextEntryIter; - } - entry->entryId = nextIndex++; - } - } - } - - // Now do reference linking. - for (auto& type : *mTable) { - for (auto& entry : type->entries) { - if (entry->publicStatus.isPublic && entry->values.empty()) { - // A public resource has no values. It will not be encoded - // properly without a symbol table. This is a unresolved symbol. - addUnresolvedSymbol(ResourceNameRef{ - mTable->getPackage(), type->type, entry->name }, - entry->publicStatus.source); - continue; - } - - for (auto& valueConfig : entry->values) { - // Dispatch to the right method of this linker - // based on the value's type. - valueConfig.value->accept(*this, Args{ - ResourceNameRef{ mTable->getPackage(), type->type, entry->name }, - valueConfig.source - }); - } - } - } - return !mError; -} - -const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const { - return mUnresolvedSymbols; -} - -void Linker::doResolveReference(Reference& reference, const SourceLine& source) { - Maybe<ResourceId> result = mResolver->findId(reference.name); - if (!result) { - addUnresolvedSymbol(reference.name, source); - return; - } - assert(result.value().isValid()); - - if (mOptions.linkResourceIds) { - reference.id = result.value(); - } else { - reference.id = 0; - } -} - -const Attribute* Linker::doResolveAttribute(Reference& attribute, const SourceLine& source) { - Maybe<IResolver::Entry> result = mResolver->findAttribute(attribute.name); - if (!result || !result.value().attr) { - addUnresolvedSymbol(attribute.name, source); - return nullptr; - } - - const IResolver::Entry& entry = result.value(); - assert(entry.id.isValid()); - - if (mOptions.linkResourceIds) { - attribute.id = entry.id; - } else { - attribute.id = 0; - } - return entry.attr; -} - -void Linker::visit(Reference& reference, ValueVisitorArgs& a) { - Args& args = static_cast<Args&>(a); - - if (reference.name.entry.empty()) { - // We can't have a completely bad reference. - if (!reference.id.isValid()) { - Logger::error() << "srsly? " << args.referrer << std::endl; - assert(reference.id.isValid()); - } - - // This reference has no name but has an ID. - // It is a really bad error to have no name and have the same - // package ID. - assert(reference.id.packageId() != mTable->getPackageId()); - - // The reference goes outside this package, let it stay as a - // resource ID because it will not change. - return; - } - - doResolveReference(reference, args.source); - - // TODO(adamlesinski): Verify the referencedType is another reference - // or a compatible primitive. -} - -void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine& source, - const Attribute& attr, std::unique_ptr<Item>& value) { - std::unique_ptr<Item> convertedValue; - visitFunc<RawString>(*value, [&](RawString& str) { - // This is a raw string, so check if it can be converted to anything. - // We can NOT swap value with the converted value in here, since - // we called through the original value. - - auto onCreateReference = [&](const ResourceName& name) { - // We should never get here. All references would have been - // parsed in the parser phase. - assert(false); - }; - - convertedValue = ResourceParser::parseItemForAttribute(*str.value, attr, - onCreateReference); - if (!convertedValue && attr.typeMask & android::ResTable_map::TYPE_STRING) { - // Last effort is to parse as a string. - util::StringBuilder builder; - builder.append(*str.value); - if (builder) { - convertedValue = util::make_unique<String>( - mTable->getValueStringPool().makeRef(builder.str())); - } - } - }); - - if (convertedValue) { - value = std::move(convertedValue); - } - - // Process this new or old value (it can be a reference!). - value->accept(*this, Args{ name, source }); - - // Flatten the value to see what resource type it is. - android::Res_value resValue; - value->flatten(resValue); - - // Always allow references. - const uint32_t typeMask = attr.typeMask | android::ResTable_map::TYPE_REFERENCE; - if (!(typeMask & ResourceParser::androidTypeToAttributeTypeMask(resValue.dataType))) { - Logger::error(source) - << *value - << " is not compatible with attribute " - << attr - << "." - << std::endl; - mError = true; - } -} - -void Linker::visit(Style& style, ValueVisitorArgs& a) { - Args& args = static_cast<Args&>(a); - - if (style.parent.name.isValid() || style.parent.id.isValid()) { - visit(style.parent, a); - } - - for (Style::Entry& styleEntry : style.entries) { - const Attribute* attr = doResolveAttribute(styleEntry.key, args.source); - if (attr) { - processAttributeValue(args.referrer, args.source, *attr, styleEntry.value); - } - } -} - -void Linker::visit(Attribute& attr, ValueVisitorArgs& a) { - static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM | - android::ResTable_map::TYPE_FLAGS; - if (attr.typeMask & kMask) { - for (auto& symbol : attr.symbols) { - visit(symbol.symbol, a); - } - } -} - -void Linker::visit(Styleable& styleable, ValueVisitorArgs& a) { - for (auto& attrRef : styleable.entries) { - visit(attrRef, a); - } -} - -void Linker::visit(Array& array, ValueVisitorArgs& a) { - Args& args = static_cast<Args&>(a); - - for (auto& item : array.items) { - item->accept(*this, Args{ args.referrer, args.source }); - } -} - -void Linker::visit(Plural& plural, ValueVisitorArgs& a) { - Args& args = static_cast<Args&>(a); - - for (auto& item : plural.values) { - if (item) { - item->accept(*this, Args{ args.referrer, args.source }); - } - } -} - -void Linker::addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source) { - mUnresolvedSymbols[name.toResourceName()].push_back(source); -} - -} // namespace aapt diff --git a/tools/aapt2/Linker.h b/tools/aapt2/Linker.h deleted file mode 100644 index 6f0351592fcd..000000000000 --- a/tools/aapt2/Linker.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AAPT_LINKER_H -#define AAPT_LINKER_H - -#include "Resolver.h" -#include "ResourceTable.h" -#include "ResourceValues.h" -#include "Source.h" -#include "StringPiece.h" - -#include <androidfw/AssetManager.h> -#include <map> -#include <memory> -#include <ostream> -#include <set> -#include <vector> - -namespace aapt { - -/** - * The Linker has two jobs. It follows resource references - * and verifies that their targert exists and that their - * types are compatible. The Linker will also assign resource - * IDs and fill in all the dependent references with the newly - * assigned resource IDs. - * - * To do this, the Linker builds a graph of references. This - * can be useful to do other analysis, like building a - * dependency graph of source files. The hope is to be able to - * add functionality that operates on the graph without - * overcomplicating the Linker. - * - * TODO(adamlesinski): Build the graph first then run the separate - * steps over the graph. - */ -class Linker : ValueVisitor { -public: - struct Options { - /** - * Assign resource Ids to references when linking. - * When building a static library, set this to false. - */ - bool linkResourceIds = true; - }; - - /** - * Create a Linker for the given resource table with the sources available in - * IResolver. IResolver should contain the ResourceTable as a source too. - */ - Linker(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver, const Options& options); - - Linker(const Linker&) = delete; - - virtual ~Linker() = default; - - /** - * Entry point to the linker. Assigns resource IDs, follows references, - * and validates types. Returns true if all references to defined values - * are type-compatible. Missing resource references are recorded but do - * not cause this method to fail. - */ - bool linkAndValidate(); - - /** - * Returns any references to resources that were not defined in any of the - * sources. - */ - using ResourceNameToSourceMap = std::map<ResourceName, std::vector<SourceLine>>; - const ResourceNameToSourceMap& getUnresolvedReferences() const; - -protected: - virtual void doResolveReference(Reference& reference, const SourceLine& source); - virtual const Attribute* doResolveAttribute(Reference& attribute, const SourceLine& source); - - std::shared_ptr<IResolver> mResolver; - -private: - struct Args : public ValueVisitorArgs { - Args(const ResourceNameRef& r, const SourceLine& s); - - const ResourceNameRef& referrer; - const SourceLine& source; - }; - - // - // Overrides of ValueVisitor - // - void visit(Reference& reference, ValueVisitorArgs& args) override; - void visit(Attribute& attribute, ValueVisitorArgs& args) override; - void visit(Styleable& styleable, ValueVisitorArgs& args) override; - void visit(Style& style, ValueVisitorArgs& args) override; - void visit(Array& array, ValueVisitorArgs& args) override; - void visit(Plural& plural, ValueVisitorArgs& args) override; - - void processAttributeValue(const ResourceNameRef& name, const SourceLine& source, - const Attribute& attr, std::unique_ptr<Item>& value); - - void addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source); - - std::shared_ptr<ResourceTable> mTable; - std::map<ResourceName, std::vector<SourceLine>> mUnresolvedSymbols; - Options mOptions; - bool mError; -}; - -} // namespace aapt - -#endif // AAPT_LINKER_H diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp deleted file mode 100644 index d897f9824a95..000000000000 --- a/tools/aapt2/Linker_test.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Linker.h" -#include "ResourceTable.h" -#include "ResourceTableResolver.h" -#include "ResourceValues.h" -#include "Util.h" - -#include <androidfw/AssetManager.h> -#include <gtest/gtest.h> -#include <string> - -namespace aapt { - -struct LinkerTest : public ::testing::Test { - virtual void SetUp() override { - mTable = std::make_shared<ResourceTable>(); - mTable->setPackage(u"android"); - mTable->setPackageId(0x01); - mLinker = std::make_shared<Linker>(mTable, std::make_shared<ResourceTableResolver>( - mTable, std::vector<std::shared_ptr<const android::AssetManager>>()), - Linker::Options{}); - - // Create a few attributes for use in the tests. - - addResource(ResourceName{ {}, ResourceType::kAttr, u"integer" }, - util::make_unique<Attribute>(false, android::ResTable_map::TYPE_INTEGER)); - - addResource(ResourceName{ {}, ResourceType::kAttr, u"string" }, - util::make_unique<Attribute>(false, android::ResTable_map::TYPE_STRING)); - - addResource(ResourceName{ {}, ResourceType::kId, u"apple" }, util::make_unique<Id>()); - - addResource(ResourceName{ {}, ResourceType::kId, u"banana" }, util::make_unique<Id>()); - - std::unique_ptr<Attribute> flagAttr = util::make_unique<Attribute>( - false, android::ResTable_map::TYPE_FLAGS); - flagAttr->symbols.push_back(Attribute::Symbol{ - ResourceNameRef{ u"android", ResourceType::kId, u"apple" }, 1 }); - flagAttr->symbols.push_back(Attribute::Symbol{ - ResourceNameRef{ u"android", ResourceType::kId, u"banana" }, 2 }); - addResource(ResourceName{ {}, ResourceType::kAttr, u"flags" }, std::move(flagAttr)); - } - - /* - * Convenience method for adding resources with the default configuration and some - * bogus source line. - */ - bool addResource(const ResourceNameRef& name, std::unique_ptr<Value> value) { - return mTable->addResource(name, {}, SourceLine{ "test.xml", 21 }, std::move(value)); - } - - std::shared_ptr<ResourceTable> mTable; - std::shared_ptr<Linker> mLinker; -}; - -TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) { - ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kString, u"foo" }, - util::make_unique<String>(mTable->getValueStringPool().makeRef(u"?123")))); - - ASSERT_TRUE(mLinker->linkAndValidate()); - EXPECT_TRUE(mLinker->getUnresolvedReferences().empty()); -} - -TEST_F(LinkerTest, EscapeAndConvertRawString) { - std::unique_ptr<Style> style = util::make_unique<Style>(); - style->entries.push_back(Style::Entry{ - ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" }, - util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u" 123")) - }); - const Style* result = style.get(); - ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" }, - std::move(style))); - - ASSERT_TRUE(mLinker->linkAndValidate()); - EXPECT_TRUE(mLinker->getUnresolvedReferences().empty()); - - EXPECT_NE(nullptr, dynamic_cast<BinaryPrimitive*>(result->entries.front().value.get())); -} - -TEST_F(LinkerTest, FailToConvertRawString) { - std::unique_ptr<Style> style = util::make_unique<Style>(); - style->entries.push_back(Style::Entry{ - ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" }, - util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"yo what is up?")) - }); - ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" }, - std::move(style))); - - ASSERT_FALSE(mLinker->linkAndValidate()); -} - -TEST_F(LinkerTest, ConvertRawStringToString) { - std::unique_ptr<Style> style = util::make_unique<Style>(); - style->entries.push_back(Style::Entry{ - ResourceNameRef{ u"android", ResourceType::kAttr, u"string" }, - util::make_unique<RawString>( - mTable->getValueStringPool().makeRef(u" \"this is \\u00fa\".")) - }); - const Style* result = style.get(); - ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" }, - std::move(style))); - - ASSERT_TRUE(mLinker->linkAndValidate()); - EXPECT_TRUE(mLinker->getUnresolvedReferences().empty()); - - const String* str = dynamic_cast<const String*>(result->entries.front().value.get()); - ASSERT_NE(nullptr, str); - EXPECT_EQ(*str->value, u"this is \u00fa."); -} - -TEST_F(LinkerTest, ConvertRawStringToFlags) { - std::unique_ptr<Style> style = util::make_unique<Style>(); - style->entries.push_back(Style::Entry{ - ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" }, - util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple")) - }); - const Style* result = style.get(); - ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" }, - std::move(style))); - - ASSERT_TRUE(mLinker->linkAndValidate()); - EXPECT_TRUE(mLinker->getUnresolvedReferences().empty()); - - const BinaryPrimitive* bin = dynamic_cast<const BinaryPrimitive*>( - result->entries.front().value.get()); - ASSERT_NE(nullptr, bin); - EXPECT_EQ(bin->value.data, 1u | 2u); -} - -TEST_F(LinkerTest, AllowReferenceWithOnlyResourceIdPointingToDifferentPackage) { - ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" }, - util::make_unique<Reference>(ResourceId{ 0x02, 0x01, 0x01 }))); - - ASSERT_TRUE(mLinker->linkAndValidate()); - EXPECT_TRUE(mLinker->getUnresolvedReferences().empty()); -} - -} // namespace aapt diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp index eed0ea71f6c0..20a2d0c6db37 100644 --- a/tools/aapt2/Locale.cpp +++ b/tools/aapt2/Locale.cpp @@ -15,7 +15,7 @@ */ #include "Locale.h" -#include "Util.h" +#include "util/Util.h" #include <algorithm> #include <ctype.h> diff --git a/tools/aapt2/Locale_test.cpp b/tools/aapt2/Locale_test.cpp index 4e154d6720d6..758e1e31c0e7 100644 --- a/tools/aapt2/Locale_test.cpp +++ b/tools/aapt2/Locale_test.cpp @@ -15,7 +15,7 @@ */ #include "Locale.h" -#include "Util.h" +#include "util/Util.h" #include <gtest/gtest.h> #include <string> diff --git a/tools/aapt2/Logger.cpp b/tools/aapt2/Logger.cpp deleted file mode 100644 index 384718567984..000000000000 --- a/tools/aapt2/Logger.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "Logger.h" -#include "Source.h" - -#include <memory> -#include <iostream> - -namespace aapt { - -Log::Log(std::ostream& _out, std::ostream& _err) : out(_out), err(_err) { -} - -std::shared_ptr<Log> Logger::sLog(std::make_shared<Log>(std::cerr, std::cerr)); - -void Logger::setLog(const std::shared_ptr<Log>& log) { - sLog = log; -} - -std::ostream& Logger::error() { - return sLog->err << "error: "; -} - -std::ostream& Logger::error(const Source& source) { - return sLog->err << source << ": error: "; -} - -std::ostream& Logger::error(const SourceLine& source) { - return sLog->err << source << ": error: "; -} - -std::ostream& Logger::warn() { - return sLog->err << "warning: "; -} - -std::ostream& Logger::warn(const Source& source) { - return sLog->err << source << ": warning: "; -} - -std::ostream& Logger::warn(const SourceLine& source) { - return sLog->err << source << ": warning: "; -} - -std::ostream& Logger::note() { - return sLog->out << "note: "; -} - -std::ostream& Logger::note(const Source& source) { - return sLog->err << source << ": note: "; -} - -std::ostream& Logger::note(const SourceLine& source) { - return sLog->err << source << ": note: "; -} - -SourceLogger::SourceLogger(const Source& source) -: mSource(source) { -} - -std::ostream& SourceLogger::error() { - return Logger::error(mSource); -} - -std::ostream& SourceLogger::error(size_t line) { - return Logger::error(SourceLine{ mSource.path, line }); -} - -std::ostream& SourceLogger::warn() { - return Logger::warn(mSource); -} - -std::ostream& SourceLogger::warn(size_t line) { - return Logger::warn(SourceLine{ mSource.path, line }); -} - -std::ostream& SourceLogger::note() { - return Logger::note(mSource); -} - -std::ostream& SourceLogger::note(size_t line) { - return Logger::note(SourceLine{ mSource.path, line }); -} - -} // namespace aapt diff --git a/tools/aapt2/Logger.h b/tools/aapt2/Logger.h deleted file mode 100644 index eed58b83ffc4..000000000000 --- a/tools/aapt2/Logger.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AAPT_LOGGER_H -#define AAPT_LOGGER_H - -#include "Source.h" -#include "StringPiece.h" - -#include <memory> -#include <ostream> -#include <string> - -namespace aapt { - -struct Log { - Log(std::ostream& out, std::ostream& err); - Log(const Log& rhs) = delete; - - std::ostream& out; - std::ostream& err; -}; - -class Logger { -public: - static void setLog(const std::shared_ptr<Log>& log); - - static std::ostream& error(); - static std::ostream& error(const Source& source); - static std::ostream& error(const SourceLine& sourceLine); - - static std::ostream& warn(); - static std::ostream& warn(const Source& source); - static std::ostream& warn(const SourceLine& sourceLine); - - static std::ostream& note(); - static std::ostream& note(const Source& source); - static std::ostream& note(const SourceLine& sourceLine); - -private: - static std::shared_ptr<Log> sLog; -}; - -class SourceLogger { -public: - SourceLogger(const Source& source); - - std::ostream& error(); - std::ostream& error(size_t line); - - std::ostream& warn(); - std::ostream& warn(size_t line); - - std::ostream& note(); - std::ostream& note(size_t line); - -private: - Source mSource; -}; - -} // namespace aapt - -#endif // AAPT_LOGGER_H diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 54a7329359f1..248e7ad73a82 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -14,1262 +14,39 @@ * limitations under the License. */ -#include "AppInfo.h" -#include "BigBuffer.h" -#include "BinaryResourceParser.h" -#include "BindingXmlPullParser.h" -#include "Debug.h" -#include "Files.h" -#include "Flag.h" -#include "JavaClassGenerator.h" -#include "Linker.h" -#include "ManifestMerger.h" -#include "ManifestParser.h" -#include "ManifestValidator.h" -#include "NameMangler.h" -#include "Png.h" -#include "ProguardRules.h" -#include "ResourceParser.h" -#include "ResourceTable.h" -#include "ResourceTableResolver.h" -#include "ResourceValues.h" -#include "SdkConstants.h" -#include "SourceXmlPullParser.h" -#include "StringPiece.h" -#include "TableFlattener.h" -#include "Util.h" -#include "XmlFlattener.h" -#include "ZipFile.h" +#include "util/StringPiece.h" -#include <algorithm> -#include <androidfw/AssetManager.h> -#include <cstdlib> -#include <dirent.h> -#include <errno.h> -#include <fstream> #include <iostream> -#include <sstream> -#include <sys/stat.h> -#include <unordered_set> -#include <utils/Errors.h> +#include <vector> -constexpr const char* kAaptVersionStr = "2.0-alpha"; +namespace aapt { -using namespace aapt; +extern int compile(const std::vector<StringPiece>& args); +extern int link(const std::vector<StringPiece>& args); -/** - * Used with smart pointers to free malloc'ed memory. - */ -struct DeleteMalloc { - void operator()(void* ptr) { - free(ptr); - } -}; - -struct StaticLibraryData { - Source source; - std::unique_ptr<ZipFile> apk; -}; - -/** - * Collect files from 'root', filtering out any files that do not - * match the FileFilter 'filter'. - */ -bool walkTree(const Source& root, const FileFilter& filter, - std::vector<Source>* outEntries) { - bool error = false; - - for (const std::string& dirName : listFiles(root.path)) { - std::string dir = root.path; - appendPath(&dir, dirName); - - FileType ft = getFileType(dir); - if (!filter(dirName, ft)) { - continue; - } - - if (ft != FileType::kDirectory) { - continue; - } - - for (const std::string& fileName : listFiles(dir)) { - std::string file(dir); - appendPath(&file, fileName); - - FileType ft = getFileType(file); - if (!filter(fileName, ft)) { - continue; - } - - if (ft != FileType::kRegular) { - Logger::error(Source{ file }) << "not a regular file." << std::endl; - error = true; - continue; - } - outEntries->push_back(Source{ file }); - } - } - return !error; -} - -void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) { - for (auto& type : *table) { - if (type->type != ResourceType::kStyle) { - continue; - } - - for (auto& entry : type->entries) { - // Add the versioned styles we want to create - // here. They are added to the table after - // iterating over the original set of styles. - // - // A stack is used since auto-generated styles - // from later versions should override - // auto-generated styles from earlier versions. - // Iterating over the styles is done in order, - // so we will always visit sdkVersions from smallest - // to largest. - std::stack<ResourceConfigValue> addStack; - - for (ResourceConfigValue& configValue : entry->values) { - visitFunc<Style>(*configValue.value, [&](Style& style) { - // Collect which entries we've stripped and the smallest - // SDK level which was stripped. - size_t minSdkStripped = std::numeric_limits<size_t>::max(); - std::vector<Style::Entry> stripped; - - // Iterate over the style's entries and erase/record the - // attributes whose SDK level exceeds the config's sdkVersion. - auto iter = style.entries.begin(); - while (iter != style.entries.end()) { - if (iter->key.name.package == u"android") { - size_t sdkLevel = findAttributeSdkLevel(iter->key.name); - if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) { - // Record that we are about to strip this. - stripped.emplace_back(std::move(*iter)); - minSdkStripped = std::min(minSdkStripped, sdkLevel); - - // Erase this from this style. - iter = style.entries.erase(iter); - continue; - } - } - ++iter; - } - - if (!stripped.empty()) { - // We have stripped attributes, so let's create a new style to hold them. - ConfigDescription versionConfig(configValue.config); - versionConfig.sdkVersion = minSdkStripped; - - ResourceConfigValue value = { - versionConfig, - configValue.source, - {}, - - // Create a copy of the original style. - std::unique_ptr<Value>(configValue.value->clone( - &table->getValueStringPool())) - }; - - Style& newStyle = static_cast<Style&>(*value.value); - - // Move the recorded stripped attributes into this new style. - std::move(stripped.begin(), stripped.end(), - std::back_inserter(newStyle.entries)); - - // We will add this style to the table later. If we do it now, we will - // mess up iteration. - addStack.push(std::move(value)); - } - }); - } - - auto comparator = - [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool { - return lhs.config < rhs; - }; - - while (!addStack.empty()) { - ResourceConfigValue& value = addStack.top(); - auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), - value.config, comparator); - if (iter == entry->values.end() || iter->config != value.config) { - entry->values.insert(iter, std::move(value)); - } - addStack.pop(); - } - } - } -} - -struct CompileItem { - ResourceName name; - ConfigDescription config; - Source source; - std::string extension; -}; - -struct LinkItem { - ResourceName name; - ConfigDescription config; - Source source; - std::string originalPath; - ZipFile* apk; - std::u16string originalPackage; -}; - -template <typename TChar> -static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) { - auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.')); - if (iter == str.end()) { - return BasicStringPiece<TChar>(); - } - size_t offset = (iter - str.begin()) + 1; - return str.substr(offset, str.size() - offset); -} - -std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config, - const StringPiece& extension) { - std::stringstream path; - path << "res/" << name.type; - if (config != ConfigDescription{}) { - path << "-" << config; - } - path << "/" << util::utf16ToUtf8(name.entry); - if (!extension.empty()) { - path << "." << extension; - } - return path.str(); -} - -std::string buildFileReference(const CompileItem& item) { - return buildFileReference(item.name, item.config, item.extension); -} - -std::string buildFileReference(const LinkItem& item) { - return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath)); -} - -template <typename T> -bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) { - StringPool& pool = table->getValueStringPool(); - StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)), - StringPool::Context{ 0, item.config }); - return table->addResource(item.name, item.config, item.source.line(0), - util::make_unique<FileReference>(ref)); -} - -struct AaptOptions { - enum class Phase { - Link, - Compile, - Dump, - DumpStyleGraph, - }; - - enum class PackageType { - StandardApp, - StaticLibrary, - }; - - // The phase to process. - Phase phase; - - // The type of package to produce. - PackageType packageType = PackageType::StandardApp; - - // Details about the app. - AppInfo appInfo; - - // The location of the manifest file. - Source manifest; - - // The APK files to link. - std::vector<Source> input; - - // The libraries these files may reference. - std::vector<Source> libraries; - - // Output path. This can be a directory or file - // depending on the phase. - Source output; - - // Directory in which to write binding xml files. - Source bindingOutput; - - // Directory to in which to generate R.java. - Maybe<Source> generateJavaClass; - - // File in which to produce proguard rules. - Maybe<Source> generateProguardRules; - - // Whether to output verbose details about - // compilation. - bool verbose = false; - - // Whether or not to auto-version styles or layouts - // referencing attributes defined in a newer SDK - // level than the style or layout is defined for. - bool versionStylesAndLayouts = true; - - // The target style that will have it's style hierarchy dumped - // when the phase is DumpStyleGraph. - ResourceName dumpStyleTarget; -}; - -struct IdCollector : public xml::Visitor { - IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) : - mSource(source), mTable(table) { - } - - virtual void visit(xml::Text* node) override {} - - virtual void visit(xml::Namespace* node) override { - for (const auto& child : node->children) { - child->accept(this); - } - } - - virtual void visit(xml::Element* node) override { - for (const xml::Attribute& attr : node->attributes) { - bool create = false; - bool priv = false; - ResourceNameRef nameRef; - if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) { - if (create) { - mTable->addResource(nameRef, {}, mSource.line(node->lineNumber), - util::make_unique<Id>()); - } - } - } - - for (const auto& child : node->children) { - child->accept(this); - } - } - -private: - Source mSource; - std::shared_ptr<ResourceTable> mTable; -}; - -bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, - const CompileItem& item, ZipFile* outApk) { - std::ifstream in(item.source.path, std::ifstream::binary); - if (!in) { - Logger::error(item.source) << strerror(errno) << std::endl; - return false; - } - - SourceLogger logger(item.source); - std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); - if (!root) { - return false; - } - - // Collect any resource ID's declared here. - IdCollector idCollector(item.source, table); - root->accept(&idCollector); - - BigBuffer outBuffer(1024); - if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) { - logger.error() << "failed to encode XML." << std::endl; - return false; - } - - // Write the resulting compiled XML file to the output APK. - if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored, - nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to write compiled '" << item.source - << "' to apk." << std::endl; - return false; - } - return true; -} - -/** - * Determines if a layout should be auto generated based on SDK level. We do not - * generate a layout if there is already a layout defined whose SDK version is greater than - * the one we want to generate. - */ -bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table, - const ResourceName& name, const ConfigDescription& config, - int sdkVersionToGenerate) { - assert(sdkVersionToGenerate > config.sdkVersion); - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = table->findResource(name); - assert(type && entry); - - auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config, - [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool { - return lhs.config < config; - }); - - assert(iter != entry->values.end()); - ++iter; - - if (iter == entry->values.end()) { - return true; - } - - ConfigDescription newConfig = config; - newConfig.sdkVersion = sdkVersionToGenerate; - return newConfig < iter->config; -} - -bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver, const LinkItem& item, - const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue, - proguard::KeepSet* keepSet) { - SourceLogger logger(item.source); - std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger); - if (!root) { - return false; - } - - xml::FlattenOptions xmlOptions; - if (options.packageType == AaptOptions::PackageType::StaticLibrary) { - xmlOptions.keepRawValues = true; - } - - if (options.versionStylesAndLayouts) { - // We strip attributes that do not belong in this version of the resource. - // Non-version qualified resources have an implicit version 1 requirement. - xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1; - } - - if (options.generateProguardRules) { - proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet); - } - - BigBuffer outBuffer(1024); - Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(), - item.originalPackage, resolver, - xmlOptions, &outBuffer); - if (!minStrippedSdk) { - logger.error() << "failed to encode XML." << std::endl; - return false; - } - - if (minStrippedSdk.value() > 0) { - // Something was stripped, so let's generate a new file - // with the version of the smallest SDK version stripped. - // We can only generate a versioned layout if there doesn't exist a layout - // with sdk version greater than the current one but less than the one we - // want to generate. - if (shouldGenerateVersionedResource(table, item.name, item.config, - minStrippedSdk.value())) { - LinkItem newWork = item; - newWork.config.sdkVersion = minStrippedSdk.value(); - outQueue->push(newWork); - - if (!addFileReference(table, newWork)) { - Logger::error(options.output) << "failed to add auto-versioned resource '" - << newWork.name << "'." << std::endl; - return false; - } - } - } - - if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated, - nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to write linked file '" - << buildFileReference(item) << "' to apk." << std::endl; - return false; - } - return true; -} - -bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) { - std::ifstream in(item.source.path, std::ifstream::binary); - if (!in) { - Logger::error(item.source) << strerror(errno) << std::endl; - return false; - } - - BigBuffer outBuffer(4096); - std::string err; - Png png; - if (!png.process(item.source, in, &outBuffer, {}, &err)) { - Logger::error(item.source) << err << std::endl; - return false; - } - - if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored, - nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to write compiled '" << item.source - << "' to apk." << std::endl; - return false; - } - return true; -} - -bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) { - if (outApk->add(item.source.path.data(), buildFileReference(item).data(), - ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk." - << std::endl; - return false; - } - return true; -} - -bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver, - const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks, - const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) { - if (options.verbose) { - Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl; - } - - std::ifstream in(options.manifest.path, std::ifstream::binary); - if (!in) { - Logger::error(options.manifest) << strerror(errno) << std::endl; - return false; - } - - SourceLogger logger(options.manifest); - std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); - if (!root) { - return false; - } - - ManifestMerger merger({}); - if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) { - return false; - } - - for (const auto& entry : libApks) { - ZipFile* libApk = entry.second.apk.get(); - const std::u16string& libPackage = entry.first->getPackage(); - const Source& libSource = entry.second.source; - - ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml"); - if (!zipEntry) { - continue; - } - - std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>( - libApk->uncompress(zipEntry)); - assert(uncompressedData); - - SourceLogger logger(libSource); - std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(), - zipEntry->getUncompressedLen(), &logger); - if (!libRoot) { - return false; - } - - if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) { - return false; - } - } - - if (options.generateProguardRules) { - proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(), - keepSet); - } - - BigBuffer outBuffer(1024); - if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package, - resolver, {}, &outBuffer)) { - return false; - } - - std::unique_ptr<uint8_t[]> data = util::copy(outBuffer); - - android::ResXMLTree tree; - if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) { - return false; - } - - ManifestValidator validator(table); - if (!validator.validate(options.manifest, &tree)) { - return false; - } - - if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml", - ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk." - << std::endl; - return false; - } - return true; -} - -static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source, - const ConfigDescription& config) { - std::ifstream in(source.path, std::ifstream::binary); - if (!in) { - Logger::error(source) << strerror(errno) << std::endl; - return false; - } - - std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in); - ResourceParser parser(table, source, config, xmlParser); - return parser.parse(); -} - -struct ResourcePathData { - std::u16string resourceDir; - std::u16string name; - std::string extension; - ConfigDescription config; -}; - -/** - * Resource file paths are expected to look like: - * [--/res/]type[-config]/name - */ -static Maybe<ResourcePathData> extractResourcePathData(const Source& source) { - // TODO(adamlesinski): Use Windows path separator on windows. - std::vector<std::string> parts = util::splitAndLowercase(source.path, '/'); - if (parts.size() < 2) { - Logger::error(source) << "bad resource path." << std::endl; - return {}; - } - - std::string& dir = parts[parts.size() - 2]; - StringPiece dirStr = dir; - - ConfigDescription config; - size_t dashPos = dir.find('-'); - if (dashPos != std::string::npos) { - StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1)); - if (!ConfigDescription::parse(configStr, &config)) { - Logger::error(source) - << "invalid configuration '" - << configStr - << "'." - << std::endl; - return {}; - } - dirStr = dirStr.substr(0, dashPos); - } - - std::string& filename = parts[parts.size() - 1]; - StringPiece name = filename; - StringPiece extension; - size_t dotPos = filename.find('.'); - if (dotPos != std::string::npos) { - extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1)); - name = name.substr(0, dotPos); - } - - return ResourcePathData{ - util::utf8ToUtf16(dirStr), - util::utf8ToUtf16(name), - extension.toString(), - config - }; -} - -bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, - const TableFlattener::Options& flattenerOptions, ZipFile* outApk) { - if (table->begin() != table->end()) { - BigBuffer buffer(1024); - TableFlattener flattener(flattenerOptions); - if (!flattener.flatten(&buffer, *table)) { - Logger::error() << "failed to flatten resource table." << std::endl; - return false; - } - - if (options.verbose) { - Logger::note() << "Final resource table size=" << util::formatSize(buffer.size()) - << std::endl; - } - - if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) != - android::NO_ERROR) { - Logger::note(options.output) << "failed to store resource table." << std::endl; - return false; - } - } - return true; -} - -/** - * For each FileReference in the table, adds a LinkItem to the link queue for processing. - */ -static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source, - const std::shared_ptr<ResourceTable>& table, - const std::unique_ptr<ZipFile>& apk, - std::queue<LinkItem>* outLinkQueue) { - bool mangle = package != table->getPackage(); - for (auto& type : *table) { - for (auto& entry : type->entries) { - ResourceName name = { package, type->type, entry->name }; - if (mangle) { - NameMangler::mangle(table->getPackage(), &name.entry); - } - - for (auto& value : entry->values) { - visitFunc<FileReference>(*value.value, [&](FileReference& ref) { - std::string pathUtf8 = util::utf16ToUtf8(*ref.path); - Source newSource = source; - newSource.path += "/"; - newSource.path += pathUtf8; - outLinkQueue->push(LinkItem{ - name, value.config, newSource, pathUtf8, apk.get(), - table->getPackage() }); - // Now rewrite the file path. - if (mangle) { - ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16( - buildFileReference(name, value.config, - getExtension<char>(pathUtf8)))); - } - }); - } - } - } -} - -static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate | - ZipFile::kOpenReadWrite; - -bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable, - const std::shared_ptr<IResolver>& resolver) { - std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles; - std::unordered_set<std::u16string> linkedPackages; - - // Populate the linkedPackages with our own. - linkedPackages.insert(options.appInfo.package); - - // Load all APK files. - for (const Source& source : options.input) { - std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>(); - if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) { - Logger::error(source) << "failed to open: " << strerror(errno) << std::endl; - return false; - } - - std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); - - ZipEntry* entry = zipFile->getEntryByName("resources.arsc"); - if (!entry) { - Logger::error(source) << "missing 'resources.arsc'." << std::endl; - return false; - } - - std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>( - zipFile->uncompress(entry)); - assert(uncompressedData); - - BinaryResourceParser parser(table, resolver, source, options.appInfo.package, - uncompressedData.get(), entry->getUncompressedLen()); - if (!parser.parse()) { - return false; - } - - // Keep track of where this table came from. - apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) }; - - // Add the package to the set of linked packages. - linkedPackages.insert(table->getPackage()); - } - - std::queue<LinkItem> linkQueue; - for (auto& p : apkFiles) { - const std::shared_ptr<ResourceTable>& inTable = p.first; - - // Collect all FileReferences and add them to the queue for processing. - addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk, - &linkQueue); - - // Merge the tables. - if (!outTable->merge(std::move(*inTable))) { - return false; - } - } - - // Version all styles referencing attributes outside of their specified SDK version. - if (options.versionStylesAndLayouts) { - versionStylesForCompat(outTable); - } - - { - // Now that everything is merged, let's link it. - Linker::Options linkerOptions; - if (options.packageType == AaptOptions::PackageType::StaticLibrary) { - linkerOptions.linkResourceIds = false; - } - Linker linker(outTable, resolver, linkerOptions); - if (!linker.linkAndValidate()) { - return false; - } - - // Verify that all symbols exist. - const auto& unresolvedRefs = linker.getUnresolvedReferences(); - if (!unresolvedRefs.empty()) { - for (const auto& entry : unresolvedRefs) { - for (const auto& source : entry.second) { - Logger::error(source) << "unresolved symbol '" << entry.first << "'." - << std::endl; - } - } - return false; - } - } - - // Open the output APK file for writing. - ZipFile outApk; - if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) { - Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl; - return false; - } - - proguard::KeepSet keepSet; - - android::ResTable binTable; - if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) { - return false; - } - - for (; !linkQueue.empty(); linkQueue.pop()) { - const LinkItem& item = linkQueue.front(); - - assert(!item.originalPackage.empty()); - ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data()); - if (!entry) { - Logger::error(item.source) << "failed to find '" << item.originalPath << "'." - << std::endl; - return false; - } - - if (util::stringEndsWith<char>(item.originalPath, ".xml")) { - void* uncompressedData = item.apk->uncompress(entry); - assert(uncompressedData); - - if (!linkXml(options, outTable, resolver, item, uncompressedData, - entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) { - Logger::error(options.output) << "failed to link '" << item.originalPath << "'." - << std::endl; - return false; - } - } else { - if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) != - android::NO_ERROR) { - Logger::error(options.output) << "failed to copy '" << item.originalPath << "'." - << std::endl; - return false; - } - } - } - - // Generate the Java class file. - if (options.generateJavaClass) { - JavaClassGenerator::Options javaOptions; - if (options.packageType == AaptOptions::PackageType::StaticLibrary) { - javaOptions.useFinal = false; - } - JavaClassGenerator generator(outTable, javaOptions); - - for (const std::u16string& package : linkedPackages) { - Source outPath = options.generateJavaClass.value(); - - // Build the output directory from the package name. - // Eg. com.android.app -> com/android/app - const std::string packageUtf8 = util::utf16ToUtf8(package); - for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) { - appendPath(&outPath.path, part); - } - - if (!mkdirs(outPath.path)) { - Logger::error(outPath) << strerror(errno) << std::endl; - return false; - } - - appendPath(&outPath.path, "R.java"); - - if (options.verbose) { - Logger::note(outPath) << "writing Java symbols." << std::endl; - } - - std::ofstream fout(outPath.path); - if (!fout) { - Logger::error(outPath) << strerror(errno) << std::endl; - return false; - } - - if (!generator.generate(package, fout)) { - Logger::error(outPath) << generator.getError() << "." << std::endl; - return false; - } - } - } - - // Generate the Proguard rules file. - if (options.generateProguardRules) { - const Source& outPath = options.generateProguardRules.value(); - - if (options.verbose) { - Logger::note(outPath) << "writing proguard rules." << std::endl; - } - - std::ofstream fout(outPath.path); - if (!fout) { - Logger::error(outPath) << strerror(errno) << std::endl; - return false; - } - - if (!proguard::writeKeepSet(&fout, keepSet)) { - Logger::error(outPath) << "failed to write proguard rules." << std::endl; - return false; - } - } - - outTable->getValueStringPool().prune(); - outTable->getValueStringPool().sort( - [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - if (a.context.priority < b.context.priority) { - return true; - } - - if (a.context.priority > b.context.priority) { - return false; - } - return a.value < b.value; - }); - - - // Flatten the resource table. - TableFlattener::Options flattenerOptions; - if (options.packageType != AaptOptions::PackageType::StaticLibrary) { - flattenerOptions.useExtendedChunks = false; - } - - if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) { - return false; - } - - outApk.flush(); - return true; -} - -bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver) { - std::queue<CompileItem> compileQueue; - bool error = false; - - // Compile all the resource files passed in on the command line. - for (const Source& source : options.input) { - // Need to parse the resource type/config/filename. - Maybe<ResourcePathData> maybePathData = extractResourcePathData(source); - if (!maybePathData) { - return false; - } - - const ResourcePathData& pathData = maybePathData.value(); - if (pathData.resourceDir == u"values") { - // The file is in the values directory, which means its contents will - // go into the resource table. - if (options.verbose) { - Logger::note(source) << "compiling values." << std::endl; - } - - error |= !compileValues(table, source, pathData.config); - } else { - // The file is in a directory like 'layout' or 'drawable'. Find out - // the type. - const ResourceType* type = parseResourceType(pathData.resourceDir); - if (!type) { - Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'." - << std::endl; - return false; - } - - compileQueue.push(CompileItem{ - ResourceName{ table->getPackage(), *type, pathData.name }, - pathData.config, - source, - pathData.extension - }); - } - } - - if (error) { - return false; - } - // Open the output APK file for writing. - ZipFile outApk; - if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) { - Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl; - return false; - } - - // Compile each file. - for (; !compileQueue.empty(); compileQueue.pop()) { - const CompileItem& item = compileQueue.front(); - - // Add the file name to the resource table. - error |= !addFileReference(table, item); - - if (item.extension == "xml") { - error |= !compileXml(options, table, item, &outApk); - } else if (item.extension == "png" || item.extension == "9.png") { - error |= !compilePng(options, item, &outApk); - } else { - error |= !copyFile(options, item, &outApk); - } - } - - if (error) { - return false; - } - - // Link and assign resource IDs. - Linker linker(table, resolver, {}); - if (!linker.linkAndValidate()) { - return false; - } - - // Flatten the resource table. - if (!writeResourceTable(options, table, {}, &outApk)) { - return false; - } - - outApk.flush(); - return true; -} - -bool loadAppInfo(const Source& source, AppInfo* outInfo) { - std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary); - if (!ifs) { - Logger::error(source) << strerror(errno) << std::endl; - return false; - } - - ManifestParser parser; - std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs); - return parser.parse(source, pullParser, outInfo); -} - -static void printCommandsAndDie() { - std::cerr << "The following commands are supported:" << std::endl << std::endl; - std::cerr << "compile compiles a subset of resources" << std::endl; - std::cerr << "link links together compiled resources and libraries" << std::endl; - std::cerr << "dump dumps resource contents to to standard out" << std::endl; - std::cerr << std::endl; - std::cerr << "run aapt2 with one of the commands and the -h flag for extra details." - << std::endl; - exit(1); -} - -static AaptOptions prepareArgs(int argc, char** argv) { - if (argc < 2) { - std::cerr << "no command specified." << std::endl << std::endl; - printCommandsAndDie(); - } - - const StringPiece command(argv[1]); - argc -= 2; - argv += 2; - - AaptOptions options; - - if (command == "--version" || command == "version") { - std::cout << kAaptVersionStr << std::endl; - exit(0); - } else if (command == "link") { - options.phase = AaptOptions::Phase::Link; - } else if (command == "compile") { - options.phase = AaptOptions::Phase::Compile; - } else if (command == "dump") { - options.phase = AaptOptions::Phase::Dump; - } else if (command == "dump-style-graph") { - options.phase = AaptOptions::Phase::DumpStyleGraph; - } else { - std::cerr << "invalid command '" << command << "'." << std::endl << std::endl; - printCommandsAndDie(); - } - - bool isStaticLib = false; - if (options.phase == AaptOptions::Phase::Link) { - flag::requiredFlag("--manifest", "AndroidManifest.xml of your app", - [&options](const StringPiece& arg) { - options.manifest = Source{ arg.toString() }; - }); - - flag::optionalFlag("-I", "add an Android APK to link against", - [&options](const StringPiece& arg) { - options.libraries.push_back(Source{ arg.toString() }); - }); - - flag::optionalFlag("--java", "directory in which to generate R.java", - [&options](const StringPiece& arg) { - options.generateJavaClass = Source{ arg.toString() }; - }); - - flag::optionalFlag("--proguard", "file in which to output proguard rules", - [&options](const StringPiece& arg) { - options.generateProguardRules = Source{ arg.toString() }; - }); - - flag::optionalSwitch("--static-lib", "generate a static Android library", true, - &isStaticLib); - - flag::optionalFlag("--binding", "Output directory for binding XML files", - [&options](const StringPiece& arg) { - options.bindingOutput = Source{ arg.toString() }; - }); - flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning", - false, &options.versionStylesAndLayouts); - } - - if (options.phase == AaptOptions::Phase::Compile || - options.phase == AaptOptions::Phase::Link) { - // Common flags for all steps. - flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) { - options.output = Source{ arg.toString() }; - }); - } - - if (options.phase == AaptOptions::Phase::DumpStyleGraph) { - flag::requiredFlag("--style", "Name of the style to dump", - [&options](const StringPiece& arg, std::string* outError) -> bool { - Reference styleReference; - if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg), - &styleReference, outError)) { - return false; - } - options.dumpStyleTarget = styleReference.name; - return true; - }); - } - - bool help = false; - flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose); - flag::optionalSwitch("-h", "displays this help menu", true, &help); - - // Build the command string for output (eg. "aapt2 compile"). - std::string fullCommand = "aapt2"; - fullCommand += " "; - fullCommand += command.toString(); - - // Actually read the command line flags. - flag::parse(argc, argv, fullCommand); - - if (help) { - flag::usageAndDie(fullCommand); - } - - if (isStaticLib) { - options.packageType = AaptOptions::PackageType::StaticLibrary; - } - - // Copy all the remaining arguments. - for (const std::string& arg : flag::getArgs()) { - options.input.push_back(Source{ arg }); - } - return options; -} - -static bool doDump(const AaptOptions& options) { - for (const Source& source : options.input) { - std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>(); - if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) { - Logger::error(source) << "failed to open: " << strerror(errno) << std::endl; - return false; - } - - std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); - std::shared_ptr<ResourceTableResolver> resolver = - std::make_shared<ResourceTableResolver>( - table, std::vector<std::shared_ptr<const android::AssetManager>>()); - - ZipEntry* entry = zipFile->getEntryByName("resources.arsc"); - if (!entry) { - Logger::error(source) << "missing 'resources.arsc'." << std::endl; - return false; - } - - std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>( - zipFile->uncompress(entry)); - assert(uncompressedData); - - BinaryResourceParser parser(table, resolver, source, {}, uncompressedData.get(), - entry->getUncompressedLen()); - if (!parser.parse()) { - return false; - } - - if (options.phase == AaptOptions::Phase::Dump) { - Debug::printTable(table); - } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) { - Debug::printStyleGraph(table, options.dumpStyleTarget); - } - } - return true; -} +} // namespace aapt int main(int argc, char** argv) { - Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr)); - AaptOptions options = prepareArgs(argc, argv); + if (argc >= 2) { + argv += 1; + argc -= 1; - if (options.phase == AaptOptions::Phase::Dump || - options.phase == AaptOptions::Phase::DumpStyleGraph) { - if (!doDump(options)) { - return 1; - } - return 0; - } - - // If we specified a manifest, go ahead and load the package name from the manifest. - if (!options.manifest.path.empty()) { - if (!loadAppInfo(options.manifest, &options.appInfo)) { - return false; + std::vector<aapt::StringPiece> args; + for (int i = 1; i < argc; i++) { + args.push_back(argv[i]); } - if (options.appInfo.package.empty()) { - Logger::error() << "no package name specified." << std::endl; - return false; + aapt::StringPiece command(argv[0]); + if (command == "compile" || command == "c") { + return aapt::compile(args); + } else if (command == "link" || command == "l") { + return aapt::link(args); } - } - - // Every phase needs a resource table. - std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); - - // The package name is empty when in the compile phase. - table->setPackage(options.appInfo.package); - if (options.appInfo.package == u"android") { - table->setPackageId(0x01); + std::cerr << "unknown command '" << command << "'\n"; } else { - table->setPackageId(0x7f); - } - - // Load the included libraries. - std::vector<std::shared_ptr<const android::AssetManager>> sources; - for (const Source& source : options.libraries) { - std::shared_ptr<android::AssetManager> assetManager = - std::make_shared<android::AssetManager>(); - int32_t cookie; - if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) { - Logger::error(source) << "failed to load library." << std::endl; - return false; - } - - if (cookie == 0) { - Logger::error(source) << "failed to load library." << std::endl; - return false; - } - sources.push_back(assetManager); + std::cerr << "no command specified\n"; } - // Make the resolver that will cache IDs for us. - std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>( - table, sources); - - if (options.phase == AaptOptions::Phase::Compile) { - if (!compile(options, table, resolver)) { - Logger::error() << "aapt exiting with failures." << std::endl; - return 1; - } - } else if (options.phase == AaptOptions::Phase::Link) { - if (!link(options, table, resolver)) { - Logger::error() << "aapt exiting with failures." << std::endl; - return 1; - } - } - return 0; + std::cerr << "\nusage: aapt2 [compile|link] ..." << std::endl; + return 1; } diff --git a/tools/aapt2/ManifestMerger.cpp b/tools/aapt2/ManifestMerger.cpp deleted file mode 100644 index 71d3424c6ad8..000000000000 --- a/tools/aapt2/ManifestMerger.cpp +++ /dev/null @@ -1,376 +0,0 @@ -#include "ManifestMerger.h" -#include "Maybe.h" -#include "ResourceParser.h" -#include "Source.h" -#include "Util.h" -#include "XmlPullParser.h" - -#include <iostream> -#include <memory> -#include <set> -#include <string> - -namespace aapt { - -constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; - -static xml::Element* findManifest(xml::Node* root) { - if (!root) { - return nullptr; - } - - while (root->type == xml::NodeType::kNamespace) { - if (root->children.empty()) { - break; - } - root = root->children[0].get(); - } - - if (root && root->type == xml::NodeType::kElement) { - xml::Element* el = static_cast<xml::Element*>(root); - if (el->namespaceUri.empty() && el->name == u"manifest") { - return el; - } - } - return nullptr; -} - -static xml::Element* findChildWithSameName(xml::Element* parent, xml::Element* src) { - xml::Attribute* attrKey = src->findAttribute(kSchemaAndroid, u"name"); - if (!attrKey) { - return nullptr; - } - return parent->findChildWithAttribute(src->namespaceUri, src->name, attrKey); -} - -static bool attrLess(const xml::Attribute& lhs, const xml::Attribute& rhs) { - return std::tie(lhs.namespaceUri, lhs.name, lhs.value) - < std::tie(rhs.namespaceUri, rhs.name, rhs.value); -} - -static int compare(xml::Element* lhs, xml::Element* rhs) { - int diff = lhs->attributes.size() - rhs->attributes.size(); - if (diff != 0) { - return diff; - } - - std::set<xml::Attribute, decltype(&attrLess)> lhsAttrs(&attrLess); - lhsAttrs.insert(lhs->attributes.begin(), lhs->attributes.end()); - for (auto& attr : rhs->attributes) { - if (lhsAttrs.erase(attr) == 0) { - // The rhs attribute is not in the left. - return -1; - } - } - - if (!lhsAttrs.empty()) { - // The lhs has attributes not in the rhs. - return 1; - } - return 0; -} - -ManifestMerger::ManifestMerger(const Options& options) : - mOptions(options), mAppLogger({}), mLogger({}) { -} - -bool ManifestMerger::setAppManifest(const Source& source, const std::u16string& package, - std::unique_ptr<xml::Node> root) { - - mAppLogger = SourceLogger{ source }; - mRoot = std::move(root); - return true; -} - -bool ManifestMerger::checkEqual(xml::Element* elA, xml::Element* elB) { - if (compare(elA, elB) != 0) { - mLogger.error(elB->lineNumber) - << "library tag '" << elB->name << "' conflicts with app tag." - << std::endl; - mAppLogger.note(elA->lineNumber) - << "app tag '" << elA->name << "' defined here." - << std::endl; - return false; - } - - std::vector<xml::Element*> childrenA = elA->getChildElements(); - std::vector<xml::Element*> childrenB = elB->getChildElements(); - - if (childrenA.size() != childrenB.size()) { - mLogger.error(elB->lineNumber) - << "library tag '" << elB->name << "' children conflict with app tag." - << std::endl; - mAppLogger.note(elA->lineNumber) - << "app tag '" << elA->name << "' defined here." - << std::endl; - return false; - } - - auto cmp = [](xml::Element* lhs, xml::Element* rhs) -> bool { - return compare(lhs, rhs) < 0; - }; - - std::sort(childrenA.begin(), childrenA.end(), cmp); - std::sort(childrenB.begin(), childrenB.end(), cmp); - - for (size_t i = 0; i < childrenA.size(); i++) { - if (!checkEqual(childrenA[i], childrenB[i])) { - return false; - } - } - return true; -} - -bool ManifestMerger::mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB) { - if (!elA) { - parentA->addChild(elB->clone()); - return true; - } - return checkEqual(elA, elB); -} - -bool ManifestMerger::mergePreferRequired(xml::Element* parentA, xml::Element* elA, - xml::Element* elB) { - if (!elA) { - parentA->addChild(elB->clone()); - return true; - } - - xml::Attribute* reqA = elA->findAttribute(kSchemaAndroid, u"required"); - xml::Attribute* reqB = elB->findAttribute(kSchemaAndroid, u"required"); - bool requiredA = !reqA || (reqA->value != u"false" && reqA->value != u"FALSE"); - bool requiredB = !reqB || (reqB->value != u"false" && reqB->value != u"FALSE"); - if (!requiredA && requiredB) { - if (reqA) { - *reqA = xml::Attribute{ kSchemaAndroid, u"required", u"true" }; - } else { - elA->attributes.push_back(xml::Attribute{ kSchemaAndroid, u"required", u"true" }); - } - } - return true; -} - -static int findIntegerValue(xml::Attribute* attr, int defaultValue) { - if (attr) { - std::unique_ptr<BinaryPrimitive> integer = ResourceParser::tryParseInt(attr->value); - if (integer) { - return integer->value.data; - } - } - return defaultValue; -} - -bool ManifestMerger::mergeUsesSdk(xml::Element* elA, xml::Element* elB) { - bool error = false; - xml::Attribute* minAttrA = nullptr; - xml::Attribute* minAttrB = nullptr; - if (elA) { - minAttrA = elA->findAttribute(kSchemaAndroid, u"minSdkVersion"); - } - - if (elB) { - minAttrB = elB->findAttribute(kSchemaAndroid, u"minSdkVersion"); - } - - int minSdkA = findIntegerValue(minAttrA, 1); - int minSdkB = findIntegerValue(minAttrB, 1); - - if (minSdkA < minSdkB) { - std::ostream* out; - if (minAttrA) { - out = &(mAppLogger.error(elA->lineNumber) << "app declares "); - } else if (elA) { - out = &(mAppLogger.error(elA->lineNumber) << "app has implied "); - } else { - out = &(mAppLogger.error() << "app has implied "); - } - - *out << "minSdkVersion=" << minSdkA << " but library expects a higher SDK version." - << std::endl; - - // elB is valid because minSdkB wouldn't be greater than minSdkA if it wasn't. - mLogger.note(elB->lineNumber) - << "library declares minSdkVersion=" << minSdkB << "." - << std::endl; - error = true; - } - - xml::Attribute* targetAttrA = nullptr; - xml::Attribute* targetAttrB = nullptr; - - if (elA) { - targetAttrA = elA->findAttribute(kSchemaAndroid, u"targetSdkVersion"); - } - - if (elB) { - targetAttrB = elB->findAttribute(kSchemaAndroid, u"targetSdkVersion"); - } - - int targetSdkA = findIntegerValue(targetAttrA, minSdkA); - int targetSdkB = findIntegerValue(targetAttrB, minSdkB); - - if (targetSdkA < targetSdkB) { - std::ostream* out; - if (targetAttrA) { - out = &(mAppLogger.warn(elA->lineNumber) << "app declares "); - } else if (elA) { - out = &(mAppLogger.warn(elA->lineNumber) << "app has implied "); - } else { - out = &(mAppLogger.warn() << "app has implied "); - } - - *out << "targetSdkVerion=" << targetSdkA << " but library expects target SDK " - << targetSdkB << "." << std::endl; - - mLogger.note(elB->lineNumber) - << "library declares targetSdkVersion=" << targetSdkB << "." - << std::endl; - error = true; - } - return !error; -} - -bool ManifestMerger::mergeApplication(xml::Element* applicationA, xml::Element* applicationB) { - if (!applicationA || !applicationB) { - return true; - } - - bool error = false; - - // First make sure that the names are identical. - xml::Attribute* nameA = applicationA->findAttribute(kSchemaAndroid, u"name"); - xml::Attribute* nameB = applicationB->findAttribute(kSchemaAndroid, u"name"); - if (nameB) { - if (!nameA) { - applicationA->attributes.push_back(*nameB); - } else if (nameA->value != nameB->value) { - mLogger.error(applicationB->lineNumber) - << "conflicting application name '" - << nameB->value - << "'." << std::endl; - mAppLogger.note(applicationA->lineNumber) - << "application defines application name '" - << nameA->value - << "'." << std::endl; - error = true; - } - } - - // Now we descend into the activity/receiver/service/provider tags - for (xml::Element* elB : applicationB->getChildElements()) { - if (!elB->namespaceUri.empty()) { - continue; - } - - if (elB->name == u"activity" || elB->name == u"activity-alias" - || elB->name == u"service" || elB->name == u"receiver" - || elB->name == u"provider" || elB->name == u"meta-data") { - xml::Element* elA = findChildWithSameName(applicationA, elB); - error |= !mergeNewOrEqual(applicationA, elA, elB); - } else if (elB->name == u"uses-library") { - xml::Element* elA = findChildWithSameName(applicationA, elB); - error |= !mergePreferRequired(applicationA, elA, elB); - } - } - return !error; -} - -bool ManifestMerger::mergeLibraryManifest(const Source& source, const std::u16string& package, - std::unique_ptr<xml::Node> libRoot) { - mLogger = SourceLogger{ source }; - xml::Element* manifestA = findManifest(mRoot.get()); - xml::Element* manifestB = findManifest(libRoot.get()); - if (!manifestA) { - mAppLogger.error() << "missing manifest tag." << std::endl; - return false; - } - - if (!manifestB) { - mLogger.error() << "library missing manifest tag." << std::endl; - return false; - } - - bool error = false; - - // Do <application> first. - xml::Element* applicationA = manifestA->findChild({}, u"application"); - xml::Element* applicationB = manifestB->findChild({}, u"application"); - error |= !mergeApplication(applicationA, applicationB); - - // Do <uses-sdk> next. - xml::Element* usesSdkA = manifestA->findChild({}, u"uses-sdk"); - xml::Element* usesSdkB = manifestB->findChild({}, u"uses-sdk"); - error |= !mergeUsesSdk(usesSdkA, usesSdkB); - - for (xml::Element* elB : manifestB->getChildElements()) { - if (!elB->namespaceUri.empty()) { - continue; - } - - if (elB->name == u"uses-permission" || elB->name == u"permission" - || elB->name == u"permission-group" || elB->name == u"permission-tree") { - xml::Element* elA = findChildWithSameName(manifestA, elB); - error |= !mergeNewOrEqual(manifestA, elA, elB); - } else if (elB->name == u"uses-feature") { - xml::Element* elA = findChildWithSameName(manifestA, elB); - error |= !mergePreferRequired(manifestA, elA, elB); - } else if (elB->name == u"uses-configuration" || elB->name == u"supports-screen" - || elB->name == u"compatible-screens" || elB->name == u"supports-gl-texture") { - xml::Element* elA = findChildWithSameName(manifestA, elB); - error |= !checkEqual(elA, elB); - } - } - return !error; -} - -static void printMerged(xml::Node* node, int depth) { - std::string indent; - for (int i = 0; i < depth; i++) { - indent += " "; - } - - switch (node->type) { - case xml::NodeType::kNamespace: - std::cerr << indent << "N: " - << "xmlns:" << static_cast<xml::Namespace*>(node)->namespacePrefix - << "=\"" << static_cast<xml::Namespace*>(node)->namespaceUri - << "\"\n"; - break; - - case xml::NodeType::kElement: - std::cerr << indent << "E: " - << static_cast<xml::Element*>(node)->namespaceUri - << ":" << static_cast<xml::Element*>(node)->name - << "\n"; - for (const auto& attr : static_cast<xml::Element*>(node)->attributes) { - std::cerr << indent << " A: " - << attr.namespaceUri - << ":" << attr.name - << "=\"" << attr.value << "\"\n"; - } - break; - - case xml::NodeType::kText: - std::cerr << indent << "T: \"" << static_cast<xml::Text*>(node)->text << "\"\n"; - break; - } - - for (auto& child : node->children) { - printMerged(child.get(), depth + 1); - } -} - -xml::Node* ManifestMerger::getMergedXml() { - return mRoot.get(); -} - -bool ManifestMerger::printMerged() { - if (!mRoot) { - return false; - } - - ::aapt::printMerged(mRoot.get(), 0); - return true; -} - -} // namespace aapt diff --git a/tools/aapt2/ManifestMerger.h b/tools/aapt2/ManifestMerger.h deleted file mode 100644 index c6219dbba65e..000000000000 --- a/tools/aapt2/ManifestMerger.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef AAPT_MANIFEST_MERGER_H -#define AAPT_MANIFEST_MERGER_H - -#include "Logger.h" -#include "Source.h" -#include "XmlDom.h" - -#include <memory> -#include <string> - -namespace aapt { - -class ManifestMerger { -public: - struct Options { - }; - - ManifestMerger(const Options& options); - - bool setAppManifest(const Source& source, const std::u16string& package, - std::unique_ptr<xml::Node> root); - - bool mergeLibraryManifest(const Source& source, const std::u16string& package, - std::unique_ptr<xml::Node> libRoot); - - xml::Node* getMergedXml(); - - bool printMerged(); - -private: - bool mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB); - bool mergePreferRequired(xml::Element* parentA, xml::Element* elA, xml::Element* elB); - bool checkEqual(xml::Element* elA, xml::Element* elB); - bool mergeApplication(xml::Element* applicationA, xml::Element* applicationB); - bool mergeUsesSdk(xml::Element* elA, xml::Element* elB); - - Options mOptions; - std::unique_ptr<xml::Node> mRoot; - SourceLogger mAppLogger; - SourceLogger mLogger; -}; - -} // namespace aapt - -#endif // AAPT_MANIFEST_MERGER_H diff --git a/tools/aapt2/ManifestMerger_test.cpp b/tools/aapt2/ManifestMerger_test.cpp deleted file mode 100644 index 6838253dad20..000000000000 --- a/tools/aapt2/ManifestMerger_test.cpp +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ManifestMerger.h" -#include "SourceXmlPullParser.h" - -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -namespace aapt { - -constexpr const char* kAppManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android"> - <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" /> - <uses-permission android:name="android.permission.INTERNET"/> - <uses-feature android:name="android.hardware.GPS" android:required="false" /> - <application android:name="com.android.library.Application"> - <activity android:name="com.android.example.MainActivity"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.DEFAULT" /> - </intent-filter> - </activity> - <service android:name="com.android.library.Service"> - <intent-filter> - <action android:name="com.android.library.intent.action.SYNC" /> - </intent-filter> - </service> - </application> -</manifest> -)EOF"; - -constexpr const char* kLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android"> - <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21" /> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-feature android:name="android.hardware.GPS" /> - <uses-permission android:name="android.permission.GPS" /> - <application android:name="com.android.library.Application"> - <service android:name="com.android.library.Service"> - <intent-filter> - <action android:name="com.android.library.intent.action.SYNC" /> - </intent-filter> - </service> - <provider android:name="com.android.library.DocumentProvider" - android:authorities="com.android.library.documents" - android:grantUriPermission="true" - android:exported="true" - android:permission="android.permission.MANAGE_DOCUMENTS" - android:enabled="@bool/atLeastKitKat"> - <intent-filter> - <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> - </intent-filter> - </provider> - </application> -</manifest> -)EOF"; - -constexpr const char* kBadLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android"> - <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="22" /> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-feature android:name="android.hardware.GPS" /> - <uses-permission android:name="android.permission.GPS" /> - <application android:name="com.android.library.Application2"> - <service android:name="com.android.library.Service"> - <intent-filter> - <action android:name="com.android.library.intent.action.SYNC_ACTION" /> - </intent-filter> - </service> - </application> -</manifest> -)EOF"; - -TEST(ManifestMergerTest, MergeManifestsSuccess) { - std::stringstream inA(kAppManifest); - std::stringstream inB(kLibManifest); - - const Source sourceA = { "AndroidManifest.xml" }; - const Source sourceB = { "lib.apk/AndroidManifest.xml" }; - SourceLogger loggerA(sourceA); - SourceLogger loggerB(sourceB); - - ManifestMerger merger({}); - EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example", - xml::inflate(&inA, &loggerA))); - EXPECT_TRUE(merger.mergeLibraryManifest(sourceB, u"com.android.library", - xml::inflate(&inB, &loggerB))); -} - -TEST(ManifestMergerTest, MergeManifestFail) { - std::stringstream inA(kAppManifest); - std::stringstream inB(kBadLibManifest); - - const Source sourceA = { "AndroidManifest.xml" }; - const Source sourceB = { "lib.apk/AndroidManifest.xml" }; - SourceLogger loggerA(sourceA); - SourceLogger loggerB(sourceB); - - ManifestMerger merger({}); - EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example", - xml::inflate(&inA, &loggerA))); - EXPECT_FALSE(merger.mergeLibraryManifest(sourceB, u"com.android.library", - xml::inflate(&inB, &loggerB))); -} - -} // namespace aapt diff --git a/tools/aapt2/ManifestParser.cpp b/tools/aapt2/ManifestParser.cpp deleted file mode 100644 index b8f0a430bcee..000000000000 --- a/tools/aapt2/ManifestParser.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "AppInfo.h" -#include "Logger.h" -#include "ManifestParser.h" -#include "Source.h" -#include "XmlPullParser.h" - -#include <string> - -namespace aapt { - -bool ManifestParser::parse(const Source& source, std::shared_ptr<XmlPullParser> parser, - AppInfo* outInfo) { - SourceLogger logger = { source }; - - int depth = 0; - while (XmlPullParser::isGoodEvent(parser->next())) { - XmlPullParser::Event event = parser->getEvent(); - if (event == XmlPullParser::Event::kEndElement) { - depth--; - continue; - } else if (event != XmlPullParser::Event::kStartElement) { - continue; - } - - depth++; - - const std::u16string& element = parser->getElementName(); - if (depth == 1) { - if (element == u"manifest") { - if (!parseManifest(logger, parser, outInfo)) { - return false; - } - } else { - logger.error() - << "unexpected top-level element '" - << element - << "'." - << std::endl; - return false; - } - } else { - XmlPullParser::skipCurrentElement(parser.get()); - } - } - - if (parser->getEvent() == XmlPullParser::Event::kBadDocument) { - logger.error(parser->getLineNumber()) - << "failed to parse manifest: " - << parser->getLastError() - << "." - << std::endl; - return false; - } - return true; -} - -bool ManifestParser::parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser, - AppInfo* outInfo) { - auto attrIter = parser->findAttribute(u"", u"package"); - if (attrIter == parser->endAttributes() || attrIter->value.empty()) { - logger.error() << "no 'package' attribute found for element <manifest>." << std::endl; - return false; - } - outInfo->package = attrIter->value; - return true; -} - -} // namespace aapt diff --git a/tools/aapt2/ManifestParser_test.cpp b/tools/aapt2/ManifestParser_test.cpp deleted file mode 100644 index be3a6fbe614a..000000000000 --- a/tools/aapt2/ManifestParser_test.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "AppInfo.h" -#include "ManifestParser.h" -#include "SourceXmlPullParser.h" - -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -namespace aapt { - -TEST(ManifestParserTest, FindPackage) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" - "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" - "package=\"android\">\n" - "</manifest>\n"; - - ManifestParser parser; - AppInfo info; - std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input); - ASSERT_TRUE(parser.parse(Source{ "AndroidManifest.xml" }, xmlParser, &info)); - - EXPECT_EQ(std::u16string(u"android"), info.package); -} - -} // namespace aapt diff --git a/tools/aapt2/ManifestValidator.cpp b/tools/aapt2/ManifestValidator.cpp index 123b9fae2fba..9f971fbcf728 100644 --- a/tools/aapt2/ManifestValidator.cpp +++ b/tools/aapt2/ManifestValidator.cpp @@ -16,9 +16,9 @@ #include "Logger.h" #include "ManifestValidator.h" -#include "Maybe.h" +#include "util/Maybe.h" #include "Source.h" -#include "Util.h" +#include "util/Util.h" #include <androidfw/ResourceTypes.h> diff --git a/tools/aapt2/ManifestValidator.h b/tools/aapt2/ManifestValidator.h index 318878499cfa..1a7f48e9b5ac 100644 --- a/tools/aapt2/ManifestValidator.h +++ b/tools/aapt2/ManifestValidator.h @@ -18,9 +18,9 @@ #define AAPT_MANIFEST_VALIDATOR_H #include "Logger.h" -#include "Maybe.h" +#include "util/Maybe.h" #include "Source.h" -#include "StringPiece.h" +#include "util/StringPiece.h" #include <androidfw/ResourceTypes.h> diff --git a/tools/aapt2/MockResolver.h b/tools/aapt2/MockResolver.h deleted file mode 100644 index 0c9b95464186..000000000000 --- a/tools/aapt2/MockResolver.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AAPT_MOCK_RESOLVER_H -#define AAPT_MOCK_RESOLVER_H - -#include "Maybe.h" -#include "Resolver.h" -#include "Resource.h" -#include "ResourceTable.h" -#include "ResourceTableResolver.h" -#include "ResourceValues.h" -#include "StringPiece.h" - -#include <map> -#include <string> - -namespace aapt { - -struct MockResolver : public IResolver { - MockResolver(const std::shared_ptr<ResourceTable>& table, - const std::map<ResourceName, ResourceId>& items) : - mResolver(std::make_shared<ResourceTableResolver>( - table, std::vector<std::shared_ptr<const android::AssetManager>>())), - mAttr(false, android::ResTable_map::TYPE_ANY), mItems(items) { - } - - virtual Maybe<ResourceId> findId(const ResourceName& name) override { - Maybe<ResourceId> result = mResolver->findId(name); - if (result) { - return result; - } - - const auto iter = mItems.find(name); - if (iter != mItems.end()) { - return iter->second; - } - return {}; - } - - virtual Maybe<Entry> findAttribute(const ResourceName& name) override { - Maybe<Entry> tableResult = mResolver->findAttribute(name); - if (tableResult) { - return tableResult; - } - - Maybe<ResourceId> result = findId(name); - if (result) { - if (name.type == ResourceType::kAttr) { - return Entry{ result.value(), &mAttr }; - } else { - return Entry{ result.value() }; - } - } - return {}; - } - - virtual Maybe<ResourceName> findName(ResourceId resId) override { - Maybe<ResourceName> result = mResolver->findName(resId); - if (result) { - return result; - } - - for (auto& p : mItems) { - if (p.second == resId) { - return p.first; - } - } - return {}; - } - -private: - std::shared_ptr<ResourceTableResolver> mResolver; - Attribute mAttr; - std::map<ResourceName, ResourceId> mItems; -}; - -} // namespace aapt - -#endif // AAPT_MOCK_RESOLVER_H diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h index 1e15e2071e65..6d752bb38d2a 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -17,19 +17,63 @@ #ifndef AAPT_NAME_MANGLER_H #define AAPT_NAME_MANGLER_H +#include "Resource.h" + +#include "util/Maybe.h" + +#include <set> #include <string> namespace aapt { -struct NameMangler { +struct NameManglerPolicy { + /** + * Represents the package we are trying to build. References pointing + * to this package are not mangled, and mangled references inherit this package name. + */ + std::u16string targetPackageName; + + /** + * We must know which references to mangle, and which to keep (android vs. com.android.support). + */ + std::set<std::u16string> packagesToMangle; +}; + +class NameMangler { +private: + NameManglerPolicy mPolicy; + +public: + NameMangler(NameManglerPolicy policy) : mPolicy(policy) { + } + + Maybe<ResourceName> mangleName(const ResourceName& name) { + if (mPolicy.targetPackageName == name.package || + mPolicy.packagesToMangle.count(name.package) == 0) { + return {}; + } + + return ResourceName{ + mPolicy.targetPackageName, + name.type, + mangleEntry(name.package, name.entry) + }; + } + + bool shouldMangle(const std::u16string& package) { + if (package.empty() || mPolicy.targetPackageName == package) { + return false; + } + return mPolicy.packagesToMangle.count(package) != 0; + } + /** - * Mangles the name in `outName` with the `package` and stores the mangled - * result in `outName`. The mangled name should contain symbols that are - * illegal to define in XML, so that there will never be name mangling - * collisions. + * Returns a mangled name that is a combination of `name` and `package`. + * The mangled name should contain symbols that are illegal to define in XML, + * so that there will never be name mangling collisions. */ - static void mangle(const std::u16string& package, std::u16string* outName) { - *outName = package + u"$" + *outName; + static std::u16string mangleEntry(const std::u16string& package, const std::u16string& name) { + return package + u"$" + name; } /** diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/ProguardRules.cpp index e89fb7c8bd3d..7f4dc91eae9f 100644 --- a/tools/aapt2/ProguardRules.cpp +++ b/tools/aapt2/ProguardRules.cpp @@ -15,9 +15,10 @@ */ #include "ProguardRules.h" -#include "Util.h" #include "XmlDom.h" +#include "util/Util.h" + #include <memory> #include <string> @@ -61,11 +62,11 @@ public: protected: void addClass(size_t lineNumber, const std::u16string& className) { - mKeepSet->addClass(mSource.line(lineNumber), className); + mKeepSet->addClass(Source(mSource.path, lineNumber), className); } void addMethod(size_t lineNumber, const std::u16string& methodName) { - mKeepSet->addMethod(mSource.line(lineNumber), methodName); + mKeepSet->addMethod(Source(mSource.path, lineNumber), methodName); } private: @@ -186,30 +187,36 @@ struct ManifestVisitor : public BaseVisitor { std::u16string mPackage; }; -bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet) { +bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet) { ManifestVisitor visitor(source, keepSet); - node->accept(&visitor); - return true; + if (res->root) { + res->root->accept(&visitor); + return true; + } + return false; } -bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node, - KeepSet* keepSet) { - switch (type) { +bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet) { + if (!res->root) { + return false; + } + + switch (res->file.name.type) { case ResourceType::kLayout: { LayoutVisitor visitor(source, keepSet); - node->accept(&visitor); + res->root->accept(&visitor); break; } case ResourceType::kXml: { XmlResourceVisitor visitor(source, keepSet); - node->accept(&visitor); + res->root->accept(&visitor); break; } case ResourceType::kTransition: { TransitionVisitor visitor(source, keepSet); - node->accept(&visitor); + res->root->accept(&visitor); break; } @@ -221,14 +228,14 @@ bool collectProguardRules(ResourceType type, const Source& source, xml::Node* no bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) { for (const auto& entry : keepSet.mKeepSet) { - for (const SourceLine& source : entry.second) { + for (const Source& source : entry.second) { *out << "// Referenced at " << source << "\n"; } *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl; } for (const auto& entry : keepSet.mKeepMethodSet) { - for (const SourceLine& source : entry.second) { + for (const Source& source : entry.second) { *out << "// Referenced at " << source << "\n"; } *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl; diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/ProguardRules.h index bbb3e64e6758..be61eb9095c2 100644 --- a/tools/aapt2/ProguardRules.h +++ b/tools/aapt2/ProguardRules.h @@ -19,7 +19,8 @@ #include "Resource.h" #include "Source.h" -#include "XmlDom.h" + +#include "process/IResourceTableConsumer.h" #include <map> #include <ostream> @@ -31,24 +32,23 @@ namespace proguard { class KeepSet { public: - inline void addClass(const SourceLine& source, const std::u16string& className) { + inline void addClass(const Source& source, const std::u16string& className) { mKeepSet[className].insert(source); } - inline void addMethod(const SourceLine& source, const std::u16string& methodName) { + inline void addMethod(const Source& source, const std::u16string& methodName) { mKeepMethodSet[methodName].insert(source); } private: friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet); - std::map<std::u16string, std::set<SourceLine>> mKeepSet; - std::map<std::u16string, std::set<SourceLine>> mKeepMethodSet; + std::map<std::u16string, std::set<Source>> mKeepSet; + std::map<std::u16string, std::set<Source>> mKeepMethodSet; }; -bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet); -bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node, - KeepSet* keepSet); +bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet); +bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet); bool writeKeepSet(std::ostream* out, const KeepSet& keepSet); diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h deleted file mode 100644 index cb9318e24917..000000000000 --- a/tools/aapt2/Resolver.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AAPT_RESOLVER_H -#define AAPT_RESOLVER_H - -#include "Maybe.h" -#include "Resource.h" -#include "ResourceValues.h" - -#include <androidfw/ResourceTypes.h> - -namespace aapt { - -/** - * Resolves symbolic references (package:type/entry) into resource IDs/objects. - */ -class IResolver { -public: - virtual ~IResolver() {}; - - /** - * Holds the result of a resource name lookup. - */ - struct Entry { - /** - * The ID of the resource. ResourceId::isValid() may - * return false if the resource has not been assigned - * an ID. - */ - ResourceId id; - - /** - * If the resource is an attribute, this will point - * to a valid Attribute object, or else it will be - * nullptr. - */ - const Attribute* attr; - }; - - /** - * Returns a ResourceID if the name is found. The ResourceID - * may not be valid if the resource was not assigned an ID. - */ - virtual Maybe<ResourceId> findId(const ResourceName& name) = 0; - - /** - * Returns an Entry if the name is found. Entry::attr - * may be nullptr if the resource is not an attribute. - */ - virtual Maybe<Entry> findAttribute(const ResourceName& name) = 0; - - /** - * Find a resource by ID. Resolvers may contain resources without - * resource IDs assigned to them. - */ - virtual Maybe<ResourceName> findName(ResourceId resId) = 0; -}; - -} // namespace aapt - -#endif // AAPT_RESOLVER_H diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index 287d8de1b767..1962f582ae7c 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -15,7 +15,7 @@ */ #include "Resource.h" -#include "StringPiece.h" +#include "util/StringPiece.h" #include <map> #include <string> @@ -28,7 +28,7 @@ StringPiece16 toString(ResourceType type) { case ResourceType::kAnimator: return u"animator"; case ResourceType::kArray: return u"array"; case ResourceType::kAttr: return u"attr"; - case ResourceType::kAttrPrivate: return u"attr"; + case ResourceType::kAttrPrivate: return u"^attr-private"; case ResourceType::kBool: return u"bool"; case ResourceType::kColor: return u"color"; case ResourceType::kDimen: return u"dimen"; diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index fa9ac07b1779..31fe298670ae 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -17,12 +17,16 @@ #ifndef AAPT_RESOURCE_H #define AAPT_RESOURCE_H -#include "StringPiece.h" +#include "ConfigDescription.h" +#include "Source.h" + +#include "util/StringPiece.h" #include <iomanip> #include <limits> #include <string> #include <tuple> +#include <vector> namespace aapt { @@ -78,6 +82,7 @@ struct ResourceName { bool operator<(const ResourceName& rhs) const; bool operator==(const ResourceName& rhs) const; bool operator!=(const ResourceName& rhs) const; + std::u16string toString() const; }; /** @@ -125,7 +130,7 @@ struct ResourceId { ResourceId(); ResourceId(const ResourceId& rhs); ResourceId(uint32_t resId); - ResourceId(size_t p, size_t t, size_t e); + ResourceId(uint8_t p, uint8_t t, uint16_t e); bool isValid() const; uint8_t packageId() const; @@ -135,6 +140,29 @@ struct ResourceId { bool operator==(const ResourceId& rhs) const; }; +struct SourcedResourceName { + ResourceName name; + size_t line; + + inline bool operator==(const SourcedResourceName& rhs) const { + return name == rhs.name && line == rhs.line; + } +}; + +struct ResourceFile { + // Name + ResourceName name; + + // Configuration + ConfigDescription config; + + // Source + Source source; + + // Exported symbols + std::vector<SourcedResourceName> exportedSymbols; +}; + // // ResourceId implementation. // @@ -148,17 +176,7 @@ inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) { inline ResourceId::ResourceId(uint32_t resId) : id(resId) { } -inline ResourceId::ResourceId(size_t p, size_t t, size_t e) : id(0) { - if (p > std::numeric_limits<uint8_t>::max() || - t > std::numeric_limits<uint8_t>::max() || - e > std::numeric_limits<uint16_t>::max()) { - // This will leave the ResourceId in an invalid state. - return; - } - - id = (static_cast<uint8_t>(p) << 24) | - (static_cast<uint8_t>(t) << 16) | - static_cast<uint16_t>(e); +inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) : id((p << 24) | (t << 16) | e) { } inline bool ResourceId::isValid() const { @@ -217,6 +235,10 @@ inline bool ResourceName::operator<(const ResourceName& rhs) const { < std::tie(rhs.package, rhs.type, rhs.entry); } +inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) { + return ResourceNameRef(lhs) < b; +} + inline bool ResourceName::operator==(const ResourceName& rhs) const { return std::tie(package, type, entry) == std::tie(rhs.package, rhs.type, rhs.entry); @@ -227,6 +249,18 @@ inline bool ResourceName::operator!=(const ResourceName& rhs) const { != std::tie(rhs.package, rhs.type, rhs.entry); } +inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) { + return ResourceNameRef(lhs) != rhs; +} + +inline std::u16string ResourceName::toString() const { + std::u16string result; + if (!package.empty()) { + result = package + u":"; + } + return result + aapt::toString(type).toString() + u"/" + entry; +} + inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) { if (!name.package.empty()) { out << name.package << ":"; diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 13f916bfc8f3..5e5fc5338e29 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -14,473 +14,43 @@ * limitations under the License. */ -#include "Logger.h" #include "ResourceParser.h" +#include "ResourceTable.h" +#include "ResourceUtils.h" #include "ResourceValues.h" -#include "ScopedXmlPullParser.h" -#include "SourceXmlPullParser.h" -#include "Util.h" -#include "XliffXmlPullParser.h" +#include "util/Util.h" +#include "ValueVisitor.h" +#include "XmlPullParser.h" #include <sstream> namespace aapt { -void ResourceParser::extractResourceName(const StringPiece16& str, StringPiece16* outPackage, - StringPiece16* outType, StringPiece16* outEntry) { - const char16_t* start = str.data(); - const char16_t* end = start + str.size(); - const char16_t* current = start; - while (current != end) { - if (outType->size() == 0 && *current == u'/') { - outType->assign(start, current - start); - start = current + 1; - } else if (outPackage->size() == 0 && *current == u':') { - outPackage->assign(start, current - start); - start = current + 1; - } - current++; - } - outEntry->assign(start, end - start); -} - -bool ResourceParser::tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, - bool* outCreate, bool* outPrivate) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - if (trimmedStr.empty()) { - return false; - } - - if (trimmedStr.data()[0] == u'@') { - size_t offset = 1; - *outCreate = false; - if (trimmedStr.data()[1] == u'+') { - *outCreate = true; - offset += 1; - } else if (trimmedStr.data()[1] == u'*') { - *outPrivate = true; - offset += 1; - } - StringPiece16 package; - StringPiece16 type; - StringPiece16 entry; - extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), - &package, &type, &entry); - - const ResourceType* parsedType = parseResourceType(type); - if (!parsedType) { - return false; - } - - if (*outCreate && *parsedType != ResourceType::kId) { - return false; - } - - outRef->package = package; - outRef->type = *parsedType; - outRef->entry = entry; - return true; - } - return false; -} - -bool ResourceParser::tryParseAttributeReference(const StringPiece16& str, - ResourceNameRef* outRef) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - if (trimmedStr.empty()) { - return false; - } - - if (*trimmedStr.data() == u'?') { - StringPiece16 package; - StringPiece16 type; - StringPiece16 entry; - extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry); - - if (!type.empty() && type != u"attr") { - return false; - } - - outRef->package = package; - outRef->type = ResourceType::kAttr; - outRef->entry = entry; - return true; - } - return false; -} - -/* - * Style parent's are a bit different. We accept the following formats: - * - * @[package:]style/<entry> - * ?[package:]style/<entry> - * <package>:[style/]<entry> - * [package:style/]<entry> - */ -bool ResourceParser::parseStyleParentReference(const StringPiece16& str, Reference* outReference, - std::string* outError) { - if (str.empty()) { - return true; - } - - StringPiece16 name = str; - - bool hasLeadingIdentifiers = false; - bool privateRef = false; - - // Skip over these identifiers. A style's parent is a normal reference. - if (name.data()[0] == u'@' || name.data()[0] == u'?') { - hasLeadingIdentifiers = true; - name = name.substr(1, name.size() - 1); - if (name.data()[0] == u'*') { - privateRef = true; - name = name.substr(1, name.size() - 1); - } - } - - ResourceNameRef ref; - ref.type = ResourceType::kStyle; - - StringPiece16 typeStr; - extractResourceName(name, &ref.package, &typeStr, &ref.entry); - if (!typeStr.empty()) { - // If we have a type, make sure it is a Style. - const ResourceType* parsedType = parseResourceType(typeStr); - if (!parsedType || *parsedType != ResourceType::kStyle) { - std::stringstream err; - err << "invalid resource type '" << typeStr << "' for parent of style"; - *outError = err.str(); - return false; - } - } else { - // No type was defined, this should not have a leading identifier. - if (hasLeadingIdentifiers) { - std::stringstream err; - err << "invalid parent reference '" << str << "'"; - *outError = err.str(); - return false; - } - } - - if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) { - std::stringstream err; - err << "invalid parent reference '" << str << "'"; - *outError = err.str(); - return false; - } - - outReference->name = ref.toResourceName(); - outReference->privateReference = privateRef; - return true; -} - -std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str, - bool* outCreate) { - ResourceNameRef ref; - bool privateRef = false; - if (tryParseReference(str, &ref, outCreate, &privateRef)) { - std::unique_ptr<Reference> value = util::make_unique<Reference>(ref); - value->privateReference = privateRef; - return value; - } - - if (tryParseAttributeReference(str, &ref)) { - *outCreate = false; - return util::make_unique<Reference>(ref, Reference::Type::kAttribute); - } - return {}; -} - -std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseNullOrEmpty(const StringPiece16& str) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - android::Res_value value = {}; - if (trimmedStr == u"@null") { - // TYPE_NULL with data set to 0 is interpreted by the runtime as an error. - // Instead we set the data type to TYPE_REFERENCE with a value of 0. - value.dataType = android::Res_value::TYPE_REFERENCE; - } else if (trimmedStr == u"@empty") { - // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime. - value.dataType = android::Res_value::TYPE_NULL; - value.data = android::Res_value::DATA_NULL_EMPTY; - } else { - return {}; - } - return util::make_unique<BinaryPrimitive>(value); -} - -std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseEnumSymbol(const Attribute& enumAttr, - const StringPiece16& str) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - for (const auto& entry : enumAttr.symbols) { - // Enum symbols are stored as @package:id/symbol resources, - // so we need to match against the 'entry' part of the identifier. - const ResourceName& enumSymbolResourceName = entry.symbol.name; - if (trimmedStr == enumSymbolResourceName.entry) { - android::Res_value value = {}; - value.dataType = android::Res_value::TYPE_INT_DEC; - value.data = entry.value; - return util::make_unique<BinaryPrimitive>(value); - } - } - return {}; -} - -std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFlagSymbol(const Attribute& flagAttr, - const StringPiece16& str) { - android::Res_value flags = {}; - flags.dataType = android::Res_value::TYPE_INT_DEC; - - for (StringPiece16 part : util::tokenize(str, u'|')) { - StringPiece16 trimmedPart = util::trimWhitespace(part); - - bool flagSet = false; - for (const auto& entry : flagAttr.symbols) { - // Flag symbols are stored as @package:id/symbol resources, - // so we need to match against the 'entry' part of the identifier. - const ResourceName& flagSymbolResourceName = entry.symbol.name; - if (trimmedPart == flagSymbolResourceName.entry) { - flags.data |= entry.value; - flagSet = true; - break; - } - } - - if (!flagSet) { - return {}; - } - } - return util::make_unique<BinaryPrimitive>(flags); -} - -static uint32_t parseHex(char16_t c, bool* outError) { - if (c >= u'0' && c <= u'9') { - return c - u'0'; - } else if (c >= u'a' && c <= u'f') { - return c - u'a' + 0xa; - } else if (c >= u'A' && c <= u'F') { - return c - u'A' + 0xa; - } else { - *outError = true; - return 0xffffffffu; - } -} - -std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseColor(const StringPiece16& str) { - StringPiece16 colorStr(util::trimWhitespace(str)); - const char16_t* start = colorStr.data(); - const size_t len = colorStr.size(); - if (len == 0 || start[0] != u'#') { - return {}; - } - - android::Res_value value = {}; - bool error = false; - if (len == 4) { - value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4; - value.data = 0xff000000u; - value.data |= parseHex(start[1], &error) << 20; - value.data |= parseHex(start[1], &error) << 16; - value.data |= parseHex(start[2], &error) << 12; - value.data |= parseHex(start[2], &error) << 8; - value.data |= parseHex(start[3], &error) << 4; - value.data |= parseHex(start[3], &error); - } else if (len == 5) { - value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4; - value.data |= parseHex(start[1], &error) << 28; - value.data |= parseHex(start[1], &error) << 24; - value.data |= parseHex(start[2], &error) << 20; - value.data |= parseHex(start[2], &error) << 16; - value.data |= parseHex(start[3], &error) << 12; - value.data |= parseHex(start[3], &error) << 8; - value.data |= parseHex(start[4], &error) << 4; - value.data |= parseHex(start[4], &error); - } else if (len == 7) { - value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8; - value.data = 0xff000000u; - value.data |= parseHex(start[1], &error) << 20; - value.data |= parseHex(start[2], &error) << 16; - value.data |= parseHex(start[3], &error) << 12; - value.data |= parseHex(start[4], &error) << 8; - value.data |= parseHex(start[5], &error) << 4; - value.data |= parseHex(start[6], &error); - } else if (len == 9) { - value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8; - value.data |= parseHex(start[1], &error) << 28; - value.data |= parseHex(start[2], &error) << 24; - value.data |= parseHex(start[3], &error) << 20; - value.data |= parseHex(start[4], &error) << 16; - value.data |= parseHex(start[5], &error) << 12; - value.data |= parseHex(start[6], &error) << 8; - value.data |= parseHex(start[7], &error) << 4; - value.data |= parseHex(start[8], &error); - } else { - return {}; - } - return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value); -} - -std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseBool(const StringPiece16& str) { - StringPiece16 trimmedStr(util::trimWhitespace(str)); - uint32_t data = 0; - if (trimmedStr == u"true" || trimmedStr == u"TRUE") { - data = 0xffffffffu; - } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") { - return {}; - } - android::Res_value value = {}; - value.dataType = android::Res_value::TYPE_INT_BOOLEAN; - value.data = data; - return util::make_unique<BinaryPrimitive>(value); -} - -std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseInt(const StringPiece16& str) { - android::Res_value value; - if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) { - return {}; - } - return util::make_unique<BinaryPrimitive>(value); -} +constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2"; -std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseFloat(const StringPiece16& str) { - android::Res_value value; - if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) { - return {}; - } - return util::make_unique<BinaryPrimitive>(value); -} - -uint32_t ResourceParser::androidTypeToAttributeTypeMask(uint16_t type) { - switch (type) { - case android::Res_value::TYPE_NULL: - case android::Res_value::TYPE_REFERENCE: - case android::Res_value::TYPE_ATTRIBUTE: - case android::Res_value::TYPE_DYNAMIC_REFERENCE: - return android::ResTable_map::TYPE_REFERENCE; - - case android::Res_value::TYPE_STRING: - return android::ResTable_map::TYPE_STRING; - - case android::Res_value::TYPE_FLOAT: - return android::ResTable_map::TYPE_FLOAT; - - case android::Res_value::TYPE_DIMENSION: - return android::ResTable_map::TYPE_DIMENSION; - - case android::Res_value::TYPE_FRACTION: - return android::ResTable_map::TYPE_FRACTION; - - case android::Res_value::TYPE_INT_DEC: - case android::Res_value::TYPE_INT_HEX: - return android::ResTable_map::TYPE_INTEGER | - android::ResTable_map::TYPE_ENUM | - android::ResTable_map::TYPE_FLAGS; - - case android::Res_value::TYPE_INT_BOOLEAN: - return android::ResTable_map::TYPE_BOOLEAN; - - case android::Res_value::TYPE_INT_COLOR_ARGB8: - case android::Res_value::TYPE_INT_COLOR_RGB8: - case android::Res_value::TYPE_INT_COLOR_ARGB4: - case android::Res_value::TYPE_INT_COLOR_RGB4: - return android::ResTable_map::TYPE_COLOR; - - default: - return 0; - }; -} - -std::unique_ptr<Item> ResourceParser::parseItemForAttribute( - const StringPiece16& value, uint32_t typeMask, - std::function<void(const ResourceName&)> onCreateReference) { - std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value); - if (nullOrEmpty) { - return std::move(nullOrEmpty); - } - - bool create = false; - std::unique_ptr<Reference> reference = tryParseReference(value, &create); - if (reference) { - if (create && onCreateReference) { - onCreateReference(reference->name); - } - return std::move(reference); - } - - if (typeMask & android::ResTable_map::TYPE_COLOR) { - // Try parsing this as a color. - std::unique_ptr<BinaryPrimitive> color = tryParseColor(value); - if (color) { - return std::move(color); - } - } - - if (typeMask & android::ResTable_map::TYPE_BOOLEAN) { - // Try parsing this as a boolean. - std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value); - if (boolean) { - return std::move(boolean); - } - } - - if (typeMask & android::ResTable_map::TYPE_INTEGER) { - // Try parsing this as an integer. - std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value); - if (integer) { - return std::move(integer); - } - } - - const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT | - android::ResTable_map::TYPE_DIMENSION | - android::ResTable_map::TYPE_FRACTION; - if (typeMask & floatMask) { - // Try parsing this as a float. - std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value); - if (floatingPoint) { - if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) { - return std::move(floatingPoint); - } - } +static Maybe<StringPiece16> findAttribute(XmlPullParser* parser, const StringPiece16& name) { + auto iter = parser->findAttribute(u"", name); + if (iter != parser->endAttributes()) { + return StringPiece16(util::trimWhitespace(iter->value)); } return {}; } -/** - * We successively try to parse the string as a resource type that the Attribute - * allows. - */ -std::unique_ptr<Item> ResourceParser::parseItemForAttribute( - const StringPiece16& str, const Attribute& attr, - std::function<void(const ResourceName&)> onCreateReference) { - const uint32_t typeMask = attr.typeMask; - std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference); - if (value) { - return value; - } - - if (typeMask & android::ResTable_map::TYPE_ENUM) { - // Try parsing this as an enum. - std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str); - if (enumValue) { - return std::move(enumValue); - } - } - - if (typeMask & android::ResTable_map::TYPE_FLAGS) { - // Try parsing this as a flag. - std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str); - if (flagValue) { - return std::move(flagValue); +static Maybe<StringPiece16> findNonEmptyAttribute(XmlPullParser* parser, + const StringPiece16& name) { + auto iter = parser->findAttribute(u"", name); + if (iter != parser->endAttributes()) { + StringPiece16 trimmed = util::trimWhitespace(iter->value); + if (!trimmed.empty()) { + return trimmed; } } return {}; } -ResourceParser::ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source, - const ConfigDescription& config, - const std::shared_ptr<XmlPullParser>& parser) : - mTable(table), mSource(source), mConfig(config), mLogger(source), - mParser(std::make_shared<XliffXmlPullParser>(parser)) { +ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, + const ConfigDescription& config) : + mDiag(diag), mTable(table), mSource(source), mConfig(config) { } /** @@ -497,6 +67,11 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou while (XmlPullParser::isGoodEvent(parser->next())) { const XmlPullParser::Event event = parser->getEvent(); if (event == XmlPullParser::Event::kEndElement) { + if (!parser->getElementNamespace().empty()) { + // We already warned and skipped the start element, so just skip here too + continue; + } + depth--; if (depth == 0) { break; @@ -512,15 +87,16 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou builder.append(parser->getText()); } else if (event == XmlPullParser::Event::kStartElement) { - if (parser->getElementNamespace().size() > 0) { - mLogger.warn(parser->getLineNumber()) - << "skipping element '" - << parser->getElementName() - << "' with unknown namespace '" - << parser->getElementNamespace() - << "'." - << std::endl; - XmlPullParser::skipCurrentElement(parser); + if (!parser->getElementNamespace().empty()) { + if (parser->getElementNamespace() != sXliffNamespaceUri) { + // Only warn if this isn't an xliff namespace. + mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "skipping element '" + << parser->getElementName() + << "' with unknown namespace '" + << parser->getElementNamespace() + << "'"); + } continue; } depth++; @@ -536,11 +112,8 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou } if (builder.str().size() > std::numeric_limits<uint32_t>::max()) { - mLogger.error(parser->getLineNumber()) - << "style string '" - << builder.str() - << "' is too long." - << std::endl; + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "style string '" << builder.str() << "' is too long"); return false; } spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) }); @@ -548,11 +121,7 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou } else if (event == XmlPullParser::Event::kComment) { // Skip } else { - mLogger.warn(parser->getLineNumber()) - << "unknown event " - << event - << "." - << std::endl; + assert(false); } } assert(spanStack.empty() && "spans haven't been fully processed"); @@ -561,40 +130,38 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou return true; } -bool ResourceParser::parse() { - while (XmlPullParser::isGoodEvent(mParser->next())) { - if (mParser->getEvent() != XmlPullParser::Event::kStartElement) { +bool ResourceParser::parse(XmlPullParser* parser) { + bool error = false; + const size_t depth = parser->getDepth(); + while (XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + // Skip comments and text. continue; } - ScopedXmlPullParser parser(mParser.get()); - if (!parser.getElementNamespace().empty() || - parser.getElementName() != u"resources") { - mLogger.error(parser.getLineNumber()) - << "root element must be <resources> in the global namespace." - << std::endl; + if (!parser->getElementNamespace().empty() || parser->getElementName() != u"resources") { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "root element must be <resources>"); return false; } - if (!parseResources(&parser)) { - return false; - } - } + error |= !parseResources(parser); + break; + }; - if (mParser->getEvent() == XmlPullParser::Event::kBadDocument) { - mLogger.error(mParser->getLineNumber()) - << mParser->getLastError() - << std::endl; + if (parser->getEvent() == XmlPullParser::Event::kBadDocument) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "xml parser error: " << parser->getLastError()); return false; } - return true; + return !error; } bool ResourceParser::parseResources(XmlPullParser* parser) { - bool success = true; - + bool error = false; std::u16string comment; - while (XmlPullParser::isGoodEvent(parser->next())) { + const size_t depth = parser->getDepth(); + while (XmlPullParser::nextChildNode(parser, depth)) { const XmlPullParser::Event event = parser->getEvent(); if (event == XmlPullParser::Event::kComment) { comment = parser->getComment(); @@ -603,134 +170,95 @@ bool ResourceParser::parseResources(XmlPullParser* parser) { if (event == XmlPullParser::Event::kText) { if (!util::trimWhitespace(parser->getText()).empty()) { - comment = u""; + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "plain text not allowed here"); + error = true; } continue; } - if (event != XmlPullParser::Event::kStartElement) { - continue; - } + assert(event == XmlPullParser::Event::kStartElement); - ScopedXmlPullParser childParser(parser); - - if (!childParser.getElementNamespace().empty()) { + if (!parser->getElementNamespace().empty()) { // Skip unknown namespace. continue; } - StringPiece16 name = childParser.getElementName(); - if (name == u"skip" || name == u"eat-comment") { - continue; - } - - if (name == u"private-symbols") { - // Handle differently. - mLogger.note(childParser.getLineNumber()) - << "got a <private-symbols> tag." - << std::endl; + std::u16string elementName = parser->getElementName(); + if (elementName == u"skip" || elementName == u"eat-comment") { + comment = u""; continue; } - const auto endAttrIter = childParser.endAttributes(); - auto attrIter = childParser.findAttribute(u"", u"name"); - if (attrIter == endAttrIter || attrIter->value.empty()) { - mLogger.error(childParser.getLineNumber()) - << "<" << name << "> tag must have a 'name' attribute." - << std::endl; - success = false; + Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name"); + if (!maybeName) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "<" << elementName << "> tag must have a 'name' attribute"); + error = true; continue; } // Copy because our iterator will go out of scope when // we parse more XML. - std::u16string attributeName = attrIter->value; + std::u16string name = maybeName.value().toString(); - if (name == u"item") { + if (elementName == u"item") { // Items simply have their type encoded in the type attribute. - auto typeIter = childParser.findAttribute(u"", u"type"); - if (typeIter == endAttrIter || typeIter->value.empty()) { - mLogger.error(childParser.getLineNumber()) - << "<item> must have a 'type' attribute." - << std::endl; - success = false; + if (Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type")) { + elementName = maybeType.value().toString(); + } else { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "<item> must have a 'type' attribute"); + error = true; continue; } - name = typeIter->value; } - if (name == u"id") { - success &= mTable->addResource(ResourceNameRef{ {}, ResourceType::kId, attributeName }, - {}, mSource.line(childParser.getLineNumber()), - util::make_unique<Id>()); - } else if (name == u"string") { - success &= parseString(&childParser, - ResourceNameRef{ {}, ResourceType::kString, attributeName }); - } else if (name == u"color") { - success &= parseColor(&childParser, - ResourceNameRef{ {}, ResourceType::kColor, attributeName }); - } else if (name == u"drawable") { - success &= parseColor(&childParser, - ResourceNameRef{ {}, ResourceType::kDrawable, attributeName }); - } else if (name == u"bool") { - success &= parsePrimitive(&childParser, - ResourceNameRef{ {}, ResourceType::kBool, attributeName }); - } else if (name == u"integer") { - success &= parsePrimitive( - &childParser, - ResourceNameRef{ {}, ResourceType::kInteger, attributeName }); - } else if (name == u"dimen") { - success &= parsePrimitive(&childParser, - ResourceNameRef{ {}, ResourceType::kDimen, attributeName }); - } else if (name == u"fraction") { -// success &= parsePrimitive( -// &childParser, -// ResourceNameRef{ {}, ResourceType::kFraction, attributeName }); - } else if (name == u"style") { - success &= parseStyle(&childParser, - ResourceNameRef{ {}, ResourceType::kStyle, attributeName }); - } else if (name == u"plurals") { - success &= parsePlural(&childParser, - ResourceNameRef{ {}, ResourceType::kPlurals, attributeName }); - } else if (name == u"array") { - success &= parseArray(&childParser, - ResourceNameRef{ {}, ResourceType::kArray, attributeName }, - android::ResTable_map::TYPE_ANY); - } else if (name == u"string-array") { - success &= parseArray(&childParser, - ResourceNameRef{ {}, ResourceType::kArray, attributeName }, - android::ResTable_map::TYPE_STRING); - } else if (name == u"integer-array") { - success &= parseArray(&childParser, - ResourceNameRef{ {}, ResourceType::kArray, attributeName }, - android::ResTable_map::TYPE_INTEGER); - } else if (name == u"public") { - success &= parsePublic(&childParser, attributeName); - } else if (name == u"declare-styleable") { - success &= parseDeclareStyleable( - &childParser, - ResourceNameRef{ {}, ResourceType::kStyleable, attributeName }); - } else if (name == u"attr") { - success &= parseAttr(&childParser, - ResourceNameRef{ {}, ResourceType::kAttr, attributeName }); - } else if (name == u"bag") { - } else if (name == u"public-padding") { - } else if (name == u"java-symbol") { - } else if (name == u"add-resource") { - } - } - - if (parser->getEvent() == XmlPullParser::Event::kBadDocument) { - mLogger.error(parser->getLineNumber()) - << parser->getLastError() - << std::endl; - return false; + if (elementName == u"id") { + error |= !mTable->addResource(ResourceNameRef{ {}, ResourceType::kId, name }, + {}, mSource.withLine(parser->getLineNumber()), + util::make_unique<Id>(), mDiag); + + } else if (elementName == u"string") { + error |= !parseString(parser, ResourceNameRef{ {}, ResourceType::kString, name }); + } else if (elementName == u"color") { + error |= !parseColor(parser, ResourceNameRef{ {}, ResourceType::kColor, name }); + } else if (elementName == u"drawable") { + error |= !parseColor(parser, ResourceNameRef{ {}, ResourceType::kDrawable, name }); + } else if (elementName == u"bool") { + error |= !parsePrimitive(parser, ResourceNameRef{ {}, ResourceType::kBool, name }); + } else if (elementName == u"integer") { + error |= !parsePrimitive(parser, ResourceNameRef{ {}, ResourceType::kInteger, name }); + } else if (elementName == u"dimen") { + error |= !parsePrimitive(parser, ResourceNameRef{ {}, ResourceType::kDimen, name }); + } else if (elementName == u"style") { + error |= !parseStyle(parser, ResourceNameRef{ {}, ResourceType::kStyle, name }); + } else if (elementName == u"plurals") { + error |= !parsePlural(parser, ResourceNameRef{ {}, ResourceType::kPlurals, name }); + } else if (elementName == u"array") { + error |= !parseArray(parser, ResourceNameRef{ {}, ResourceType::kArray, name }, + android::ResTable_map::TYPE_ANY); + } else if (elementName == u"string-array") { + error |= !parseArray(parser, ResourceNameRef{ {}, ResourceType::kArray, name }, + android::ResTable_map::TYPE_STRING); + } else if (elementName == u"integer-array") { + error |= !parseArray(parser, ResourceNameRef{ {}, ResourceType::kArray, name }, + android::ResTable_map::TYPE_INTEGER); + } else if (elementName == u"public") { + error |= !parsePublic(parser, name); + } else if (elementName == u"declare-styleable") { + error |= !parseDeclareStyleable(parser, + ResourceNameRef{ {}, ResourceType::kStyleable, name }); + } else if (elementName == u"attr") { + error |= !parseAttr(parser, ResourceNameRef{ {}, ResourceType::kAttr, name }); + } else { + mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "unknown resource type '" << elementName << "'"); + } } - return success; + return !error; } - - enum { kAllowRawString = true, kNoRawString = false @@ -753,34 +281,29 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t t return {}; } - StringPool& pool = mTable->getValueStringPool(); - if (!styleString.spans.empty()) { // This can only be a StyledString. return util::make_unique<StyledString>( - pool.makeRef(styleString, StringPool::Context{ 1, mConfig })); + mTable->stringPool.makeRef(styleString, StringPool::Context{ 1, mConfig })); } auto onCreateReference = [&](const ResourceName& name) { // name.package can be empty here, as it will assume the package name of the table. - mTable->addResource(name, {}, mSource.line(beginXmlLine), util::make_unique<Id>()); + mTable->addResource(name, {}, mSource.withLine(beginXmlLine), util::make_unique<Id>(), + mDiag); }; // Process the raw value. - std::unique_ptr<Item> processedItem = parseItemForAttribute(rawValue, typeMask, - onCreateReference); + std::unique_ptr<Item> processedItem = ResourceUtils::parseItemForAttribute(rawValue, typeMask, + onCreateReference); if (processedItem) { // Fix up the reference. - visitFunc<Reference>(*processedItem, [&](Reference& ref) { - if (!ref.name.package.empty()) { - // The package name was set, so lookup its alias. - parser->applyPackageAlias(&ref.name.package, mTable->getPackage()); - } else { - // The package name was left empty, so it assumes the default package - // without alias lookup. - ref.name.package = mTable->getPackage(); + if (Reference* ref = valueCast<Reference>(processedItem.get())) { + if (Maybe<ResourceName> transformedName = + parser->transformPackage(ref->name.value(), u"")) { + ref->name = std::move(transformedName); } - }); + } return processedItem; } @@ -788,31 +311,25 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t t if (typeMask & android::ResTable_map::TYPE_STRING) { // Use the trimmed, escaped string. return util::make_unique<String>( - pool.makeRef(styleString.str, StringPool::Context{ 1, mConfig })); + mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig })); } // We can't parse this so return a RawString if we are allowed. if (allowRawValue) { return util::make_unique<RawString>( - pool.makeRef(rawValue, StringPool::Context{ 1, mConfig })); + mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig })); } return {}; } bool ResourceParser::parseString(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); - - // Mark the string as untranslateable if needed. - const auto endAttrIter = parser->endAttributes(); - auto attrIter = parser->findAttribute(u"", u"untranslateable"); - // bool untranslateable = attrIter != endAttrIter; - // TODO(adamlesinski): Do something with this (mark the string). - - // Deal with the product. - attrIter = parser->findAttribute(u"", u"product"); - if (attrIter != endAttrIter) { - if (attrIter->value != u"default" && attrIter->value != u"phone") { - // TODO(adamlesinski): Match products. + const Source source = mSource.withLine(parser->getLineNumber()); + + // TODO(adamlesinski): Read "untranslateable" attribute. + + if (Maybe<StringPiece16> maybeProduct = findAttribute(parser, u"product")) { + if (maybeProduct.value() != u"default" && maybeProduct.value() != u"phone") { + // TODO(adamlesinski): Actually match product. return true; } } @@ -820,110 +337,95 @@ bool ResourceParser::parseString(XmlPullParser* parser, const ResourceNameRef& r std::unique_ptr<Item> processedItem = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString); if (!processedItem) { - mLogger.error(source.line) - << "not a valid string." - << std::endl; + mDiag->error(DiagMessage(source) << "not a valid string"); return false; } - - return mTable->addResource(resourceName, mConfig, source, std::move(processedItem)); + return mTable->addResource(resourceName, mConfig, source, std::move(processedItem), + mDiag); } bool ResourceParser::parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); + const Source source = mSource.withLine(parser->getLineNumber()); std::unique_ptr<Item> item = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString); if (!item) { - mLogger.error(source.line) << "invalid color." << std::endl; + mDiag->error(DiagMessage(source) << "invalid color"); return false; } - return mTable->addResource(resourceName, mConfig, source, std::move(item)); + return mTable->addResource(resourceName, mConfig, source, std::move(item), + mDiag); } bool ResourceParser::parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); + const Source source = mSource.withLine(parser->getLineNumber()); uint32_t typeMask = 0; switch (resourceName.type) { - case ResourceType::kInteger: - typeMask |= android::ResTable_map::TYPE_INTEGER; - break; + case ResourceType::kInteger: + typeMask |= android::ResTable_map::TYPE_INTEGER; + break; - case ResourceType::kDimen: - typeMask |= android::ResTable_map::TYPE_DIMENSION - | android::ResTable_map::TYPE_FLOAT - | android::ResTable_map::TYPE_FRACTION; - break; + case ResourceType::kDimen: + typeMask |= android::ResTable_map::TYPE_DIMENSION + | android::ResTable_map::TYPE_FLOAT + | android::ResTable_map::TYPE_FRACTION; + break; - case ResourceType::kBool: - typeMask |= android::ResTable_map::TYPE_BOOLEAN; - break; + case ResourceType::kBool: + typeMask |= android::ResTable_map::TYPE_BOOLEAN; + break; - default: - assert(false); - break; + default: + assert(false); + break; } std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString); if (!item) { - mLogger.error(source.line) - << "invalid " - << resourceName.type - << "." - << std::endl; + mDiag->error(DiagMessage(source) << "invalid " << resourceName.type); return false; } - - return mTable->addResource(resourceName, mConfig, source, std::move(item)); + return mTable->addResource(resourceName, mConfig, source, std::move(item), + mDiag); } bool ResourceParser::parsePublic(XmlPullParser* parser, const StringPiece16& name) { - const SourceLine source = mSource.line(parser->getLineNumber()); - - const auto endAttrIter = parser->endAttributes(); - const auto typeAttrIter = parser->findAttribute(u"", u"type"); - if (typeAttrIter == endAttrIter || typeAttrIter->value.empty()) { - mLogger.error(source.line) - << "<public> must have a 'type' attribute." - << std::endl; + const Source source = mSource.withLine(parser->getLineNumber()); + + Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type"); + if (!maybeType) { + mDiag->error(DiagMessage(source) << "<public> must have a 'type' attribute"); return false; } - const ResourceType* parsedType = parseResourceType(typeAttrIter->value); + const ResourceType* parsedType = parseResourceType(maybeType.value()); if (!parsedType) { - mLogger.error(source.line) - << "invalid resource type '" - << typeAttrIter->value - << "' in <public>." - << std::endl; + mDiag->error(DiagMessage(source) << "invalid resource type '" << maybeType.value() + << "' in <public>"); return false; } ResourceNameRef resourceName { {}, *parsedType, name }; ResourceId resourceId; - const auto idAttrIter = parser->findAttribute(u"", u"id"); - if (idAttrIter != endAttrIter && !idAttrIter->value.empty()) { + if (Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"id")) { android::Res_value val; - bool result = android::ResTable::stringToInt(idAttrIter->value.data(), - idAttrIter->value.size(), &val); + bool result = android::ResTable::stringToInt(maybeId.value().data(), + maybeId.value().size(), &val); resourceId.id = val.data; if (!result || !resourceId.isValid()) { - mLogger.error(source.line) - << "invalid resource ID '" - << idAttrIter->value - << "' in <public>." - << std::endl; + mDiag->error(DiagMessage(source) << "invalid resource ID '" << maybeId.value() + << "' in <public>"); return false; } } if (*parsedType == ResourceType::kId) { // An ID marked as public is also the definition of an ID. - mTable->addResource(resourceName, {}, source, util::make_unique<Id>()); + mTable->addResource(resourceName, {}, source, util::make_unique<Id>(), + mDiag); } - - return mTable->markPublic(resourceName, resourceId, source); + return mTable->markPublic(resourceName, resourceId, source, mDiag); } static uint32_t parseFormatType(const StringPiece16& piece) { @@ -954,13 +456,14 @@ static uint32_t parseFormatAttribute(const StringPiece16& str) { } bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); + const Source source = mSource.withLine(parser->getLineNumber()); ResourceName actualName = resourceName.toResourceName(); std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &actualName, false); if (!attr) { return false; } - return mTable->addResource(actualName, mConfig, source, std::move(attr)); + return mTable->addResource(actualName, mConfig, source, std::move(attr), + mDiag); } std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser, @@ -968,16 +471,12 @@ std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser, bool weak) { uint32_t typeMask = 0; - const auto endAttrIter = parser->endAttributes(); - const auto formatAttrIter = parser->findAttribute(u"", u"format"); - if (formatAttrIter != endAttrIter) { - typeMask = parseFormatAttribute(formatAttrIter->value); + Maybe<StringPiece16> maybeFormat = findAttribute(parser, u"format"); + if (maybeFormat) { + typeMask = parseFormatAttribute(maybeFormat.value()); if (typeMask == 0) { - mLogger.error(parser->getLineNumber()) - << "invalid attribute format '" - << formatAttrIter->value - << "'." - << std::endl; + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "invalid attribute format '" << maybeFormat.value() << "'"); return {}; } } @@ -985,9 +484,9 @@ std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser, // If this is a declaration, the package name may be in the name. Separate these out. // Eg. <attr name="android:text" /> // No format attribute is allowed. - if (weak && formatAttrIter == endAttrIter) { + if (weak && !maybeFormat) { StringPiece16 package, type, name; - extractResourceName(resourceName->entry, &package, &type, &name); + ResourceUtils::extractResourceName(resourceName->entry, &package, &type, &name); if (type.empty() && !package.empty()) { resourceName->package = package.toString(); resourceName->entry = name.toString(); @@ -996,56 +495,54 @@ std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser, std::vector<Attribute::Symbol> items; + std::u16string comment; bool error = false; - while (XmlPullParser::isGoodEvent(parser->next())) { + const size_t depth = parser->getDepth(); + while (XmlPullParser::nextChildNode(parser, depth)) { if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + // Skip comments and text. continue; } - ScopedXmlPullParser childParser(parser); - - const std::u16string& name = childParser.getElementName(); - if (!childParser.getElementNamespace().empty() - || (name != u"flag" && name != u"enum")) { - mLogger.error(childParser.getLineNumber()) - << "unexpected tag <" - << name - << "> in <attr>." - << std::endl; - error = true; - continue; - } - - if (name == u"enum") { - if (typeMask & android::ResTable_map::TYPE_FLAGS) { - mLogger.error(childParser.getLineNumber()) - << "can not define an <enum>; already defined a <flag>." - << std::endl; - error = true; - continue; + const std::u16string& elementNamespace = parser->getElementNamespace(); + const std::u16string& elementName = parser->getElementName(); + if (elementNamespace == u"" && (elementName == u"flag" || elementName == u"enum")) { + if (elementName == u"enum") { + if (typeMask & android::ResTable_map::TYPE_FLAGS) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "can not define an <enum>; already defined a <flag>"); + error = true; + continue; + } + typeMask |= android::ResTable_map::TYPE_ENUM; + } else if (elementName == u"flag") { + if (typeMask & android::ResTable_map::TYPE_ENUM) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "can not define a <flag>; already defined an <enum>"); + error = true; + continue; + } + typeMask |= android::ResTable_map::TYPE_FLAGS; } - typeMask |= android::ResTable_map::TYPE_ENUM; - } else if (name == u"flag") { - if (typeMask & android::ResTable_map::TYPE_ENUM) { - mLogger.error(childParser.getLineNumber()) - << "can not define a <flag>; already defined an <enum>." - << std::endl; - error = true; - continue; - } - typeMask |= android::ResTable_map::TYPE_FLAGS; - } - Attribute::Symbol item; - if (parseEnumOrFlagItem(&childParser, name, &item)) { - if (!mTable->addResource(item.symbol.name, mConfig, - mSource.line(childParser.getLineNumber()), - util::make_unique<Id>())) { - error = true; + if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) { + if (mTable->addResource(s.value().symbol.name.value(), mConfig, + mSource.withLine(parser->getLineNumber()), + util::make_unique<Id>(), + mDiag)) { + items.push_back(std::move(s.value())); + } else { + error = true; + } } else { - items.push_back(std::move(item)); + error = true; } + } else if (elementName == u"skip" || elementName == u"eat-comment") { + comment = u""; + } else { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << ":" << elementName << ">"); error = true; } } @@ -1060,43 +557,36 @@ std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser, return attr; } -bool ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag, - Attribute::Symbol* outSymbol) { - const auto attrIterEnd = parser->endAttributes(); - const auto nameAttrIter = parser->findAttribute(u"", u"name"); - if (nameAttrIter == attrIterEnd || nameAttrIter->value.empty()) { - mLogger.error(parser->getLineNumber()) - << "no attribute 'name' found for tag <" << tag << ">." - << std::endl; - return false; +Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser, + const StringPiece16& tag) { + const Source source = mSource.withLine(parser->getLineNumber()); + + Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name"); + if (!maybeName) { + mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">"); + return {}; } - const auto valueAttrIter = parser->findAttribute(u"", u"value"); - if (valueAttrIter == attrIterEnd || valueAttrIter->value.empty()) { - mLogger.error(parser->getLineNumber()) - << "no attribute 'value' found for tag <" << tag << ">." - << std::endl; - return false; + Maybe<StringPiece16> maybeValue = findNonEmptyAttribute(parser, u"value"); + if (!maybeValue) { + mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">"); + return {}; } android::Res_value val; - if (!android::ResTable::stringToInt(valueAttrIter->value.data(), - valueAttrIter->value.size(), &val)) { - mLogger.error(parser->getLineNumber()) - << "invalid value '" - << valueAttrIter->value - << "' for <" << tag << ">; must be an integer." - << std::endl; - return false; + if (!android::ResTable::stringToInt(maybeValue.value().data(), + maybeValue.value().size(), &val)) { + mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value() + << "' for <" << tag << ">; must be an integer"); + return {}; } - outSymbol->symbol.name = ResourceName { - mTable->getPackage(), ResourceType::kId, nameAttrIter->value }; - outSymbol->value = val.data; - return true; + return Attribute::Symbol{ + Reference(ResourceName{ {}, ResourceType::kId, maybeName.value().toString() }), + val.data }; } -static bool parseXmlAttributeName(StringPiece16 str, ResourceName* outName) { +static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) { str = util::trimWhitespace(str); const char16_t* const start = str.data(); const char16_t* const end = start + str.size(); @@ -1113,289 +603,279 @@ static bool parseXmlAttributeName(StringPiece16 str, ResourceName* outName) { p++; } - outName->package = package.toString(); - outName->type = ResourceType::kAttr; - if (name.size() == 0) { - outName->entry = str.toString(); - } else { - outName->entry = name.toString(); - } - return true; + return ResourceName{ package.toString(), ResourceType::kAttr, + name.empty() ? str.toString() : name.toString() }; } -bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) { - const auto endAttrIter = parser->endAttributes(); - const auto nameAttrIter = parser->findAttribute(u"", u"name"); - if (nameAttrIter == endAttrIter || nameAttrIter->value.empty()) { - mLogger.error(parser->getLineNumber()) - << "<item> must have a 'name' attribute." - << std::endl; + +bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) { + const Source source = mSource.withLine(parser->getLineNumber()); + + Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name"); + if (!maybeName) { + mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute"); return false; } - ResourceName key; - if (!parseXmlAttributeName(nameAttrIter->value, &key)) { - mLogger.error(parser->getLineNumber()) - << "invalid attribute name '" - << nameAttrIter->value - << "'." - << std::endl; + Maybe<ResourceName> maybeKey = parseXmlAttributeName(maybeName.value()); + if (!maybeKey) { + mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'"); return false; } - if (!key.package.empty()) { - // We have a package name set, so lookup its alias. - parser->applyPackageAlias(&key.package, mTable->getPackage()); - } else { - // The package name was omitted, so use the default package name with - // no alias lookup. - key.package = mTable->getPackage(); + if (Maybe<ResourceName> transformedName = parser->transformPackage(maybeKey.value(), u"")) { + maybeKey = std::move(transformedName); } std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString); if (!value) { + mDiag->error(DiagMessage(source) << "could not parse style item"); return false; } - style.entries.push_back(Style::Entry{ Reference(key), std::move(value) }); + style->entries.push_back(Style::Entry{ Reference(maybeKey.value()), std::move(value) }); return true; } bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); + const Source source = mSource.withLine(parser->getLineNumber()); std::unique_ptr<Style> style = util::make_unique<Style>(); - const auto endAttrIter = parser->endAttributes(); - const auto parentAttrIter = parser->findAttribute(u"", u"parent"); - if (parentAttrIter != endAttrIter) { - std::string errStr; - if (!parseStyleParentReference(parentAttrIter->value, &style->parent, &errStr)) { - mLogger.error(source.line) << errStr << "." << std::endl; - return false; - } + Maybe<StringPiece16> maybeParent = findAttribute(parser, u"parent"); + if (maybeParent) { + // If the parent is empty, we don't have a parent, but we also don't infer either. + if (!maybeParent.value().empty()) { + std::string errStr; + style->parent = ResourceUtils::parseStyleParentReference(maybeParent.value(), &errStr); + if (!style->parent) { + mDiag->error(DiagMessage(source) << errStr); + return false; + } - if (!style->parent.name.package.empty()) { - // Try to interpret the package name as an alias. These take precedence. - parser->applyPackageAlias(&style->parent.name.package, mTable->getPackage()); - } else { - // If no package is specified, this can not be an alias and is the local package. - style->parent.name.package = mTable->getPackage(); + if (Maybe<ResourceName> transformedName = + parser->transformPackage(style->parent.value().name.value(), u"")) { + style->parent.value().name = std::move(transformedName); + } } + } else { // No parent was specified, so try inferring it from the style name. std::u16string styleName = resourceName.entry.toString(); size_t pos = styleName.find_last_of(u'.'); if (pos != std::string::npos) { style->parentInferred = true; - style->parent.name.package = mTable->getPackage(); - style->parent.name.type = ResourceType::kStyle; - style->parent.name.entry = styleName.substr(0, pos); + style->parent = Reference(ResourceName{ + {}, ResourceType::kStyle, styleName.substr(0, pos) }); } } - bool success = true; - while (XmlPullParser::isGoodEvent(parser->next())) { + bool error = false; + std::u16string comment; + const size_t depth = parser->getDepth(); + while (XmlPullParser::nextChildNode(parser, depth)) { if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + // Skip text and comments. continue; } - ScopedXmlPullParser childParser(parser); - const std::u16string& name = childParser.getElementName(); - if (name == u"item") { - success &= parseUntypedItem(&childParser, *style); + const std::u16string& elementNamespace = parser->getElementNamespace(); + const std::u16string& elementName = parser->getElementName(); + if (elementNamespace == u"" && elementName == u"item") { + error |= !parseStyleItem(parser, style.get()); + + } else if (elementNamespace.empty() && + (elementName == u"skip" || elementName == u"eat-comment")) { + comment = u""; + } else { - mLogger.error(childParser.getLineNumber()) - << "unexpected tag <" - << name - << "> in <style> resource." - << std::endl; - success = false; + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << ":" << elementName << ">"); + error = true; } } - if (!success) { + if (error) { return false; } - - return mTable->addResource(resourceName, mConfig, source, std::move(style)); + return mTable->addResource(resourceName, mConfig, source, std::move(style), + mDiag); } bool ResourceParser::parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName, uint32_t typeMask) { - const SourceLine source = mSource.line(parser->getLineNumber()); + const Source source = mSource.withLine(parser->getLineNumber()); std::unique_ptr<Array> array = util::make_unique<Array>(); + std::u16string comment; bool error = false; - while (XmlPullParser::isGoodEvent(parser->next())) { + const size_t depth = parser->getDepth(); + while (XmlPullParser::nextChildNode(parser, depth)) { if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + // Skip text and comments. continue; } - ScopedXmlPullParser childParser(parser); + const Source itemSource = mSource.withLine(parser->getLineNumber()); + const std::u16string& elementNamespace = parser->getElementNamespace(); + const std::u16string& elementName = parser->getElementName(); + if (elementNamespace.empty() && elementName == u"item") { + std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString); + if (!item) { + mDiag->error(DiagMessage(itemSource) << "could not parse array item"); + error = true; + continue; + } + array->items.emplace_back(std::move(item)); - if (childParser.getElementName() != u"item") { - mLogger.error(childParser.getLineNumber()) - << "unexpected tag <" - << childParser.getElementName() - << "> in <array> resource." - << std::endl; - error = true; - continue; - } + } else if (elementNamespace.empty() && + (elementName == u"skip" || elementName == u"eat-comment")) { + comment = u""; - std::unique_ptr<Item> item = parseXml(&childParser, typeMask, kNoRawString); - if (!item) { + } else { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "unknown tag <" << elementNamespace << ":" << elementName << ">"); error = true; - continue; } - array->items.emplace_back(std::move(item)); } if (error) { return false; } - - return mTable->addResource(resourceName, mConfig, source, std::move(array)); + return mTable->addResource(resourceName, mConfig, source, std::move(array), + mDiag); } bool ResourceParser::parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); + const Source source = mSource.withLine(parser->getLineNumber()); std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - bool success = true; - while (XmlPullParser::isGoodEvent(parser->next())) { + std::u16string comment; + bool error = false; + const size_t depth = parser->getDepth(); + while (XmlPullParser::nextChildNode(parser, depth)) { if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + // Skip text and comments. continue; } - ScopedXmlPullParser childParser(parser); + const std::u16string& elementNamespace = parser->getElementNamespace(); + const std::u16string& elementName = parser->getElementName(); + if (elementNamespace.empty() && elementName == u"item") { + const auto endAttrIter = parser->endAttributes(); + auto attrIter = parser->findAttribute(u"", u"quantity"); + if (attrIter == endAttrIter || attrIter->value.empty()) { + mDiag->error(DiagMessage(source) << "<item> in <plurals> requires attribute " + << "'quantity'"); + error = true; + continue; + } - if (!childParser.getElementNamespace().empty() || - childParser.getElementName() != u"item") { - success = false; - continue; - } + StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value); + size_t index = 0; + if (trimmedQuantity == u"zero") { + index = Plural::Zero; + } else if (trimmedQuantity == u"one") { + index = Plural::One; + } else if (trimmedQuantity == u"two") { + index = Plural::Two; + } else if (trimmedQuantity == u"few") { + index = Plural::Few; + } else if (trimmedQuantity == u"many") { + index = Plural::Many; + } else if (trimmedQuantity == u"other") { + index = Plural::Other; + } else { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "<item> in <plural> has invalid value '" << trimmedQuantity + << "' for attribute 'quantity'"); + error = true; + continue; + } - const auto endAttrIter = childParser.endAttributes(); - auto attrIter = childParser.findAttribute(u"", u"quantity"); - if (attrIter == endAttrIter || attrIter->value.empty()) { - mLogger.error(childParser.getLineNumber()) - << "<item> in <plurals> requires attribute 'quantity'." - << std::endl; - success = false; - continue; - } + if (plural->values[index]) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "duplicate quantity '" << trimmedQuantity << "'"); + error = true; + continue; + } - StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value); - size_t index = 0; - if (trimmedQuantity == u"zero") { - index = Plural::Zero; - } else if (trimmedQuantity == u"one") { - index = Plural::One; - } else if (trimmedQuantity == u"two") { - index = Plural::Two; - } else if (trimmedQuantity == u"few") { - index = Plural::Few; - } else if (trimmedQuantity == u"many") { - index = Plural::Many; - } else if (trimmedQuantity == u"other") { - index = Plural::Other; + if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING, + kNoRawString))) { + error = true; + } + } else if (elementNamespace.empty() && + (elementName == u"skip" || elementName == u"eat-comment")) { + comment = u""; } else { - mLogger.error(childParser.getLineNumber()) - << "<item> in <plural> has invalid value '" - << trimmedQuantity - << "' for attribute 'quantity'." - << std::endl; - success = false; - continue; - } - - if (plural->values[index]) { - mLogger.error(childParser.getLineNumber()) - << "duplicate quantity '" - << trimmedQuantity - << "'." - << std::endl; - success = false; - continue; - } - - if (!(plural->values[index] = parseXml(&childParser, android::ResTable_map::TYPE_STRING, - kNoRawString))) { - success = false; + mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":" + << elementName << ">"); + error = true; } } - if (!success) { + if (error) { return false; } - - return mTable->addResource(resourceName, mConfig, source, std::move(plural)); + return mTable->addResource(resourceName, mConfig, source, std::move(plural), mDiag); } bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, const ResourceNameRef& resourceName) { - const SourceLine source = mSource.line(parser->getLineNumber()); + const Source source = mSource.withLine(parser->getLineNumber()); std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); - bool success = true; - while (XmlPullParser::isGoodEvent(parser->next())) { + std::u16string comment; + bool error = false; + const size_t depth = parser->getDepth(); + while (XmlPullParser::nextChildNode(parser, depth)) { if (parser->getEvent() != XmlPullParser::Event::kStartElement) { + // Ignore text and comments. continue; } - ScopedXmlPullParser childParser(parser); - - const std::u16string& elementName = childParser.getElementName(); - if (elementName == u"attr") { - const auto endAttrIter = childParser.endAttributes(); - auto attrIter = childParser.findAttribute(u"", u"name"); + const std::u16string& elementNamespace = parser->getElementNamespace(); + const std::u16string& elementName = parser->getElementName(); + if (elementNamespace.empty() && elementName == u"attr") { + const auto endAttrIter = parser->endAttributes(); + auto attrIter = parser->findAttribute(u"", u"name"); if (attrIter == endAttrIter || attrIter->value.empty()) { - mLogger.error(childParser.getLineNumber()) - << "<attr> tag must have a 'name' attribute." - << std::endl; - success = false; + mDiag->error(DiagMessage(source) << "<attr> tag must have a 'name' attribute"); + error = true; continue; } // Copy because our iterator will be invalidated. - ResourceName attrResourceName = { - mTable->getPackage(), - ResourceType::kAttr, - attrIter->value - }; + ResourceName attrResourceName = { {}, ResourceType::kAttr, attrIter->value }; - std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, &attrResourceName, true); + std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &attrResourceName, true); if (!attr) { - success = false; + error = true; continue; } styleable->entries.emplace_back(attrResourceName); - // The package may have been corrected to another package. If that is so, - // we don't add the declaration. - if (attrResourceName.package == mTable->getPackage()) { - success &= mTable->addResource(attrResourceName, mConfig, - mSource.line(childParser.getLineNumber()), - std::move(attr)); - } + // Add the attribute to the resource table. Since it is weakly defined, + // it won't collide. + error |= !mTable->addResource(attrResourceName, mConfig, + mSource.withLine(parser->getLineNumber()), + std::move(attr), mDiag); + + } else if (elementNamespace.empty() && + (elementName == u"skip" || elementName == u"eat-comment")) { + comment = u""; - } else if (elementName != u"eat-comment" && elementName != u"skip") { - mLogger.error(childParser.getLineNumber()) - << "<" - << elementName - << "> is not allowed inside <declare-styleable>." - << std::endl; - success = false; + } else { + mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":" + << elementName << ">"); + error = true; } } - if (!success) { + if (error) { return false; } - - return mTable->addResource(resourceName, mConfig, source, std::move(styleable)); + return mTable->addResource(resourceName, mConfig, source, std::move(styleable), mDiag); } } // namespace aapt diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 7618999f0023..514e55852407 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -18,14 +18,15 @@ #define AAPT_RESOURCE_PARSER_H #include "ConfigDescription.h" -#include "Logger.h" +#include "Diagnostics.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "StringPiece.h" #include "StringPool.h" #include "XmlPullParser.h" -#include <istream> +#include "util/Maybe.h" +#include "util/StringPiece.h" + #include <memory> namespace aapt { @@ -35,118 +36,12 @@ namespace aapt { */ class ResourceParser { public: - /* - * Extracts the package, type, and name from a string of the format: - * - * [package:]type/name - * - * where the package can be empty. Validation must be performed on each - * individual extracted piece to verify that the pieces are valid. - */ - static void extractResourceName(const StringPiece16& str, StringPiece16* outPackage, - StringPiece16* outType, StringPiece16* outEntry); - - /* - * Returns true if the string was parsed as a reference (@[+][package:]type/name), with - * `outReference` set to the parsed reference. - * - * If '+' was present in the reference, `outCreate` is set to true. - * If '*' was present in the reference, `outPrivate` is set to true. - */ - static bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference, - bool* outCreate, bool* outPrivate); - - /* - * Returns true if the string was parsed as an attribute reference (?[package:]type/name), - * with `outReference` set to the parsed reference. - */ - static bool tryParseAttributeReference(const StringPiece16& str, - ResourceNameRef* outReference); - - /* - * Returns true if the string `str` was parsed as a valid reference to a style. - * The format for a style parent is slightly more flexible than a normal reference: - * - * @[package:]style/<entry> or - * ?[package:]style/<entry> or - * <package>:[style/]<entry> - */ - static bool parseStyleParentReference(const StringPiece16& str, Reference* outReference, - std::string* outError); - - /* - * Returns a Reference object if the string was parsed as a resource or attribute reference, - * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if - * the '+' was present in the string. - */ - static std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, - bool* outCreate); - - /* - * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed - * as one. - */ - static std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str); - - /* - * Returns a BinaryPrimitve object representing a color if the string was parsed - * as one. - */ - static std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str); - - /* - * Returns a BinaryPrimitve object representing a boolean if the string was parsed - * as one. - */ - static std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str); - - /* - * Returns a BinaryPrimitve object representing an integer if the string was parsed - * as one. - */ - static std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str); - - /* - * Returns a BinaryPrimitve object representing a floating point number - * (float, dimension, etc) if the string was parsed as one. - */ - static std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str); - - /* - * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed - * as one. - */ - static std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute& enumAttr, - const StringPiece16& str); - - /* - * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed - * as one. - */ - static std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute& enumAttr, - const StringPiece16& str); - /* - * Try to convert a string to an Item for the given attribute. The attribute will - * restrict what values the string can be converted to. - * The callback function onCreateReference is called when the parsed item is a - * reference to an ID that must be created (@+id/foo). - */ - static std::unique_ptr<Item> parseItemForAttribute( - const StringPiece16& value, const Attribute& attr, - std::function<void(const ResourceName&)> onCreateReference = {}); - - static std::unique_ptr<Item> parseItemForAttribute( - const StringPiece16& value, uint32_t typeMask, - std::function<void(const ResourceName&)> onCreateReference = {}); - - static uint32_t androidTypeToAttributeTypeMask(uint16_t type); - - ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source, - const ConfigDescription& config, const std::shared_ptr<XmlPullParser>& parser); + ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, + const ConfigDescription& config); ResourceParser(const ResourceParser&) = delete; // No copy. - bool parse(); + bool parse(XmlPullParser* parser); private: /* @@ -155,7 +50,7 @@ private: * contains the escaped and whitespace trimmed text, while `outRawString` * contains the unescaped text. Returns true on success. */ - bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,\ + bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString, StyleString* outStyleString); /* @@ -175,19 +70,17 @@ private: std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser, ResourceName* resourceName, bool weak); - bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag, - Attribute::Symbol* outSymbol); + Maybe<Attribute::Symbol> parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag); bool parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName); - bool parseUntypedItem(XmlPullParser* parser, Style& style); + bool parseStyleItem(XmlPullParser* parser, Style* style); bool parseDeclareStyleable(XmlPullParser* parser, const ResourceNameRef& resourceName); bool parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName, uint32_t typeMask); bool parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName); - std::shared_ptr<ResourceTable> mTable; + IDiagnostics* mDiag; + ResourceTable* mTable; Source mSource; ConfigDescription mConfig; - SourceLogger mLogger; - std::shared_ptr<XmlPullParser> mParser; }; } // namespace aapt diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index a93d0ff7a835..cb98afd18c5a 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -16,8 +16,11 @@ #include "ResourceParser.h" #include "ResourceTable.h" +#include "ResourceUtils.h" #include "ResourceValues.h" -#include "SourceXmlPullParser.h" +#include "XmlPullParser.h" + +#include "test/Context.h" #include <gtest/gtest.h> #include <sstream> @@ -27,156 +30,41 @@ namespace aapt { constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; -TEST(ResourceParserReferenceTest, ParseReferenceWithNoPackage) { - ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" }; - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceParser::tryParseReference(u"@color/foo", &actual, &create, &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_FALSE(privateRef); -} - -TEST(ResourceParserReferenceTest, ParseReferenceWithPackage) { - ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" }; - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceParser::tryParseReference(u"@android:color/foo", &actual, &create, - &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_FALSE(privateRef); -} - -TEST(ResourceParserReferenceTest, ParseReferenceWithSurroundingWhitespace) { - ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" }; - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceParser::tryParseReference(u"\t @android:color/foo\n \n\t", &actual, - &create, &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_FALSE(privateRef); -} - -TEST(ResourceParserReferenceTest, ParseAutoCreateIdReference) { - ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" }; - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceParser::tryParseReference(u"@+android:id/foo", &actual, &create, - &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_TRUE(create); - EXPECT_FALSE(privateRef); -} - -TEST(ResourceParserReferenceTest, ParsePrivateReference) { - ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" }; - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceParser::tryParseReference(u"@*android:id/foo", &actual, &create, - &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_TRUE(privateRef); -} - -TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) { - bool create = false; - bool privateRef = false; - ResourceNameRef actual; - EXPECT_FALSE(ResourceParser::tryParseReference(u"@+android:color/foo", &actual, &create, - &privateRef)); -} - -TEST(ResourceParserReferenceTest, ParseStyleParentReference) { - Reference ref; - std::string errStr; - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); - - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); - - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); - - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); - - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); - - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); - - EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr)); - EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); +TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::stringstream input(kXmlPreamble); + input << "<attr name=\"foo\"/>" << std::endl; + ResourceTable table; + ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {}); + XmlPullParser xmlParser(input); + ASSERT_FALSE(parser.parse(&xmlParser)); } struct ResourceParserTest : public ::testing::Test { - virtual void SetUp() override { - mTable = std::make_shared<ResourceTable>(); - mTable->setPackage(u"android"); + ResourceTable mTable; + std::unique_ptr<IAaptContext> mContext; + + void SetUp() override { + mContext = test::ContextBuilder().build(); } ::testing::AssertionResult testParse(const StringPiece& str) { std::stringstream input(kXmlPreamble); input << "<resources>\n" << str << "\n</resources>" << std::endl; - ResourceParser parser(mTable, Source{ "test" }, {}, - std::make_shared<SourceXmlPullParser>(input)); - if (parser.parse()) { + ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {}); + XmlPullParser xmlParser(input); + if (parser.parse(&xmlParser)) { return ::testing::AssertionSuccess(); } return ::testing::AssertionFailure(); } - - template <typename T> - const T* findResource(const ResourceNameRef& name, const ConfigDescription& config) { - using std::begin; - using std::end; - - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = mTable->findResource(name); - if (!type || !entry) { - return nullptr; - } - - for (const auto& configValue : entry->values) { - if (configValue.config == config) { - return dynamic_cast<const T*>(configValue.value.get()); - } - } - return nullptr; - } - - template <typename T> - const T* findResource(const ResourceNameRef& name) { - return findResource<T>(name, {}); - } - - std::shared_ptr<ResourceTable> mTable; }; -TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) { - std::stringstream input(kXmlPreamble); - input << "<attr name=\"foo\"/>" << std::endl; - ResourceParser parser(mTable, {}, {}, std::make_shared<SourceXmlPullParser>(input)); - ASSERT_FALSE(parser.parse()); -} - TEST_F(ResourceParserTest, ParseQuotedString) { std::string input = "<string name=\"foo\"> \" hey there \" </string>"; ASSERT_TRUE(testParse(input)); - const String* str = findResource<String>(ResourceName{ - u"android", ResourceType::kString, u"foo"}); + String* str = test::getValue<String>(&mTable, u"@string/foo"); ASSERT_NE(nullptr, str); EXPECT_EQ(std::u16string(u" hey there "), *str->value); } @@ -185,12 +73,22 @@ TEST_F(ResourceParserTest, ParseEscapedString) { std::string input = "<string name=\"foo\">\\?123</string>"; ASSERT_TRUE(testParse(input)); - const String* str = findResource<String>(ResourceName{ - u"android", ResourceType::kString, u"foo" }); + String* str = test::getValue<String>(&mTable, u"@string/foo"); ASSERT_NE(nullptr, str); EXPECT_EQ(std::u16string(u"?123"), *str->value); } +TEST_F(ResourceParserTest, IgnoreXliffTags) { + std::string input = "<string name=\"foo\" \n" + " xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n" + " There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>"; + ASSERT_TRUE(testParse(input)); + + String* str = test::getValue<String>(&mTable, u"@string/foo"); + ASSERT_NE(nullptr, str); + EXPECT_EQ(StringPiece16(u"There are %1$d apples"), StringPiece16(*str->value)); +} + TEST_F(ResourceParserTest, ParseNull) { std::string input = "<integer name=\"foo\">@null</integer>"; ASSERT_TRUE(testParse(input)); @@ -199,8 +97,7 @@ TEST_F(ResourceParserTest, ParseNull) { // a non-existing value, and this causes problems in styles when trying to resolve // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE // with a data value of 0. - const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{ - u"android", ResourceType::kInteger, u"foo" }); + BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo"); ASSERT_NE(nullptr, integer); EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType); EXPECT_EQ(0u, integer->value.data); @@ -210,8 +107,7 @@ TEST_F(ResourceParserTest, ParseEmpty) { std::string input = "<integer name=\"foo\">@empty</integer>"; ASSERT_TRUE(testParse(input)); - const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{ - u"android", ResourceType::kInteger, u"foo" }); + BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, u"@integer/foo"); ASSERT_NE(nullptr, integer); EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); @@ -222,14 +118,12 @@ TEST_F(ResourceParserTest, ParseAttr) { "<attr name=\"bar\"/>"; ASSERT_TRUE(testParse(input)); - const Attribute* attr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"foo"}); - EXPECT_NE(nullptr, attr); + Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); + ASSERT_NE(nullptr, attr); EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); - attr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"bar"}); - EXPECT_NE(nullptr, attr); + attr = test::getValue<Attribute>(&mTable, u"@attr/bar"); + ASSERT_NE(nullptr, attr); EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); } @@ -240,8 +134,7 @@ TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { "<attr name=\"foo\" format=\"string\"/>"; ASSERT_TRUE(testParse(input)); - const Attribute* attr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"foo"}); + Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); ASSERT_NE(nullptr, attr); EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); } @@ -255,8 +148,7 @@ TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { "</declare-styleable>"; ASSERT_TRUE(testParse(input)); - const Attribute* attr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"foo"}); + Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo"); ASSERT_NE(nullptr, attr); EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask); } @@ -269,19 +161,21 @@ TEST_F(ResourceParserTest, ParseEnumAttr) { "</attr>"; ASSERT_TRUE(testParse(input)); - const Attribute* enumAttr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"foo"}); + Attribute* enumAttr = test::getValue<Attribute>(&mTable, u"@attr/foo"); ASSERT_NE(enumAttr, nullptr); EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM); ASSERT_EQ(enumAttr->symbols.size(), 3u); - EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar"); + AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name); + EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, u"bar"); EXPECT_EQ(enumAttr->symbols[0].value, 0u); - EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat"); + AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name); + EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, u"bat"); EXPECT_EQ(enumAttr->symbols[1].value, 1u); - EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz"); + AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name); + EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, u"baz"); EXPECT_EQ(enumAttr->symbols[2].value, 2u); } @@ -293,23 +187,25 @@ TEST_F(ResourceParserTest, ParseFlagAttr) { "</attr>"; ASSERT_TRUE(testParse(input)); - const Attribute* flagAttr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"foo"}); + Attribute* flagAttr = test::getValue<Attribute>(&mTable, u"@attr/foo"); ASSERT_NE(flagAttr, nullptr); EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS); ASSERT_EQ(flagAttr->symbols.size(), 3u); - EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar"); + AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name); + EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, u"bar"); EXPECT_EQ(flagAttr->symbols[0].value, 0u); - EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat"); + AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name); + EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, u"bat"); EXPECT_EQ(flagAttr->symbols[1].value, 1u); - EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz"); + AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name); + EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, u"baz"); EXPECT_EQ(flagAttr->symbols[2].value, 2u); - std::unique_ptr<BinaryPrimitive> flagValue = - ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat"); + std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr, + u"baz|bat"); ASSERT_NE(flagValue, nullptr); EXPECT_EQ(flagValue->value.data, 1u | 2u); } @@ -331,28 +227,32 @@ TEST_F(ResourceParserTest, ParseStyle) { "</style>"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource<Style>(ResourceName{ - u"android", ResourceType::kStyle, u"foo"}); + Style* style = test::getValue<Style>(&mTable, u"@style/foo"); ASSERT_NE(style, nullptr); - EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"fu"), style->parent.name); - ASSERT_EQ(style->entries.size(), 3u); - - EXPECT_EQ(style->entries[0].key.name, - (ResourceName{ u"android", ResourceType::kAttr, u"bar" })); - EXPECT_EQ(style->entries[1].key.name, - (ResourceName{ u"android", ResourceType::kAttr, u"bat" })); - EXPECT_EQ(style->entries[2].key.name, - (ResourceName{ u"android", ResourceType::kAttr, u"baz" })); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(test::parseNameOrDie(u"@style/fu"), style->parent.value().name.value()); + ASSERT_EQ(3u, style->entries.size()); + + AAPT_ASSERT_TRUE(style->entries[0].key.name); + EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), style->entries[0].key.name.value()); + + AAPT_ASSERT_TRUE(style->entries[1].key.name); + EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), style->entries[1].key.name.value()); + + AAPT_ASSERT_TRUE(style->entries[2].key.name); + EXPECT_EQ(test::parseNameOrDie(u"@attr/baz"), style->entries[2].key.name.value()); } TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource<Style>( - ResourceName{ u"android", ResourceType::kStyle, u"foo" }); + Style* style = test::getValue<Style>(&mTable, u"@style/foo"); ASSERT_NE(style, nullptr); - EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(test::parseNameOrDie(u"@com.app:style/Theme"), style->parent.value().name.value()); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { @@ -360,10 +260,11 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { " name=\"foo\" parent=\"app:Theme\"/>"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource<Style>(ResourceName{ - u"android", ResourceType::kStyle, u"foo" }); + Style* style = test::getValue<Style>(&mTable, u"@style/foo"); ASSERT_NE(style, nullptr); - EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kStyle, u"Theme"), style->parent.name); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(test::parseNameOrDie(u"@android:style/Theme"), style->parent.value().name.value()); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { @@ -373,22 +274,21 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { "</style>"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource<Style>(ResourceName{ - u"android", ResourceType::kStyle, u"foo" }); + Style* style = test::getValue<Style>(&mTable, u"@style/foo"); ASSERT_NE(style, nullptr); ASSERT_EQ(1u, style->entries.size()); - EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kAttr, u"bar"), - style->entries[0].key.name); + EXPECT_EQ(test::parseNameOrDie(u"@android:attr/bar"), style->entries[0].key.name.value()); } TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { std::string input = "<style name=\"foo.bar\"/>"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource<Style>(ResourceName{ - u"android", ResourceType::kStyle, u"foo.bar" }); + Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar"); ASSERT_NE(style, nullptr); - EXPECT_EQ(style->parent.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie(u"@style/foo")); EXPECT_TRUE(style->parentInferred); } @@ -396,10 +296,9 @@ TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAtt std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource<Style>(ResourceName{ - u"android", ResourceType::kStyle, u"foo.bar" }); + Style* style = test::getValue<Style>(&mTable, u"@style/foo.bar"); ASSERT_NE(style, nullptr); - EXPECT_FALSE(style->parent.name.isValid()); + AAPT_EXPECT_FALSE(style->parent); EXPECT_FALSE(style->parentInferred); } @@ -407,7 +306,7 @@ TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { std::string input = "<string name=\"foo\">@+id/bar</string>"; ASSERT_TRUE(testParse(input)); - const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"}); + Id* id = test::getValue<Id>(&mTable, u"@id/bar"); ASSERT_NE(id, nullptr); } @@ -418,22 +317,20 @@ TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { "</declare-styleable>"; ASSERT_TRUE(testParse(input)); - const Attribute* attr = findResource<Attribute>(ResourceName{ - u"android", ResourceType::kAttr, u"bar"}); + Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/bar"); ASSERT_NE(attr, nullptr); EXPECT_TRUE(attr->isWeak()); - attr = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"}); + attr = test::getValue<Attribute>(&mTable, u"@attr/bat"); ASSERT_NE(attr, nullptr); EXPECT_TRUE(attr->isWeak()); - const Styleable* styleable = findResource<Styleable>(ResourceName{ - u"android", ResourceType::kStyleable, u"foo" }); + Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo"); ASSERT_NE(styleable, nullptr); ASSERT_EQ(2u, styleable->entries.size()); - EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bar"}), styleable->entries[0].name); - EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bat"}), styleable->entries[1].name); + EXPECT_EQ(test::parseNameOrDie(u"@attr/bar"), styleable->entries[0].name.value()); + EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value()); } TEST_F(ResourceParserTest, ParseArray) { @@ -444,14 +341,13 @@ TEST_F(ResourceParserTest, ParseArray) { "</array>"; ASSERT_TRUE(testParse(input)); - const Array* array = findResource<Array>(ResourceName{ - u"android", ResourceType::kArray, u"foo" }); + Array* array = test::getValue<Array>(&mTable, u"@array/foo"); ASSERT_NE(array, nullptr); ASSERT_EQ(3u, array->items.size()); - EXPECT_NE(nullptr, dynamic_cast<const Reference*>(array->items[0].get())); - EXPECT_NE(nullptr, dynamic_cast<const String*>(array->items[1].get())); - EXPECT_NE(nullptr, dynamic_cast<const BinaryPrimitive*>(array->items[2].get())); + EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get())); + EXPECT_NE(nullptr, valueCast<String>(array->items[1].get())); + EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get())); } TEST_F(ResourceParserTest, ParsePlural) { @@ -467,11 +363,11 @@ TEST_F(ResourceParserTest, ParseCommentsWithResource) { "<string name=\"foo\">Hi</string>"; ASSERT_TRUE(testParse(input)); - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = mTable->findResource(ResourceName{ - u"android", ResourceType::kString, u"foo"}); - ASSERT_NE(type, nullptr); + Maybe<ResourceTable::SearchResult> result = mTable.findResource( + test::parseNameOrDie(u"@string/foo")); + AAPT_ASSERT_TRUE(result); + + ResourceEntry* entry = result.value().entry; ASSERT_NE(entry, nullptr); ASSERT_FALSE(entry->values.empty()); EXPECT_EQ(entry->values.front().comment, u"This is a comment"); @@ -485,7 +381,7 @@ TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { std::string input = "<public type=\"id\" name=\"foo\"/>"; ASSERT_TRUE(testParse(input)); - const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" }); + Id* id = test::getValue<Id>(&mTable, u"@id/foo"); ASSERT_NE(nullptr, id); } diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index c93ecc768022..a1e7d36d91d9 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -15,11 +15,11 @@ */ #include "ConfigDescription.h" -#include "Logger.h" #include "NameMangler.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "Util.h" +#include "ValueVisitor.h" +#include "util/Util.h" #include <algorithm> #include <androidfw/ResourceTypes.h> @@ -37,65 +37,109 @@ static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, Resource return lhs->type < rhs; } -static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) { +template <typename T> +static bool lessThanStructWithName(const std::unique_ptr<T>& lhs, + const StringPiece16& rhs) { return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0; } -ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) { - // Make sure attrs always have type ID 1. - findOrCreateType(ResourceType::kAttr)->typeId = 1; +ResourceTablePackage* ResourceTable::findPackage(const StringPiece16& name) { + const auto last = packages.end(); + auto iter = std::lower_bound(packages.begin(), last, name, + lessThanStructWithName<ResourceTablePackage>); + if (iter != last && name == (*iter)->name) { + return iter->get(); + } + return nullptr; } -std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) { - auto last = mTypes.end(); - auto iter = std::lower_bound(mTypes.begin(), last, type, lessThanType); - if (iter != last) { - if ((*iter)->type == type) { - return *iter; +ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) { + for (auto& package : packages) { + if (package->id && package->id.value() == id) { + return package.get(); } } - return *mTypes.emplace(iter, new ResourceTableType{ type }); + return nullptr; } -std::unique_ptr<ResourceEntry>& ResourceTable::findOrCreateEntry( - std::unique_ptr<ResourceTableType>& type, const StringPiece16& name) { - auto last = type->entries.end(); - auto iter = std::lower_bound(type->entries.begin(), last, name, lessThanEntry); - if (iter != last) { - if (name == (*iter)->name) { - return *iter; - } +ResourceTablePackage* ResourceTable::createPackage(const StringPiece16& name, uint8_t id) { + ResourceTablePackage* package = findOrCreatePackage(name); + if (!package->id) { + package->id = id; + return package; } - return *type->entries.emplace(iter, new ResourceEntry{ name }); + + if (package->id.value() == id) { + return package; + } + return nullptr; } -struct IsAttributeVisitor : ConstValueVisitor { - bool isAttribute = false; +ResourceTablePackage* ResourceTable::findOrCreatePackage(const StringPiece16& name) { + const auto last = packages.end(); + auto iter = std::lower_bound(packages.begin(), last, name, + lessThanStructWithName<ResourceTablePackage>); + if (iter != last && name == (*iter)->name) { + return iter->get(); + } - void visit(const Attribute&, ValueVisitorArgs&) override { - isAttribute = true; + std::unique_ptr<ResourceTablePackage> newPackage = util::make_unique<ResourceTablePackage>(); + newPackage->name = name.toString(); + return packages.emplace(iter, std::move(newPackage))->get(); +} + +ResourceTableType* ResourceTablePackage::findType(ResourceType type) { + const auto last = types.end(); + auto iter = std::lower_bound(types.begin(), last, type, lessThanType); + if (iter != last && (*iter)->type == type) { + return iter->get(); } + return nullptr; +} - operator bool() { - return isAttribute; +ResourceTableType* ResourceTablePackage::findOrCreateType(ResourceType type) { + const auto last = types.end(); + auto iter = std::lower_bound(types.begin(), last, type, lessThanType); + if (iter != last && (*iter)->type == type) { + return iter->get(); } -}; + return types.emplace(iter, new ResourceTableType{ type })->get(); +} + +ResourceEntry* ResourceTableType::findEntry(const StringPiece16& name) { + const auto last = entries.end(); + auto iter = std::lower_bound(entries.begin(), last, name, + lessThanStructWithName<ResourceEntry>); + if (iter != last && name == (*iter)->name) { + return iter->get(); + } + return nullptr; +} + +ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece16& name) { + auto last = entries.end(); + auto iter = std::lower_bound(entries.begin(), last, name, + lessThanStructWithName<ResourceEntry>); + if (iter != last && name == (*iter)->name) { + return iter->get(); + } + return entries.emplace(iter, new ResourceEntry{ name })->get(); +} /** * The default handler for collisions. A return value of -1 means keep the * existing value, 0 means fail, and +1 means take the incoming value. */ -static int defaultCollisionHandler(const Value& existing, const Value& incoming) { - IsAttributeVisitor existingIsAttr, incomingIsAttr; - existing.accept(existingIsAttr, {}); - incoming.accept(incomingIsAttr, {}); +int ResourceTable::resolveValueCollision(Value* existing, Value* incoming) { + Attribute* existingAttr = valueCast<Attribute>(existing); + Attribute* incomingAttr = valueCast<Attribute>(incoming); - if (!incomingIsAttr) { - if (incoming.isWeak()) { + if (!incomingAttr) { + if (incoming->isWeak()) { // We're trying to add a weak resource but a resource // already exists. Keep the existing. return -1; - } else if (existing.isWeak()) { + } else if (existing->isWeak()) { // Override the weak resource with the new strong resource. return 1; } @@ -104,8 +148,8 @@ static int defaultCollisionHandler(const Value& existing, const Value& incoming) return 0; } - if (!existingIsAttr) { - if (existing.isWeak()) { + if (!existingAttr) { + if (existing->isWeak()) { // The existing value is not an attribute and it is weak, // so take the incoming attribute value. return 1; @@ -115,27 +159,27 @@ static int defaultCollisionHandler(const Value& existing, const Value& incoming) return 0; } + assert(incomingAttr && existingAttr); + // // Attribute specific handling. At this point we know both // values are attributes. Since we can declare and define // attributes all-over, we do special handling to see // which definition sticks. // - const Attribute& existingAttr = static_cast<const Attribute&>(existing); - const Attribute& incomingAttr = static_cast<const Attribute&>(incoming); - if (existingAttr.typeMask == incomingAttr.typeMask) { + if (existingAttr->typeMask == incomingAttr->typeMask) { // The two attributes are both DECLs, but they are plain attributes // with the same formats. // Keep the strongest one. - return existingAttr.isWeak() ? 1 : -1; + return existingAttr->isWeak() ? 1 : -1; } - if (existingAttr.isWeak() && existingAttr.typeMask == android::ResTable_map::TYPE_ANY) { + if (existingAttr->isWeak() && existingAttr->typeMask == android::ResTable_map::TYPE_ANY) { // Any incoming attribute is better than this. return 1; } - if (incomingAttr.isWeak() && incomingAttr.typeMask == android::ResTable_map::TYPE_ANY) { + if (incomingAttr->isWeak() && incomingAttr->typeMask == android::ResTable_map::TYPE_ANY) { // The incoming attribute may be a USE instead of a DECL. // Keep the existing attribute. return -1; @@ -147,180 +191,183 @@ static constexpr const char16_t* kValidNameChars = u"._-"; static constexpr const char16_t* kValidNameMangledChars = u"._-$"; bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config, - const SourceLine& source, std::unique_ptr<Value> value) { - return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars); + const Source& source, std::unique_ptr<Value> value, + IDiagnostics* diag) { + return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars, + diag); } bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId, - const ConfigDescription& config, const SourceLine& source, - std::unique_ptr<Value> value) { - return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars); + const ConfigDescription& config, const Source& source, + std::unique_ptr<Value> value, IDiagnostics* diag) { + return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars, diag); +} + +bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config, + const Source& source, const StringPiece16& path, + IDiagnostics* diag) { + return addResourceImpl(name, ResourceId{}, config, source, + util::make_unique<FileReference>(stringPool.makeRef(path)), + kValidNameChars, diag); } bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config, - const SourceLine& source, - std::unique_ptr<Value> value) { + const Source& source, + std::unique_ptr<Value> value, + IDiagnostics* diag) { return addResourceImpl(name, ResourceId{}, config, source, std::move(value), - kValidNameMangledChars); + kValidNameMangledChars, diag); } bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId, - const ConfigDescription& config, const SourceLine& source, - std::unique_ptr<Value> value, const char16_t* validChars) { - if (!name.package.empty() && name.package != mPackage) { - Logger::error(source) - << "resource '" - << name - << "' has incompatible package. Must be '" - << mPackage - << "'." - << std::endl; + const ConfigDescription& config, const Source& source, + std::unique_ptr<Value> value, const char16_t* validChars, + IDiagnostics* diag) { + auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars); + if (badCharIter != name.entry.end()) { + diag->error(DiagMessage(source) + << "resource '" + << name + << "' has invalid entry name '" + << name.entry + << "'. Invalid character '" + << StringPiece16(badCharIter, 1) + << "'"); return false; } - auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars); - if (badCharIter != name.entry.end()) { - Logger::error(source) - << "resource '" - << name - << "' has invalid entry name '" - << name.entry - << "'. Invalid character '" - << StringPiece16(badCharIter, 1) - << "'." - << std::endl; + ResourceTablePackage* package = findOrCreatePackage(name.package); + if (resId.isValid() && package->id && package->id.value() != resId.packageId()) { + diag->error(DiagMessage(source) + << "trying to add resource '" + << name + << "' with ID " + << resId + << " but package '" + << package->name + << "' already has ID " + << std::hex << (int) package->id.value() << std::dec); return false; } - std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type); - if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId && - type->typeId != resId.typeId()) { - Logger::error(source) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but type '" - << type->type - << "' already has ID " - << std::hex << type->typeId << std::dec - << "." - << std::endl; + ResourceTableType* type = package->findOrCreateType(name.type); + if (resId.isValid() && type->id && type->id.value() != resId.typeId()) { + diag->error(DiagMessage(source) + << "trying to add resource '" + << name + << "' with ID " + << resId + << " but type '" + << type->type + << "' already has ID " + << std::hex << (int) type->id.value() << std::dec); return false; } - std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry); - if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId && - entry->entryId != resId.entryId()) { - Logger::error(source) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but resource already has ID " - << ResourceId(mPackageId, type->typeId, entry->entryId) - << "." - << std::endl; + ResourceEntry* entry = type->findOrCreateEntry(name.entry); + if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) { + diag->error(DiagMessage(source) + << "trying to add resource '" + << name + << "' with ID " + << resId + << " but resource already has ID " + << ResourceId(package->id.value(), type->id.value(), entry->id.value())); return false; } - const auto endIter = std::end(entry->values); - auto iter = std::lower_bound(std::begin(entry->values), endIter, config, compareConfigs); + const auto endIter = entry->values.end(); + auto iter = std::lower_bound(entry->values.begin(), endIter, config, compareConfigs); if (iter == endIter || iter->config != config) { // This resource did not exist before, add it. entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) }); } else { - int collisionResult = defaultCollisionHandler(*iter->value, *value); + int collisionResult = resolveValueCollision(iter->value.get(), value.get()); if (collisionResult > 0) { // Take the incoming value. *iter = ResourceConfigValue{ config, source, {}, std::move(value) }; } else if (collisionResult == 0) { - Logger::error(source) - << "duplicate value for resource '" << name << "' " - << "with config '" << iter->config << "'." - << std::endl; - - Logger::error(iter->source) - << "resource previously defined here." - << std::endl; + diag->error(DiagMessage(source) + << "duplicate value for resource '" << name << "' " + << "with config '" << iter->config << "'"); + diag->error(DiagMessage(iter->source) + << "resource previously defined here"); return false; } } if (resId.isValid()) { - type->typeId = resId.typeId(); - entry->entryId = resId.entryId(); + package->id = resId.packageId(); + type->id = resId.typeId(); + entry->id = resId.entryId(); } return true; } bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId, - const SourceLine& source) { - return markPublicImpl(name, resId, source, kValidNameChars); + const Source& source, IDiagnostics* diag) { + return markPublicImpl(name, resId, source, kValidNameChars, diag); } bool ResourceTable::markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId, - const SourceLine& source) { - return markPublicImpl(name, resId, source, kValidNameMangledChars); + const Source& source, IDiagnostics* diag) { + return markPublicImpl(name, resId, source, kValidNameMangledChars, diag); } bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId resId, - const SourceLine& source, const char16_t* validChars) { - if (!name.package.empty() && name.package != mPackage) { - Logger::error(source) - << "resource '" - << name - << "' has incompatible package. Must be '" - << mPackage - << "'." - << std::endl; + const Source& source, const char16_t* validChars, + IDiagnostics* diag) { + auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars); + if (badCharIter != name.entry.end()) { + diag->error(DiagMessage(source) + << "resource '" + << name + << "' has invalid entry name '" + << name.entry + << "'. Invalid character '" + << StringPiece16(badCharIter, 1) + << "'"); return false; } - auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars); - if (badCharIter != name.entry.end()) { - Logger::error(source) - << "resource '" - << name - << "' has invalid entry name '" - << name.entry - << "'. Invalid character '" - << StringPiece16(badCharIter, 1) - << "'." - << std::endl; + ResourceTablePackage* package = findOrCreatePackage(name.package); + if (resId.isValid() && package->id && package->id.value() != resId.packageId()) { + diag->error(DiagMessage(source) + << "trying to add resource '" + << name + << "' with ID " + << resId + << " but package '" + << package->name + << "' already has ID " + << std::hex << (int) package->id.value() << std::dec); return false; } - std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type); - if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId && - type->typeId != resId.typeId()) { - Logger::error(source) - << "trying to make resource '" - << name - << "' public with ID " - << resId - << " but type '" - << type->type - << "' already has ID " - << std::hex << type->typeId << std::dec - << "." - << std::endl; + ResourceTableType* type = package->findOrCreateType(name.type); + if (resId.isValid() && type->id && type->id.value() != resId.typeId()) { + diag->error(DiagMessage(source) + << "trying to add resource '" + << name + << "' with ID " + << resId + << " but type '" + << type->type + << "' already has ID " + << std::hex << (int) type->id.value() << std::dec); return false; } - std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry); - if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId && - entry->entryId != resId.entryId()) { - Logger::error(source) - << "trying to make resource '" - << name - << "' public with ID " - << resId - << " but resource already has ID " - << ResourceId(mPackageId, type->typeId, entry->entryId) - << "." - << std::endl; + ResourceEntry* entry = type->findOrCreateEntry(name.entry); + if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) { + diag->error(DiagMessage(source) + << "trying to add resource '" + << name + << "' with ID " + << resId + << " but resource already has ID " + << ResourceId(package->id.value(), type->id.value(), entry->id.value())); return false; } @@ -329,102 +376,30 @@ bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId entry->publicStatus.source = source; if (resId.isValid()) { - type->typeId = resId.typeId(); - entry->entryId = resId.entryId(); - } - return true; -} - -bool ResourceTable::merge(ResourceTable&& other) { - const bool mangleNames = mPackage != other.getPackage(); - std::u16string mangledName; - - for (auto& otherType : other) { - std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type); - if (otherType->publicStatus.isPublic) { - if (type->publicStatus.isPublic && type->typeId != otherType->typeId) { - Logger::error() << "can not merge type '" << type->type - << "': conflicting public IDs " - << "(" << type->typeId << " vs " << otherType->typeId << ")." - << std::endl; - return false; - } - type->publicStatus = std::move(otherType->publicStatus); - type->typeId = otherType->typeId; - } - - for (auto& otherEntry : otherType->entries) { - const std::u16string* nameToAdd = &otherEntry->name; - if (mangleNames) { - mangledName = otherEntry->name; - NameMangler::mangle(other.getPackage(), &mangledName); - nameToAdd = &mangledName; - } - - std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd); - if (otherEntry->publicStatus.isPublic) { - if (entry->publicStatus.isPublic && entry->entryId != otherEntry->entryId) { - Logger::error() << "can not merge entry '" << type->type << "/" << entry->name - << "': conflicting public IDs " - << "(" << entry->entryId << " vs " << entry->entryId << ")." - << std::endl; - return false; - } - entry->publicStatus = std::move(otherEntry->publicStatus); - entry->entryId = otherEntry->entryId; - } - - for (ResourceConfigValue& otherValue : otherEntry->values) { - auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), - otherValue.config, compareConfigs); - if (iter != entry->values.end() && iter->config == otherValue.config) { - int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value); - if (collisionResult > 0) { - // Take the incoming value. - iter->source = std::move(otherValue.source); - iter->comment = std::move(otherValue.comment); - iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)); - } else if (collisionResult == 0) { - ResourceNameRef resourceName = { mPackage, type->type, entry->name }; - Logger::error(otherValue.source) - << "resource '" << resourceName << "' has a conflicting value for " - << "configuration (" << otherValue.config << ")." - << std::endl; - Logger::note(iter->source) << "originally defined here." << std::endl; - return false; - } - } else { - entry->values.insert(iter, ResourceConfigValue{ - otherValue.config, - std::move(otherValue.source), - std::move(otherValue.comment), - std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)), - }); - } - } - } + package->id = resId.packageId(); + type->id = resId.typeId(); + entry->id = resId.entryId(); } return true; } -std::tuple<const ResourceTableType*, const ResourceEntry*> -ResourceTable::findResource(const ResourceNameRef& name) const { - if (name.package != mPackage) { +Maybe<ResourceTable::SearchResult> +ResourceTable::findResource(const ResourceNameRef& name) { + ResourceTablePackage* package = findPackage(name.package); + if (!package) { return {}; } - auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType); - if (iter == mTypes.end() || (*iter)->type != name.type) { + ResourceTableType* type = package->findType(name.type); + if (!type) { return {}; } - const std::unique_ptr<ResourceTableType>& type = *iter; - auto iter2 = std::lower_bound(type->entries.begin(), type->entries.end(), name.entry, - lessThanEntry); - if (iter2 == type->entries.end() || name.entry != (*iter2)->name) { + ResourceEntry* entry = type->findEntry(name.entry); + if (!entry) { return {}; } - return std::make_tuple(iter->get(), iter2->get()); + return SearchResult{ package, type, entry }; } } // namespace aapt diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 706f56a2776f..a00c14276aa4 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -18,6 +18,7 @@ #define AAPT_RESOURCE_TABLE_H #include "ConfigDescription.h" +#include "Diagnostics.h" #include "Resource.h" #include "ResourceValues.h" #include "Source.h" @@ -35,7 +36,7 @@ namespace aapt { */ struct Public { bool isPublic = false; - SourceLine source; + Source source; std::u16string comment; }; @@ -44,7 +45,7 @@ struct Public { */ struct ResourceConfigValue { ConfigDescription config; - SourceLine source; + Source source; std::u16string comment; std::unique_ptr<Value> value; }; @@ -54,10 +55,6 @@ struct ResourceConfigValue { * varying values for each defined configuration. */ struct ResourceEntry { - enum { - kUnsetEntryId = 0xffffffffu - }; - /** * The name of the resource. Immutable, as * this determines the order of this resource @@ -68,7 +65,7 @@ struct ResourceEntry { /** * The entry ID for this resource. */ - size_t entryId; + Maybe<uint16_t> id; /** * Whether this resource is public (and must maintain the same @@ -81,8 +78,7 @@ struct ResourceEntry { */ std::vector<ResourceConfigValue> values; - inline ResourceEntry(const StringPiece16& _name); - inline ResourceEntry(const ResourceEntry* rhs); + ResourceEntry(const StringPiece16& name) : name(name.toString()) { } }; /** @@ -90,10 +86,6 @@ struct ResourceEntry { * for this type. */ struct ResourceTableType { - enum { - kUnsetTypeId = 0xffffffffu - }; - /** * The logical type of resource (string, drawable, layout, etc.). */ @@ -102,7 +94,7 @@ struct ResourceTableType { /** * The type ID for this resource. */ - size_t typeId; + Maybe<uint8_t> id; /** * Whether this type is public (and must maintain the same @@ -115,8 +107,30 @@ struct ResourceTableType { */ std::vector<std::unique_ptr<ResourceEntry>> entries; - ResourceTableType(const ResourceType _type); - ResourceTableType(const ResourceTableType* rhs); + explicit ResourceTableType(const ResourceType type) : type(type) { } + + ResourceEntry* findEntry(const StringPiece16& name); + + ResourceEntry* findOrCreateEntry(const StringPiece16& name); +}; + +enum class PackageType { + System, + Vendor, + App, + Dynamic +}; + +struct ResourceTablePackage { + PackageType type = PackageType::App; + Maybe<uint8_t> id; + std::u16string name; + + std::vector<std::unique_ptr<ResourceTableType>> types; + + ResourceTableType* findType(ResourceType type); + + ResourceTableType* findOrCreateType(const ResourceType type); }; /** @@ -125,23 +139,28 @@ struct ResourceTableType { */ class ResourceTable { public: - using iterator = std::vector<std::unique_ptr<ResourceTableType>>::iterator; - using const_iterator = std::vector<std::unique_ptr<ResourceTableType>>::const_iterator; - - enum { - kUnsetPackageId = 0xffffffff - }; + ResourceTable() = default; + ResourceTable(const ResourceTable&) = delete; + ResourceTable& operator=(const ResourceTable&) = delete; - ResourceTable(); + /** + * When a collision of resources occurs, this method decides which value to keep. + * Returns -1 if the existing value should be chosen. + * Returns 0 if the collision can not be resolved (error). + * Returns 1 if the incoming value should be chosen. + */ + static int resolveValueCollision(Value* existing, Value* incoming); - size_t getPackageId() const; - void setPackageId(size_t packageId); + bool addResource(const ResourceNameRef& name, const ConfigDescription& config, + const Source& source, std::unique_ptr<Value> value, + IDiagnostics* diag); - const std::u16string& getPackage() const; - void setPackage(const StringPiece16& package); + bool addResource(const ResourceNameRef& name, const ResourceId resId, + const ConfigDescription& config, const Source& source, + std::unique_ptr<Value> value, IDiagnostics* diag); - bool addResource(const ResourceNameRef& name, const ConfigDescription& config, - const SourceLine& source, std::unique_ptr<Value> value); + bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config, + const Source& source, const StringPiece16& path, IDiagnostics* diag); /** * Same as addResource, but doesn't verify the validity of the name. This is used @@ -149,129 +168,59 @@ public: * names. */ bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config, - const SourceLine& source, std::unique_ptr<Value> value); - - bool addResource(const ResourceNameRef& name, const ResourceId resId, - const ConfigDescription& config, const SourceLine& source, - std::unique_ptr<Value> value); + const Source& source, std::unique_ptr<Value> value, + IDiagnostics* diag); - bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source); + bool markPublic(const ResourceNameRef& name, const ResourceId resId, const Source& source, + IDiagnostics* diag); bool markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId, - const SourceLine& source); + const Source& source, IDiagnostics* diag); + struct SearchResult { + ResourceTablePackage* package; + ResourceTableType* type; + ResourceEntry* entry; + }; + + Maybe<SearchResult> findResource(const ResourceNameRef& name); - /* - * Merges the resources from `other` into this table, mangling the names of the resources - * if `other` has a different package name. + /** + * The string pool used by this resource table. Values that reference strings must use + * this pool to create their strings. + * + * NOTE: `stringPool` must come before `packages` so that it is destroyed after. + * When `string pool` references are destroyed (as they will be when `packages` + * is destroyed), they decrement a refCount, which would cause invalid + * memory access if the pool was already destroyed. + */ + StringPool stringPool; + + /** + * The list of packages in this table, sorted alphabetically by package name. */ - bool merge(ResourceTable&& other); + std::vector<std::unique_ptr<ResourceTablePackage>> packages; /** - * Returns the string pool used by this ResourceTable. - * Values that reference strings should use this pool to create - * their strings. + * Returns the package struct with the given name, or nullptr if such a package does not + * exist. The empty string is a valid package and typically is used to represent the + * 'current' package before it is known to the ResourceTable. */ - StringPool& getValueStringPool(); - const StringPool& getValueStringPool() const; + ResourceTablePackage* findPackage(const StringPiece16& name); - std::tuple<const ResourceTableType*, const ResourceEntry*> - findResource(const ResourceNameRef& name) const; + ResourceTablePackage* findPackageById(uint8_t id); - iterator begin(); - iterator end(); - const_iterator begin() const; - const_iterator end() const; + ResourceTablePackage* createPackage(const StringPiece16& name, uint8_t id); private: - std::unique_ptr<ResourceTableType>& findOrCreateType(ResourceType type); - std::unique_ptr<ResourceEntry>& findOrCreateEntry(std::unique_ptr<ResourceTableType>& type, - const StringPiece16& name); + ResourceTablePackage* findOrCreatePackage(const StringPiece16& name); bool addResourceImpl(const ResourceNameRef& name, const ResourceId resId, - const ConfigDescription& config, const SourceLine& source, - std::unique_ptr<Value> value, const char16_t* validChars); + const ConfigDescription& config, const Source& source, + std::unique_ptr<Value> value, const char16_t* validChars, + IDiagnostics* diag); bool markPublicImpl(const ResourceNameRef& name, const ResourceId resId, - const SourceLine& source, const char16_t* validChars); - - std::u16string mPackage; - size_t mPackageId; - - // StringPool must come before mTypes so that it is destroyed after. - // When StringPool references are destroyed (as they will be when mTypes - // is destroyed), they decrement a refCount, which would cause invalid - // memory access if the pool was already destroyed. - StringPool mValuePool; - - std::vector<std::unique_ptr<ResourceTableType>> mTypes; + const Source& source, const char16_t* validChars, IDiagnostics* diag); }; -// -// ResourceEntry implementation. -// - -inline ResourceEntry::ResourceEntry(const StringPiece16& _name) : - name(_name.toString()), entryId(kUnsetEntryId) { -} - -inline ResourceEntry::ResourceEntry(const ResourceEntry* rhs) : - name(rhs->name), entryId(rhs->entryId), publicStatus(rhs->publicStatus) { -} - -// -// ResourceTableType implementation. -// - -inline ResourceTableType::ResourceTableType(const ResourceType _type) : - type(_type), typeId(kUnsetTypeId) { -} - -inline ResourceTableType::ResourceTableType(const ResourceTableType* rhs) : - type(rhs->type), typeId(rhs->typeId), publicStatus(rhs->publicStatus) { -} - -// -// ResourceTable implementation. -// - -inline StringPool& ResourceTable::getValueStringPool() { - return mValuePool; -} - -inline const StringPool& ResourceTable::getValueStringPool() const { - return mValuePool; -} - -inline ResourceTable::iterator ResourceTable::begin() { - return mTypes.begin(); -} - -inline ResourceTable::iterator ResourceTable::end() { - return mTypes.end(); -} - -inline ResourceTable::const_iterator ResourceTable::begin() const { - return mTypes.begin(); -} - -inline ResourceTable::const_iterator ResourceTable::end() const { - return mTypes.end(); -} - -inline const std::u16string& ResourceTable::getPackage() const { - return mPackage; -} - -inline size_t ResourceTable::getPackageId() const { - return mPackageId; -} - -inline void ResourceTable::setPackage(const StringPiece16& package) { - mPackage = package.toString(); -} - -inline void ResourceTable::setPackageId(size_t packageId) { - mPackageId = packageId; -} - } // namespace aapt #endif // AAPT_RESOURCE_TABLE_H diff --git a/tools/aapt2/ResourceTableResolver.cpp b/tools/aapt2/ResourceTableResolver.cpp deleted file mode 100644 index 910c2c07fb84..000000000000 --- a/tools/aapt2/ResourceTableResolver.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Maybe.h" -#include "NameMangler.h" -#include "Resource.h" -#include "ResourceTable.h" -#include "ResourceTableResolver.h" -#include "ResourceValues.h" -#include "Util.h" - -#include <androidfw/AssetManager.h> -#include <androidfw/ResourceTypes.h> -#include <memory> -#include <vector> - -namespace aapt { - -ResourceTableResolver::ResourceTableResolver( - std::shared_ptr<const ResourceTable> table, - const std::vector<std::shared_ptr<const android::AssetManager>>& sources) : - mTable(table), mSources(sources) { - for (const auto& assetManager : mSources) { - const android::ResTable& resTable = assetManager->getResources(false); - const size_t packageCount = resTable.getBasePackageCount(); - for (size_t i = 0; i < packageCount; i++) { - std::u16string packageName = resTable.getBasePackageName(i).string(); - mIncludedPackages.insert(std::move(packageName)); - } - } -} - -Maybe<ResourceId> ResourceTableResolver::findId(const ResourceName& name) { - Maybe<Entry> result = findAttribute(name); - if (result) { - return result.value().id; - } - return {}; -} - -Maybe<IResolver::Entry> ResourceTableResolver::findAttribute(const ResourceName& name) { - auto cacheIter = mCache.find(name); - if (cacheIter != std::end(mCache)) { - return Entry{ cacheIter->second.id, cacheIter->second.attr.get() }; - } - - ResourceName mangledName; - const ResourceName* nameToSearch = &name; - if (name.package != mTable->getPackage()) { - // This may be a reference to an included resource or - // to a mangled resource. - if (mIncludedPackages.find(name.package) == mIncludedPackages.end()) { - // This is not in our included set, so mangle the name and - // check for that. - mangledName.entry = name.entry; - NameMangler::mangle(name.package, &mangledName.entry); - mangledName.package = mTable->getPackage(); - mangledName.type = name.type; - nameToSearch = &mangledName; - } else { - const CacheEntry* cacheEntry = buildCacheEntry(name); - if (cacheEntry) { - return Entry{ cacheEntry->id, cacheEntry->attr.get() }; - } - return {}; - } - } - - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = mTable->findResource(*nameToSearch); - if (type && entry) { - Entry result = {}; - if (mTable->getPackageId() != ResourceTable::kUnsetPackageId && - type->typeId != ResourceTableType::kUnsetTypeId && - entry->entryId != ResourceEntry::kUnsetEntryId) { - result.id = ResourceId(mTable->getPackageId(), type->typeId, entry->entryId); - } - - if (!entry->values.empty()) { - visitFunc<Attribute>(*entry->values.front().value, [&result](Attribute& attr) { - result.attr = &attr; - }); - } - return result; - } - return {}; -} - -Maybe<ResourceName> ResourceTableResolver::findName(ResourceId resId) { - for (const auto& assetManager : mSources) { - const android::ResTable& table = assetManager->getResources(false); - - android::ResTable::resource_name resourceName; - if (!table.getResourceName(resId.id, false, &resourceName)) { - continue; - } - - const ResourceType* type = parseResourceType(StringPiece16(resourceName.type, - resourceName.typeLen)); - assert(type); - return ResourceName{ - { resourceName.package, resourceName.packageLen }, - *type, - { resourceName.name, resourceName.nameLen } }; - } - return {}; -} - -/** - * This is called when we need to lookup a resource name in the AssetManager. - * Since the values in the AssetManager are not parsed like in a ResourceTable, - * we must create Attribute objects here if we find them. - */ -const ResourceTableResolver::CacheEntry* ResourceTableResolver::buildCacheEntry( - const ResourceName& name) { - for (const auto& assetManager : mSources) { - const android::ResTable& table = assetManager->getResources(false); - - const StringPiece16 type16 = toString(name.type); - ResourceId resId { - table.identifierForName( - name.entry.data(), name.entry.size(), - type16.data(), type16.size(), - name.package.data(), name.package.size()) - }; - - if (!resId.isValid()) { - continue; - } - - CacheEntry& entry = mCache[name]; - entry.id = resId; - - // - // Now check to see if this resource is an Attribute. - // - - const android::ResTable::bag_entry* bagBegin; - ssize_t bags = table.lockBag(resId.id, &bagBegin); - if (bags < 1) { - table.unlockBag(bagBegin); - return &entry; - } - - // Look for the ATTR_TYPE key in the bag and check the types it supports. - uint32_t attrTypeMask = 0; - for (ssize_t i = 0; i < bags; i++) { - if (bagBegin[i].map.name.ident == android::ResTable_map::ATTR_TYPE) { - attrTypeMask = bagBegin[i].map.value.data; - } - } - - entry.attr = util::make_unique<Attribute>(false); - - if (attrTypeMask & android::ResTable_map::TYPE_ENUM || - attrTypeMask & android::ResTable_map::TYPE_FLAGS) { - for (ssize_t i = 0; i < bags; i++) { - if (Res_INTERNALID(bagBegin[i].map.name.ident)) { - // Internal IDs are special keys, which are not enum/flag symbols, so skip. - continue; - } - - android::ResTable::resource_name symbolName; - bool result = table.getResourceName(bagBegin[i].map.name.ident, false, - &symbolName); - assert(result); - const ResourceType* type = parseResourceType( - StringPiece16(symbolName.type, symbolName.typeLen)); - assert(type); - - entry.attr->symbols.push_back(Attribute::Symbol{ - Reference(ResourceNameRef( - StringPiece16(symbolName.package, symbolName.packageLen), - *type, - StringPiece16(symbolName.name, symbolName.nameLen))), - bagBegin[i].map.value.data - }); - } - } - - entry.attr->typeMask |= attrTypeMask; - table.unlockBag(bagBegin); - return &entry; - } - return nullptr; -} - -} // namespace aapt diff --git a/tools/aapt2/ResourceTableResolver.h b/tools/aapt2/ResourceTableResolver.h deleted file mode 100644 index 8f6b0b5993e4..000000000000 --- a/tools/aapt2/ResourceTableResolver.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AAPT_RESOURCE_TABLE_RESOLVER_H -#define AAPT_RESOURCE_TABLE_RESOLVER_H - -#include "Maybe.h" -#include "Resolver.h" -#include "Resource.h" -#include "ResourceTable.h" -#include "ResourceValues.h" - -#include <androidfw/AssetManager.h> -#include <memory> -#include <vector> -#include <unordered_set> - -namespace aapt { - -/** - * Encapsulates the search of library sources as well as the local ResourceTable. - */ -class ResourceTableResolver : public IResolver { -public: - /** - * Creates a resolver with a local ResourceTable and an AssetManager - * loaded with library packages. - */ - ResourceTableResolver( - std::shared_ptr<const ResourceTable> table, - const std::vector<std::shared_ptr<const android::AssetManager>>& sources); - - ResourceTableResolver(const ResourceTableResolver&) = delete; // Not copyable. - - virtual Maybe<ResourceId> findId(const ResourceName& name) override; - - virtual Maybe<Entry> findAttribute(const ResourceName& name) override; - - virtual Maybe<ResourceName> findName(ResourceId resId) override; - -private: - struct CacheEntry { - ResourceId id; - std::unique_ptr<Attribute> attr; - }; - - const CacheEntry* buildCacheEntry(const ResourceName& name); - - std::shared_ptr<const ResourceTable> mTable; - std::vector<std::shared_ptr<const android::AssetManager>> mSources; - std::map<ResourceName, CacheEntry> mCache; - std::unordered_set<std::u16string> mIncludedPackages; -}; - -} // namespace aapt - -#endif // AAPT_RESOURCE_TABLE_RESOLVER_H diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 06d8699730de..2055a80aaba6 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -14,9 +14,12 @@ * limitations under the License. */ +#include "Diagnostics.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "Util.h" +#include "util/Util.h" + +#include "test/Common.h" #include <algorithm> #include <gtest/gtest.h> @@ -25,204 +28,90 @@ namespace aapt { -struct TestValue : public Value { - std::u16string value; - - TestValue(StringPiece16 str) : value(str.toString()) { - } - - TestValue* clone(StringPool* /*newPool*/) const override { - return new TestValue(value); - } - - void print(std::ostream& out) const override { - out << "(test) " << value; - } - - virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {} - virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {} -}; - -struct TestWeakValue : public Value { - bool isWeak() const override { - return true; - } - - TestWeakValue* clone(StringPool* /*newPool*/) const override { - return new TestWeakValue(); - } - - void print(std::ostream& out) const override { - out << "(test) [weak]"; - } +struct ResourceTableTest : public ::testing::Test { + struct EmptyDiagnostics : public IDiagnostics { + void error(const DiagMessage& msg) override {} + void warn(const DiagMessage& msg) override {} + void note(const DiagMessage& msg) override {} + }; - virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {} - virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {} + EmptyDiagnostics mDiagnostics; }; -TEST(ResourceTableTest, FailToAddResourceWithBadName) { +TEST_F(ResourceTableTest, FailToAddResourceWithBadName) { ResourceTable table; - table.setPackage(u"android"); EXPECT_FALSE(table.addResource( ResourceNameRef{ u"android", ResourceType::kId, u"hey,there" }, - {}, SourceLine{ "test.xml", 21 }, - util::make_unique<TestValue>(u"rawValue"))); + {}, Source{ "test.xml", 21 }, + util::make_unique<Id>(), &mDiagnostics)); EXPECT_FALSE(table.addResource( ResourceNameRef{ u"android", ResourceType::kId, u"hey:there" }, - {}, SourceLine{ "test.xml", 21 }, - util::make_unique<TestValue>(u"rawValue"))); + {}, Source{ "test.xml", 21 }, + util::make_unique<Id>(), &mDiagnostics)); } -TEST(ResourceTableTest, AddOneResource) { - const std::u16string kAndroidPackage = u"android"; - +TEST_F(ResourceTableTest, AddOneResource) { ResourceTable table; - table.setPackage(kAndroidPackage); - - const ResourceName name = { kAndroidPackage, ResourceType::kAttr, u"id" }; - EXPECT_TRUE(table.addResource(name, {}, SourceLine{ "test/path/file.xml", 23 }, - util::make_unique<TestValue>(u"rawValue"))); + EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), {}, + Source{ "test/path/file.xml", 23 }, + util::make_unique<Id>(), &mDiagnostics)); - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = table.findResource(name); - ASSERT_NE(nullptr, type); - ASSERT_NE(nullptr, entry); - EXPECT_EQ(name.entry, entry->name); - - ASSERT_NE(std::end(entry->values), - std::find_if(std::begin(entry->values), std::end(entry->values), - [](const ResourceConfigValue& val) -> bool { - return val.config == ConfigDescription{}; - })); + ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id")); } -TEST(ResourceTableTest, AddMultipleResources) { - const std::u16string kAndroidPackage = u"android"; +TEST_F(ResourceTableTest, AddMultipleResources) { ResourceTable table; - table.setPackage(kAndroidPackage); ConfigDescription config; ConfigDescription languageConfig; memcpy(languageConfig.language, "pl", sizeof(languageConfig.language)); EXPECT_TRUE(table.addResource( - ResourceName{ kAndroidPackage, ResourceType::kAttr, u"layout_width" }, - config, SourceLine{ "test/path/file.xml", 10 }, - util::make_unique<TestValue>(u"rawValue"))); + test::parseNameOrDie(u"@android:attr/layout_width"), + config, Source{ "test/path/file.xml", 10 }, + util::make_unique<Id>(), &mDiagnostics)); EXPECT_TRUE(table.addResource( - ResourceName{ kAndroidPackage, ResourceType::kAttr, u"id" }, - config, SourceLine{ "test/path/file.xml", 12 }, - util::make_unique<TestValue>(u"rawValue"))); + test::parseNameOrDie(u"@android:attr/id"), + config, Source{ "test/path/file.xml", 12 }, + util::make_unique<Id>(), &mDiagnostics)); EXPECT_TRUE(table.addResource( - ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" }, - config, SourceLine{ "test/path/file.xml", 14 }, - util::make_unique<TestValue>(u"Ok"))); + test::parseNameOrDie(u"@android:string/ok"), + config, Source{ "test/path/file.xml", 14 }, + util::make_unique<Id>(), &mDiagnostics)); EXPECT_TRUE(table.addResource( - ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" }, - languageConfig, SourceLine{ "test/path/file.xml", 20 }, - util::make_unique<TestValue>(u"Tak"))); - - const auto endTypeIter = std::end(table); - auto typeIter = std::begin(table); - - ASSERT_NE(endTypeIter, typeIter); - EXPECT_EQ(ResourceType::kAttr, (*typeIter)->type); - - { - const std::unique_ptr<ResourceTableType>& type = *typeIter; - const auto endEntryIter = std::end(type->entries); - auto entryIter = std::begin(type->entries); - ASSERT_NE(endEntryIter, entryIter); - EXPECT_EQ(std::u16string(u"id"), (*entryIter)->name); - - ++entryIter; - ASSERT_NE(endEntryIter, entryIter); - EXPECT_EQ(std::u16string(u"layout_width"), (*entryIter)->name); - - ++entryIter; - ASSERT_EQ(endEntryIter, entryIter); - } - - ++typeIter; - ASSERT_NE(endTypeIter, typeIter); - EXPECT_EQ(ResourceType::kString, (*typeIter)->type); - - { - const std::unique_ptr<ResourceTableType>& type = *typeIter; - const auto endEntryIter = std::end(type->entries); - auto entryIter = std::begin(type->entries); - ASSERT_NE(endEntryIter, entryIter); - EXPECT_EQ(std::u16string(u"ok"), (*entryIter)->name); - - { - const std::unique_ptr<ResourceEntry>& entry = *entryIter; - const auto endConfigIter = std::end(entry->values); - auto configIter = std::begin(entry->values); - - ASSERT_NE(endConfigIter, configIter); - EXPECT_EQ(config, configIter->config); - const TestValue* value = - dynamic_cast<const TestValue*>(configIter->value.get()); - ASSERT_NE(nullptr, value); - EXPECT_EQ(std::u16string(u"Ok"), value->value); - - ++configIter; - ASSERT_NE(endConfigIter, configIter); - EXPECT_EQ(languageConfig, configIter->config); - EXPECT_NE(nullptr, configIter->value); - - value = dynamic_cast<const TestValue*>(configIter->value.get()); - ASSERT_NE(nullptr, value); - EXPECT_EQ(std::u16string(u"Tak"), value->value); - - ++configIter; - EXPECT_EQ(endConfigIter, configIter); - } - - ++entryIter; - ASSERT_EQ(endEntryIter, entryIter); - } - - ++typeIter; - EXPECT_EQ(endTypeIter, typeIter); + test::parseNameOrDie(u"@android:string/ok"), + languageConfig, Source{ "test/path/file.xml", 20 }, + util::make_unique<BinaryPrimitive>(android::Res_value{}), &mDiagnostics)); + + ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width")); + ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id")); + ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:string/ok")); + ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(&table, u"@android:string/ok", + languageConfig)); } -TEST(ResourceTableTest, OverrideWeakResourceValue) { - const std::u16string kAndroid = u"android"; - +TEST_F(ResourceTableTest, OverrideWeakResourceValue) { ResourceTable table; - table.setPackage(kAndroid); - table.setPackageId(0x01); - - ASSERT_TRUE(table.addResource( - ResourceName{ kAndroid, ResourceType::kAttr, u"foo" }, - {}, {}, util::make_unique<TestWeakValue>())); - - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = table.findResource( - ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" }); - ASSERT_NE(nullptr, type); - ASSERT_NE(nullptr, entry); - ASSERT_EQ(entry->values.size(), 1u); - EXPECT_TRUE(entry->values.front().value->isWeak()); - - ASSERT_TRUE(table.addResource(ResourceName{ kAndroid, ResourceType::kAttr, u"foo" }, {}, {}, - util::make_unique<TestValue>(u"bar"))); - - std::tie(type, entry) = table.findResource( - ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" }); - ASSERT_NE(nullptr, type); - ASSERT_NE(nullptr, entry); - ASSERT_EQ(entry->values.size(), 1u); - EXPECT_FALSE(entry->values.front().value->isWeak()); + + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {}, + util::make_unique<Attribute>(true), &mDiagnostics)); + + Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_TRUE(attr->isWeak()); + + ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {}, + util::make_unique<Attribute>(false), &mDiagnostics)); + + attr = test::getValue<Attribute>(&table, u"@android:attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_FALSE(attr->isWeak()); } } // namespace aapt diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp new file mode 100644 index 000000000000..0db1c372c901 --- /dev/null +++ b/tools/aapt2/ResourceUtils.cpp @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ResourceUtils.h" +#include "util/Util.h" + +#include <androidfw/ResourceTypes.h> +#include <sstream> + +namespace aapt { +namespace ResourceUtils { + +void extractResourceName(const StringPiece16& str, StringPiece16* outPackage, + StringPiece16* outType, StringPiece16* outEntry) { + const char16_t* start = str.data(); + const char16_t* end = start + str.size(); + const char16_t* current = start; + while (current != end) { + if (outType->size() == 0 && *current == u'/') { + outType->assign(start, current - start); + start = current + 1; + } else if (outPackage->size() == 0 && *current == u':') { + outPackage->assign(start, current - start); + start = current + 1; + } + current++; + } + outEntry->assign(start, end - start); +} + +bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate, + bool* outPrivate) { + StringPiece16 trimmedStr(util::trimWhitespace(str)); + if (trimmedStr.empty()) { + return false; + } + + bool create = false; + bool priv = false; + if (trimmedStr.data()[0] == u'@') { + size_t offset = 1; + if (trimmedStr.data()[1] == u'+') { + create = true; + offset += 1; + } else if (trimmedStr.data()[1] == u'*') { + priv = true; + offset += 1; + } + StringPiece16 package; + StringPiece16 type; + StringPiece16 entry; + extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), &package, &type, + &entry); + + const ResourceType* parsedType = parseResourceType(type); + if (!parsedType) { + return false; + } + + if (create && *parsedType != ResourceType::kId) { + return false; + } + + outRef->package = package; + outRef->type = *parsedType; + outRef->entry = entry; + if (outCreate) { + *outCreate = create; + } + if (outPrivate) { + *outPrivate = priv; + } + return true; + } + return false; +} + +bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) { + StringPiece16 trimmedStr(util::trimWhitespace(str)); + if (trimmedStr.empty()) { + return false; + } + + if (*trimmedStr.data() == u'?') { + StringPiece16 package; + StringPiece16 type; + StringPiece16 entry; + extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry); + + if (!type.empty() && type != u"attr") { + return false; + } + + outRef->package = package; + outRef->type = ResourceType::kAttr; + outRef->entry = entry; + return true; + } + return false; +} + +/* + * Style parent's are a bit different. We accept the following formats: + * + * @[package:]style/<entry> + * ?[package:]style/<entry> + * <package>:[style/]<entry> + * [package:style/]<entry> + */ +Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) { + if (str.empty()) { + return {}; + } + + StringPiece16 name = str; + + bool hasLeadingIdentifiers = false; + bool privateRef = false; + + // Skip over these identifiers. A style's parent is a normal reference. + if (name.data()[0] == u'@' || name.data()[0] == u'?') { + hasLeadingIdentifiers = true; + name = name.substr(1, name.size() - 1); + if (name.data()[0] == u'*') { + privateRef = true; + name = name.substr(1, name.size() - 1); + } + } + + ResourceNameRef ref; + ref.type = ResourceType::kStyle; + + StringPiece16 typeStr; + extractResourceName(name, &ref.package, &typeStr, &ref.entry); + if (!typeStr.empty()) { + // If we have a type, make sure it is a Style. + const ResourceType* parsedType = parseResourceType(typeStr); + if (!parsedType || *parsedType != ResourceType::kStyle) { + std::stringstream err; + err << "invalid resource type '" << typeStr << "' for parent of style"; + *outError = err.str(); + return {}; + } + } else { + // No type was defined, this should not have a leading identifier. + if (hasLeadingIdentifiers) { + std::stringstream err; + err << "invalid parent reference '" << str << "'"; + *outError = err.str(); + return {}; + } + } + + if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) { + std::stringstream err; + err << "invalid parent reference '" << str << "'"; + *outError = err.str(); + return {}; + } + + Reference result(ref); + result.privateReference = privateRef; + return result; +} + +std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate) { + ResourceNameRef ref; + bool privateRef = false; + if (tryParseReference(str, &ref, outCreate, &privateRef)) { + std::unique_ptr<Reference> value = util::make_unique<Reference>(ref); + value->privateReference = privateRef; + return value; + } + + if (tryParseAttributeReference(str, &ref)) { + if (outCreate) { + *outCreate = false; + } + return util::make_unique<Reference>(ref, Reference::Type::kAttribute); + } + return {}; +} + +std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str) { + StringPiece16 trimmedStr(util::trimWhitespace(str)); + android::Res_value value = { }; + if (trimmedStr == u"@null") { + // TYPE_NULL with data set to 0 is interpreted by the runtime as an error. + // Instead we set the data type to TYPE_REFERENCE with a value of 0. + value.dataType = android::Res_value::TYPE_REFERENCE; + } else if (trimmedStr == u"@empty") { + // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime. + value.dataType = android::Res_value::TYPE_NULL; + value.data = android::Res_value::DATA_NULL_EMPTY; + } else { + return {}; + } + return util::make_unique<BinaryPrimitive>(value); +} + +std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr, + const StringPiece16& str) { + StringPiece16 trimmedStr(util::trimWhitespace(str)); + for (const Attribute::Symbol& symbol : enumAttr->symbols) { + // Enum symbols are stored as @package:id/symbol resources, + // so we need to match against the 'entry' part of the identifier. + const ResourceName& enumSymbolResourceName = symbol.symbol.name.value(); + if (trimmedStr == enumSymbolResourceName.entry) { + android::Res_value value = { }; + value.dataType = android::Res_value::TYPE_INT_DEC; + value.data = symbol.value; + return util::make_unique<BinaryPrimitive>(value); + } + } + return {}; +} + +std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr, + const StringPiece16& str) { + android::Res_value flags = { }; + flags.dataType = android::Res_value::TYPE_INT_DEC; + + for (StringPiece16 part : util::tokenize(str, u'|')) { + StringPiece16 trimmedPart = util::trimWhitespace(part); + + bool flagSet = false; + for (const Attribute::Symbol& symbol : flagAttr->symbols) { + // Flag symbols are stored as @package:id/symbol resources, + // so we need to match against the 'entry' part of the identifier. + const ResourceName& flagSymbolResourceName = symbol.symbol.name.value(); + if (trimmedPart == flagSymbolResourceName.entry) { + flags.data |= symbol.value; + flagSet = true; + break; + } + } + + if (!flagSet) { + return {}; + } + } + return util::make_unique<BinaryPrimitive>(flags); +} + +static uint32_t parseHex(char16_t c, bool* outError) { + if (c >= u'0' && c <= u'9') { + return c - u'0'; + } else if (c >= u'a' && c <= u'f') { + return c - u'a' + 0xa; + } else if (c >= u'A' && c <= u'F') { + return c - u'A' + 0xa; + } else { + *outError = true; + return 0xffffffffu; + } +} + +std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str) { + StringPiece16 colorStr(util::trimWhitespace(str)); + const char16_t* start = colorStr.data(); + const size_t len = colorStr.size(); + if (len == 0 || start[0] != u'#') { + return {}; + } + + android::Res_value value = { }; + bool error = false; + if (len == 4) { + value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4; + value.data = 0xff000000u; + value.data |= parseHex(start[1], &error) << 20; + value.data |= parseHex(start[1], &error) << 16; + value.data |= parseHex(start[2], &error) << 12; + value.data |= parseHex(start[2], &error) << 8; + value.data |= parseHex(start[3], &error) << 4; + value.data |= parseHex(start[3], &error); + } else if (len == 5) { + value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4; + value.data |= parseHex(start[1], &error) << 28; + value.data |= parseHex(start[1], &error) << 24; + value.data |= parseHex(start[2], &error) << 20; + value.data |= parseHex(start[2], &error) << 16; + value.data |= parseHex(start[3], &error) << 12; + value.data |= parseHex(start[3], &error) << 8; + value.data |= parseHex(start[4], &error) << 4; + value.data |= parseHex(start[4], &error); + } else if (len == 7) { + value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8; + value.data = 0xff000000u; + value.data |= parseHex(start[1], &error) << 20; + value.data |= parseHex(start[2], &error) << 16; + value.data |= parseHex(start[3], &error) << 12; + value.data |= parseHex(start[4], &error) << 8; + value.data |= parseHex(start[5], &error) << 4; + value.data |= parseHex(start[6], &error); + } else if (len == 9) { + value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8; + value.data |= parseHex(start[1], &error) << 28; + value.data |= parseHex(start[2], &error) << 24; + value.data |= parseHex(start[3], &error) << 20; + value.data |= parseHex(start[4], &error) << 16; + value.data |= parseHex(start[5], &error) << 12; + value.data |= parseHex(start[6], &error) << 8; + value.data |= parseHex(start[7], &error) << 4; + value.data |= parseHex(start[8], &error); + } else { + return {}; + } + return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value); +} + +std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) { + StringPiece16 trimmedStr(util::trimWhitespace(str)); + uint32_t data = 0; + if (trimmedStr == u"true" || trimmedStr == u"TRUE") { + data = 0xffffffffu; + } else if (trimmedStr != u"false" && trimmedStr != u"FALSE") { + return {}; + } + android::Res_value value = { }; + value.dataType = android::Res_value::TYPE_INT_BOOLEAN; + value.data = data; + return util::make_unique<BinaryPrimitive>(value); +} + +std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) { + android::Res_value value; + if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) { + return {}; + } + return util::make_unique<BinaryPrimitive>(value); +} + +std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) { + android::Res_value value; + if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) { + return {}; + } + return util::make_unique<BinaryPrimitive>(value); +} + +uint32_t androidTypeToAttributeTypeMask(uint16_t type) { + switch (type) { + case android::Res_value::TYPE_NULL: + case android::Res_value::TYPE_REFERENCE: + case android::Res_value::TYPE_ATTRIBUTE: + case android::Res_value::TYPE_DYNAMIC_REFERENCE: + return android::ResTable_map::TYPE_REFERENCE; + + case android::Res_value::TYPE_STRING: + return android::ResTable_map::TYPE_STRING; + + case android::Res_value::TYPE_FLOAT: + return android::ResTable_map::TYPE_FLOAT; + + case android::Res_value::TYPE_DIMENSION: + return android::ResTable_map::TYPE_DIMENSION; + + case android::Res_value::TYPE_FRACTION: + return android::ResTable_map::TYPE_FRACTION; + + case android::Res_value::TYPE_INT_DEC: + case android::Res_value::TYPE_INT_HEX: + return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM + | android::ResTable_map::TYPE_FLAGS; + + case android::Res_value::TYPE_INT_BOOLEAN: + return android::ResTable_map::TYPE_BOOLEAN; + + case android::Res_value::TYPE_INT_COLOR_ARGB8: + case android::Res_value::TYPE_INT_COLOR_RGB8: + case android::Res_value::TYPE_INT_COLOR_ARGB4: + case android::Res_value::TYPE_INT_COLOR_RGB4: + return android::ResTable_map::TYPE_COLOR; + + default: + return 0; + }; +} + +std::unique_ptr<Item> parseItemForAttribute( + const StringPiece16& value, uint32_t typeMask, + std::function<void(const ResourceName&)> onCreateReference) { + std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value); + if (nullOrEmpty) { + return std::move(nullOrEmpty); + } + + bool create = false; + std::unique_ptr<Reference> reference = tryParseReference(value, &create); + if (reference) { + if (create && onCreateReference) { + onCreateReference(reference->name.value()); + } + return std::move(reference); + } + + if (typeMask & android::ResTable_map::TYPE_COLOR) { + // Try parsing this as a color. + std::unique_ptr<BinaryPrimitive> color = tryParseColor(value); + if (color) { + return std::move(color); + } + } + + if (typeMask & android::ResTable_map::TYPE_BOOLEAN) { + // Try parsing this as a boolean. + std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value); + if (boolean) { + return std::move(boolean); + } + } + + if (typeMask & android::ResTable_map::TYPE_INTEGER) { + // Try parsing this as an integer. + std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value); + if (integer) { + return std::move(integer); + } + } + + const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT + | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION; + if (typeMask & floatMask) { + // Try parsing this as a float. + std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value); + if (floatingPoint) { + if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) { + return std::move(floatingPoint); + } + } + } + return {}; +} + +/** + * We successively try to parse the string as a resource type that the Attribute + * allows. + */ +std::unique_ptr<Item> parseItemForAttribute( + const StringPiece16& str, const Attribute* attr, + std::function<void(const ResourceName&)> onCreateReference) { + const uint32_t typeMask = attr->typeMask; + std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference); + if (value) { + return value; + } + + if (typeMask & android::ResTable_map::TYPE_ENUM) { + // Try parsing this as an enum. + std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str); + if (enumValue) { + return std::move(enumValue); + } + } + + if (typeMask & android::ResTable_map::TYPE_FLAGS) { + // Try parsing this as a flag. + std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str); + if (flagValue) { + return std::move(flagValue); + } + } + return {}; +} + +} // namespace ResourceUtils +} // namespace aapt diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h new file mode 100644 index 000000000000..118a2ee9d769 --- /dev/null +++ b/tools/aapt2/ResourceUtils.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_RESOURCEUTILS_H +#define AAPT_RESOURCEUTILS_H + +#include "Resource.h" +#include "ResourceValues.h" +#include "util/StringPiece.h" + +#include <functional> +#include <memory> + +namespace aapt { +namespace ResourceUtils { + +/* + * Extracts the package, type, and name from a string of the format: + * + * [package:]type/name + * + * where the package can be empty. Validation must be performed on each + * individual extracted piece to verify that the pieces are valid. + */ +void extractResourceName(const StringPiece16& str, StringPiece16* outPackage, + StringPiece16* outType, StringPiece16* outEntry); + +/* + * Returns true if the string was parsed as a reference (@[+][package:]type/name), with + * `outReference` set to the parsed reference. + * + * If '+' was present in the reference, `outCreate` is set to true. + * If '*' was present in the reference, `outPrivate` is set to true. + */ +bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference, + bool* outCreate = nullptr, bool* outPrivate = nullptr); + +/* + * Returns true if the string was parsed as an attribute reference (?[package:]type/name), + * with `outReference` set to the parsed reference. + */ +bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference); + +/* + * Returns a Reference, or None Maybe instance if the string `str` was parsed as a + * valid reference to a style. + * The format for a style parent is slightly more flexible than a normal reference: + * + * @[package:]style/<entry> or + * ?[package:]style/<entry> or + * <package>:[style/]<entry> + */ +Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError); + +/* + * Returns a Reference object if the string was parsed as a resource or attribute reference, + * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if + * the '+' was present in the string. + */ +std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate = nullptr); + +/* + * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed + * as one. + */ +std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str); + +/* + * Returns a BinaryPrimitve object representing a color if the string was parsed + * as one. + */ +std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str); + +/* + * Returns a BinaryPrimitve object representing a boolean if the string was parsed + * as one. + */ +std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str); + +/* + * Returns a BinaryPrimitve object representing an integer if the string was parsed + * as one. + */ +std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str); + +/* + * Returns a BinaryPrimitve object representing a floating point number + * (float, dimension, etc) if the string was parsed as one. + */ +std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str); + +/* + * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed + * as one. + */ +std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr, + const StringPiece16& str); + +/* + * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed + * as one. + */ +std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr, + const StringPiece16& str); +/* + * Try to convert a string to an Item for the given attribute. The attribute will + * restrict what values the string can be converted to. + * The callback function onCreateReference is called when the parsed item is a + * reference to an ID that must be created (@+id/foo). + */ +std::unique_ptr<Item> parseItemForAttribute( + const StringPiece16& value, const Attribute* attr, + std::function<void(const ResourceName&)> onCreateReference = {}); + +std::unique_ptr<Item> parseItemForAttribute( + const StringPiece16& value, uint32_t typeMask, + std::function<void(const ResourceName&)> onCreateReference = {}); + +uint32_t androidTypeToAttributeTypeMask(uint16_t type); + +} // namespace ResourceUtils +} // namespace aapt + +#endif /* AAPT_RESOURCEUTILS_H */ diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp new file mode 100644 index 000000000000..7de8f4130316 --- /dev/null +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Resource.h" +#include "ResourceUtils.h" + +#include "test/Common.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) { + ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" }; + ResourceNameRef actual; + bool create = false; + bool privateRef = false; + EXPECT_TRUE(ResourceUtils::tryParseReference(u"@color/foo", &actual, &create, &privateRef)); + EXPECT_EQ(expected, actual); + EXPECT_FALSE(create); + EXPECT_FALSE(privateRef); +} + +TEST(ResourceUtilsTest, ParseReferenceWithPackage) { + ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" }; + ResourceNameRef actual; + bool create = false; + bool privateRef = false; + EXPECT_TRUE(ResourceUtils::tryParseReference(u"@android:color/foo", &actual, &create, + &privateRef)); + EXPECT_EQ(expected, actual); + EXPECT_FALSE(create); + EXPECT_FALSE(privateRef); +} + +TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) { + ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" }; + ResourceNameRef actual; + bool create = false; + bool privateRef = false; + EXPECT_TRUE(ResourceUtils::tryParseReference(u"\t @android:color/foo\n \n\t", &actual, + &create, &privateRef)); + EXPECT_EQ(expected, actual); + EXPECT_FALSE(create); + EXPECT_FALSE(privateRef); +} + +TEST(ResourceUtilsTest, ParseAutoCreateIdReference) { + ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" }; + ResourceNameRef actual; + bool create = false; + bool privateRef = false; + EXPECT_TRUE(ResourceUtils::tryParseReference(u"@+android:id/foo", &actual, &create, + &privateRef)); + EXPECT_EQ(expected, actual); + EXPECT_TRUE(create); + EXPECT_FALSE(privateRef); +} + +TEST(ResourceUtilsTest, ParsePrivateReference) { + ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" }; + ResourceNameRef actual; + bool create = false; + bool privateRef = false; + EXPECT_TRUE(ResourceUtils::tryParseReference(u"@*android:id/foo", &actual, &create, + &privateRef)); + EXPECT_EQ(expected, actual); + EXPECT_FALSE(create); + EXPECT_TRUE(privateRef); +} + +TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { + bool create = false; + bool privateRef = false; + ResourceNameRef actual; + EXPECT_FALSE(ResourceUtils::tryParseReference(u"@+android:color/foo", &actual, &create, + &privateRef)); +} + +TEST(ResourceUtilsTest, ParseStyleParentReference) { + const ResourceName kAndroidStyleFooName = { u"android", ResourceType::kStyle, u"foo" }; + const ResourceName kStyleFooName = { {}, ResourceType::kStyle, u"foo" }; + + std::string errStr; + Maybe<Reference> ref = ResourceUtils::parseStyleParentReference(u"@android:style/foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::parseStyleParentReference(u"@style/foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kStyleFooName); + + ref = ResourceUtils::parseStyleParentReference(u"?android:style/foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::parseStyleParentReference(u"?style/foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kStyleFooName); + + ref = ResourceUtils::parseStyleParentReference(u"android:style/foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::parseStyleParentReference(u"android:foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::parseStyleParentReference(u"foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kStyleFooName); +} + +} // namespace aapt diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index aabb375e6c5e..ecc5cd2bdcfa 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -15,15 +15,26 @@ */ #include "Resource.h" -#include "ResourceTypeExtensions.h" +#include "flatten/ResourceTypeExtensions.h" #include "ResourceValues.h" -#include "Util.h" +#include "util/Util.h" +#include "ValueVisitor.h" #include <androidfw/ResourceTypes.h> #include <limits> namespace aapt { +template <typename Derived> +void BaseValue<Derived>::accept(RawValueVisitor* visitor) { + visitor->visit(static_cast<Derived*>(this)); +} + +template <typename Derived> +void BaseItem<Derived>::accept(RawValueVisitor* visitor) { + visitor->visit(static_cast<Derived*>(this)); +} + bool Value::isItem() const { return false; } @@ -43,14 +54,14 @@ RawString* RawString::clone(StringPool* newPool) const { return new RawString(newPool->makeRef(*value)); } -bool RawString::flatten(android::Res_value& outValue) const { - outValue.dataType = ExtendedTypes::TYPE_RAW_STRING; - outValue.data = static_cast<uint32_t>(value.getIndex()); +bool RawString::flatten(android::Res_value* outValue) const { + outValue->dataType = ExtendedTypes::TYPE_RAW_STRING; + outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); return true; } -void RawString::print(std::ostream& out) const { - out << "(raw string) " << *value; +void RawString::print(std::ostream* out) const { + *out << "(raw string) " << *value; } Reference::Reference() : referenceType(Reference::Type::kResource) { @@ -63,11 +74,11 @@ Reference::Reference(const ResourceNameRef& n, Type t) : Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) { } -bool Reference::flatten(android::Res_value& outValue) const { - outValue.dataType = (referenceType == Reference::Type::kResource) +bool Reference::flatten(android::Res_value* outValue) const { + outValue->dataType = (referenceType == Reference::Type::kResource) ? android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE; - outValue.data = id.id; + outValue->data = util::hostToDevice32(id ? id.value().id : 0); return true; } @@ -79,20 +90,20 @@ Reference* Reference::clone(StringPool* /*newPool*/) const { return ref; } -void Reference::print(std::ostream& out) const { - out << "(reference) "; +void Reference::print(std::ostream* out) const { + *out << "(reference) "; if (referenceType == Reference::Type::kResource) { - out << "@"; + *out << "@"; } else { - out << "?"; + *out << "?"; } - if (name.isValid()) { - out << name; + if (name) { + *out << name.value(); } - if (id.isValid() || Res_INTERNALID(id.id)) { - out << " " << id; + if (id && !Res_INTERNALID(id.value().id)) { + *out << " " << id.value(); } } @@ -100,9 +111,9 @@ bool Id::isWeak() const { return true; } -bool Id::flatten(android::Res_value& out) const { - out.dataType = android::Res_value::TYPE_INT_BOOLEAN; - out.data = 0; +bool Id::flatten(android::Res_value* out) const { + out->dataType = android::Res_value::TYPE_INT_BOOLEAN; + out->data = util::hostToDevice32(0); return true; } @@ -110,21 +121,21 @@ Id* Id::clone(StringPool* /*newPool*/) const { return new Id(); } -void Id::print(std::ostream& out) const { - out << "(id)"; +void Id::print(std::ostream* out) const { + *out << "(id)"; } String::String(const StringPool::Ref& ref) : value(ref) { } -bool String::flatten(android::Res_value& outValue) const { - // Verify that our StringPool index is within encodeable limits. +bool String::flatten(android::Res_value* outValue) const { + // Verify that our StringPool index is within encode-able limits. if (value.getIndex() > std::numeric_limits<uint32_t>::max()) { return false; } - outValue.dataType = android::Res_value::TYPE_STRING; - outValue.data = static_cast<uint32_t>(value.getIndex()); + outValue->dataType = android::Res_value::TYPE_STRING; + outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); return true; } @@ -132,20 +143,20 @@ String* String::clone(StringPool* newPool) const { return new String(newPool->makeRef(*value)); } -void String::print(std::ostream& out) const { - out << "(string) \"" << *value << "\""; +void String::print(std::ostream* out) const { + *out << "(string) \"" << *value << "\""; } StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) { } -bool StyledString::flatten(android::Res_value& outValue) const { +bool StyledString::flatten(android::Res_value* outValue) const { if (value.getIndex() > std::numeric_limits<uint32_t>::max()) { return false; } - outValue.dataType = android::Res_value::TYPE_STRING; - outValue.data = static_cast<uint32_t>(value.getIndex()); + outValue->dataType = android::Res_value::TYPE_STRING; + outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); return true; } @@ -153,20 +164,20 @@ StyledString* StyledString::clone(StringPool* newPool) const { return new StyledString(newPool->makeRef(value)); } -void StyledString::print(std::ostream& out) const { - out << "(styled string) \"" << *value->str << "\""; +void StyledString::print(std::ostream* out) const { + *out << "(styled string) \"" << *value->str << "\""; } FileReference::FileReference(const StringPool::Ref& _path) : path(_path) { } -bool FileReference::flatten(android::Res_value& outValue) const { +bool FileReference::flatten(android::Res_value* outValue) const { if (path.getIndex() > std::numeric_limits<uint32_t>::max()) { return false; } - outValue.dataType = android::Res_value::TYPE_STRING; - outValue.data = static_cast<uint32_t>(path.getIndex()); + outValue->dataType = android::Res_value::TYPE_STRING; + outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex())); return true; } @@ -174,15 +185,21 @@ FileReference* FileReference::clone(StringPool* newPool) const { return new FileReference(newPool->makeRef(*path)); } -void FileReference::print(std::ostream& out) const { - out << "(file) " << *path; +void FileReference::print(std::ostream* out) const { + *out << "(file) " << *path; } BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) { } -bool BinaryPrimitive::flatten(android::Res_value& outValue) const { - outValue = value; +BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) { + value.dataType = dataType; + value.data = data; +} + +bool BinaryPrimitive::flatten(android::Res_value* outValue) const { + outValue->dataType = value.dataType; + outValue->data = util::hostToDevice32(value.data); return true; } @@ -190,29 +207,29 @@ BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const { return new BinaryPrimitive(value); } -void BinaryPrimitive::print(std::ostream& out) const { +void BinaryPrimitive::print(std::ostream* out) const { switch (value.dataType) { case android::Res_value::TYPE_NULL: - out << "(null)"; + *out << "(null)"; break; case android::Res_value::TYPE_INT_DEC: - out << "(integer) " << value.data; + *out << "(integer) " << value.data; break; case android::Res_value::TYPE_INT_HEX: - out << "(integer) " << std::hex << value.data << std::dec; + *out << "(integer) " << std::hex << value.data << std::dec; break; case android::Res_value::TYPE_INT_BOOLEAN: - out << "(boolean) " << (value.data != 0 ? "true" : "false"); + *out << "(boolean) " << (value.data != 0 ? "true" : "false"); break; case android::Res_value::TYPE_INT_COLOR_ARGB8: case android::Res_value::TYPE_INT_COLOR_RGB8: case android::Res_value::TYPE_INT_COLOR_ARGB4: case android::Res_value::TYPE_INT_COLOR_RGB4: - out << "(color) #" << std::hex << value.data << std::dec; + *out << "(color) #" << std::hex << value.data << std::dec; break; default: - out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x" - << std::hex << value.data << std::dec; + *out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x" + << std::hex << value.data << std::dec; break; } } @@ -231,9 +248,9 @@ Attribute* Attribute::clone(StringPool* /*newPool*/) const { return attr; } -void Attribute::printMask(std::ostream& out) const { +void Attribute::printMask(std::ostream* out) const { if (typeMask == android::ResTable_map::TYPE_ANY) { - out << "any"; + *out << "any"; return; } @@ -242,103 +259,105 @@ void Attribute::printMask(std::ostream& out) const { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "reference"; + *out << "reference"; } if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "string"; + *out << "string"; } if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "integer"; + *out << "integer"; } if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "boolean"; + *out << "boolean"; } if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "color"; + *out << "color"; } if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "float"; + *out << "float"; } if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "dimension"; + *out << "dimension"; } if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "fraction"; + *out << "fraction"; } if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "enum"; + *out << "enum"; } if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) { if (!set) { set = true; } else { - out << "|"; + *out << "|"; } - out << "flags"; + *out << "flags"; } } -void Attribute::print(std::ostream& out) const { - out << "(attr) "; +void Attribute::print(std::ostream* out) const { + *out << "(attr) "; printMask(out); - out << " [" - << util::joiner(symbols.begin(), symbols.end(), ", ") - << "]"; + if (!symbols.empty()) { + *out << " [" + << util::joiner(symbols.begin(), symbols.end(), ", ") + << "]"; + } if (weak) { - out << " [weak]"; + *out << " [weak]"; } } @@ -355,19 +374,24 @@ Style* Style::clone(StringPool* newPool) const { return style; } -void Style::print(std::ostream& out) const { - out << "(style) "; - if (!parent.name.entry.empty()) { - out << parent.name; +void Style::print(std::ostream* out) const { + *out << "(style) "; + if (parent && parent.value().name) { + *out << parent.value().name.value(); } - out << " [" + *out << " [" << util::joiner(entries.begin(), entries.end(), ", ") << "]"; } static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) { - out << value.key.name << " = "; - value.value->print(out); + if (value.key.name) { + out << value.key.name.value(); + } else { + out << "???"; + } + out << " = "; + value.value->print(&out); return out; } @@ -379,8 +403,8 @@ Array* Array::clone(StringPool* newPool) const { return array; } -void Array::print(std::ostream& out) const { - out << "(array) [" +void Array::print(std::ostream* out) const { + *out << "(array) [" << util::joiner(items.begin(), items.end(), ", ") << "]"; } @@ -396,8 +420,8 @@ Plural* Plural::clone(StringPool* newPool) const { return p; } -void Plural::print(std::ostream& out) const { - out << "(plural)"; +void Plural::print(std::ostream* out) const { + *out << "(plural)"; } static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) { @@ -410,8 +434,8 @@ Styleable* Styleable::clone(StringPool* /*newPool*/) const { return styleable; } -void Styleable::print(std::ostream& out) const { - out << "(styleable) " << " [" +void Styleable::print(std::ostream* out) const { + *out << "(styleable) " << " [" << util::joiner(entries.begin(), entries.end(), ", ") << "]"; } diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index ef6594e6f231..0dae091dffb4 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -17,6 +17,7 @@ #ifndef AAPT_RESOURCE_VALUES_H #define AAPT_RESOURCE_VALUES_H +#include "util/Maybe.h" #include "Resource.h" #include "StringPool.h" @@ -27,9 +28,7 @@ namespace aapt { -struct ValueVisitor; -struct ConstValueVisitor; -struct ValueVisitorArgs; +struct RawValueVisitor; /** * A resource value. This is an all-encompassing representation @@ -39,13 +38,15 @@ struct ValueVisitorArgs; * but it is the simplest strategy. */ struct Value { + virtual ~Value() = default; + /** * Whether or not this is an Item. */ virtual bool isItem() const; /** - * Whether this value is weak and can be overriden without + * Whether this value is weak and can be overridden without * warning or error. Default for base class is false. */ virtual bool isWeak() const; @@ -53,12 +54,7 @@ struct Value { /** * Calls the appropriate overload of ValueVisitor. */ - virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) = 0; - - /** - * Const version of accept(). - */ - virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const = 0; + virtual void accept(RawValueVisitor* visitor) = 0; /** * Clone the value. @@ -68,7 +64,7 @@ struct Value { /** * Human readable printout of this value. */ - virtual void print(std::ostream& out) const = 0; + virtual void print(std::ostream* out) const = 0; }; /** @@ -76,8 +72,7 @@ struct Value { */ template <typename Derived> struct BaseValue : public Value { - virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override; - virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override; + void accept(RawValueVisitor* visitor) override; }; /** @@ -96,9 +91,9 @@ struct Item : public Value { /** * Fills in an android::Res_value structure with this Item's binary representation. - * Returns false if an error ocurred. + * Returns false if an error occurred. */ - virtual bool flatten(android::Res_value& outValue) const = 0; + virtual bool flatten(android::Res_value* outValue) const = 0; }; /** @@ -106,8 +101,7 @@ struct Item : public Value { */ template <typename Derived> struct BaseItem : public Item { - virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override; - virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override; + void accept(RawValueVisitor* visitor) override; }; /** @@ -122,8 +116,8 @@ struct Reference : public BaseItem<Reference> { kAttribute, }; - ResourceName name; - ResourceId id; + Maybe<ResourceName> name; + Maybe<ResourceId> id; Reference::Type referenceType; bool privateReference = false; @@ -131,9 +125,9 @@ struct Reference : public BaseItem<Reference> { Reference(const ResourceNameRef& n, Type type = Type::kResource); Reference(const ResourceId& i, Type type = Type::kResource); - bool flatten(android::Res_value& outValue) const override; + bool flatten(android::Res_value* outValue) const override; Reference* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; /** @@ -141,9 +135,9 @@ struct Reference : public BaseItem<Reference> { */ struct Id : public BaseItem<Id> { bool isWeak() const override; - bool flatten(android::Res_value& out) const override; + bool flatten(android::Res_value* out) const override; Id* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; /** @@ -156,9 +150,9 @@ struct RawString : public BaseItem<RawString> { RawString(const StringPool::Ref& ref); - bool flatten(android::Res_value& outValue) const override; + bool flatten(android::Res_value* outValue) const override; RawString* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; struct String : public BaseItem<String> { @@ -166,9 +160,9 @@ struct String : public BaseItem<String> { String(const StringPool::Ref& ref); - bool flatten(android::Res_value& outValue) const override; + bool flatten(android::Res_value* outValue) const override; String* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; struct StyledString : public BaseItem<StyledString> { @@ -176,9 +170,9 @@ struct StyledString : public BaseItem<StyledString> { StyledString(const StringPool::StyleRef& ref); - bool flatten(android::Res_value& outValue) const override; + bool flatten(android::Res_value* outValue) const override; StyledString* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; struct FileReference : public BaseItem<FileReference> { @@ -187,9 +181,9 @@ struct FileReference : public BaseItem<FileReference> { FileReference() = default; FileReference(const StringPool::Ref& path); - bool flatten(android::Res_value& outValue) const override; + bool flatten(android::Res_value* outValue) const override; FileReference* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; /** @@ -200,10 +194,11 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> { BinaryPrimitive() = default; BinaryPrimitive(const android::Res_value& val); + BinaryPrimitive(uint8_t dataType, uint32_t data); - bool flatten(android::Res_value& outValue) const override; + bool flatten(android::Res_value* outValue) const override; BinaryPrimitive* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; struct Attribute : public BaseValue<Attribute> { @@ -212,7 +207,7 @@ struct Attribute : public BaseValue<Attribute> { uint32_t value; }; - bool weak; + bool weak; uint32_t typeMask; uint32_t minInt; uint32_t maxInt; @@ -221,9 +216,9 @@ struct Attribute : public BaseValue<Attribute> { Attribute(bool w, uint32_t t = 0u); bool isWeak() const override; - virtual Attribute* clone(StringPool* newPool) const override; - void printMask(std::ostream& out) const; - virtual void print(std::ostream& out) const override; + Attribute* clone(StringPool* newPool) const override; + void printMask(std::ostream* out) const; + void print(std::ostream* out) const override; }; struct Style : public BaseValue<Style> { @@ -232,7 +227,7 @@ struct Style : public BaseValue<Style> { std::unique_ptr<Item> value; }; - Reference parent; + Maybe<Reference> parent; /** * If set to true, the parent was auto inferred from the @@ -243,14 +238,14 @@ struct Style : public BaseValue<Style> { std::vector<Entry> entries; Style* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; struct Array : public BaseValue<Array> { std::vector<std::unique_ptr<Item>> items; Array* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; struct Plural : public BaseValue<Plural> { @@ -267,180 +262,31 @@ struct Plural : public BaseValue<Plural> { std::array<std::unique_ptr<Item>, Count> values; Plural* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; struct Styleable : public BaseValue<Styleable> { std::vector<Reference> entries; Styleable* clone(StringPool* newPool) const override; - void print(std::ostream& out) const override; + void print(std::ostream* out) const override; }; /** * Stream operator for printing Value objects. */ inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) { - value.print(out); + value.print(&out); return out; } inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) { - return out << s.symbol.name.entry << "=" << s.value; -} - -/** - * The argument object that gets passed through the value - * back to the ValueVisitor. Subclasses of ValueVisitor should - * subclass ValueVisitorArgs to contain the data they need - * to operate. - */ -struct ValueVisitorArgs {}; - -/** - * Visits a value and runs the appropriate method based on its type. - */ -struct ValueVisitor { - virtual void visit(Reference& reference, ValueVisitorArgs& args) { - visitItem(reference, args); - } - - virtual void visit(RawString& string, ValueVisitorArgs& args) { - visitItem(string, args); - } - - virtual void visit(String& string, ValueVisitorArgs& args) { - visitItem(string, args); - } - - virtual void visit(StyledString& string, ValueVisitorArgs& args) { - visitItem(string, args); - } - - virtual void visit(FileReference& file, ValueVisitorArgs& args) { - visitItem(file, args); - } - - virtual void visit(Id& id, ValueVisitorArgs& args) { - visitItem(id, args); - } - - virtual void visit(BinaryPrimitive& primitive, ValueVisitorArgs& args) { - visitItem(primitive, args); - } - - virtual void visit(Attribute& attr, ValueVisitorArgs& args) {} - virtual void visit(Style& style, ValueVisitorArgs& args) {} - virtual void visit(Array& array, ValueVisitorArgs& args) {} - virtual void visit(Plural& array, ValueVisitorArgs& args) {} - virtual void visit(Styleable& styleable, ValueVisitorArgs& args) {} - - virtual void visitItem(Item& item, ValueVisitorArgs& args) {} -}; - -/** - * Const version of ValueVisitor. - */ -struct ConstValueVisitor { - virtual void visit(const Reference& reference, ValueVisitorArgs& args) { - visitItem(reference, args); - } - - virtual void visit(const RawString& string, ValueVisitorArgs& args) { - visitItem(string, args); - } - - virtual void visit(const String& string, ValueVisitorArgs& args) { - visitItem(string, args); - } - - virtual void visit(const StyledString& string, ValueVisitorArgs& args) { - visitItem(string, args); - } - - virtual void visit(const FileReference& file, ValueVisitorArgs& args) { - visitItem(file, args); + if (s.symbol.name) { + out << s.symbol.name.value().entry; + } else { + out << "???"; } - - virtual void visit(const Id& id, ValueVisitorArgs& args) { - visitItem(id, args); - } - - virtual void visit(const BinaryPrimitive& primitive, ValueVisitorArgs& args) { - visitItem(primitive, args); - } - - virtual void visit(const Attribute& attr, ValueVisitorArgs& args) {} - virtual void visit(const Style& style, ValueVisitorArgs& args) {} - virtual void visit(const Array& array, ValueVisitorArgs& args) {} - virtual void visit(const Plural& array, ValueVisitorArgs& args) {} - virtual void visit(const Styleable& styleable, ValueVisitorArgs& args) {} - - virtual void visitItem(const Item& item, ValueVisitorArgs& args) {} -}; - -/** - * Convenience Visitor that forwards a specific type to a function. - * Args are not used as the function can bind variables. Do not use - * directly, use the wrapper visitFunc() method. - */ -template <typename T, typename TFunc> -struct ValueVisitorFunc : ValueVisitor { - TFunc func; - - ValueVisitorFunc(TFunc f) : func(f) { - } - - void visit(T& value, ValueVisitorArgs&) override { - func(value); - } -}; - -/** - * Const version of ValueVisitorFunc. - */ -template <typename T, typename TFunc> -struct ConstValueVisitorFunc : ConstValueVisitor { - TFunc func; - - ConstValueVisitorFunc(TFunc f) : func(f) { - } - - void visit(const T& value, ValueVisitorArgs&) override { - func(value); - } -}; - -template <typename T, typename TFunc> -void visitFunc(Value& value, TFunc f) { - ValueVisitorFunc<T, TFunc> visitor(f); - value.accept(visitor, ValueVisitorArgs{}); -} - -template <typename T, typename TFunc> -void visitFunc(const Value& value, TFunc f) { - ConstValueVisitorFunc<T, TFunc> visitor(f); - value.accept(visitor, ValueVisitorArgs{}); -} - -template <typename Derived> -void BaseValue<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) { - visitor.visit(static_cast<Derived&>(*this), args); -} - -template <typename Derived> -void BaseValue<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const { - visitor.visit(static_cast<const Derived&>(*this), args); -} - -template <typename Derived> -void BaseItem<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) { - visitor.visit(static_cast<Derived&>(*this), args); -} - -template <typename Derived> -void BaseItem<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const { - visitor.visit(static_cast<const Derived&>(*this), args); + return out << "=" << s.value; } } // namespace aapt diff --git a/tools/aapt2/ScopedXmlPullParser.cpp b/tools/aapt2/ScopedXmlPullParser.cpp deleted file mode 100644 index 48da93edaa02..000000000000 --- a/tools/aapt2/ScopedXmlPullParser.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ScopedXmlPullParser.h" - -#include <string> - -namespace aapt { - -ScopedXmlPullParser::ScopedXmlPullParser(XmlPullParser* parser) : - mParser(parser), mDepth(parser->getDepth()), mDone(false) { -} - -ScopedXmlPullParser::~ScopedXmlPullParser() { - while (isGoodEvent(next())); -} - -XmlPullParser::Event ScopedXmlPullParser::next() { - if (mDone) { - return Event::kEndDocument; - } - - const Event event = mParser->next(); - if (mParser->getDepth() <= mDepth) { - mDone = true; - } - return event; -} - -XmlPullParser::Event ScopedXmlPullParser::getEvent() const { - return mParser->getEvent(); -} - -const std::string& ScopedXmlPullParser::getLastError() const { - return mParser->getLastError(); -} - -const std::u16string& ScopedXmlPullParser::getComment() const { - return mParser->getComment(); -} - -size_t ScopedXmlPullParser::getLineNumber() const { - return mParser->getLineNumber(); -} - -size_t ScopedXmlPullParser::getDepth() const { - const size_t depth = mParser->getDepth(); - if (depth < mDepth) { - return 0; - } - return depth - mDepth; -} - -const std::u16string& ScopedXmlPullParser::getText() const { - return mParser->getText(); -} - -const std::u16string& ScopedXmlPullParser::getNamespacePrefix() const { - return mParser->getNamespacePrefix(); -} - -const std::u16string& ScopedXmlPullParser::getNamespaceUri() const { - return mParser->getNamespaceUri(); -} - -bool ScopedXmlPullParser::applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const { - return mParser->applyPackageAlias(package, defaultPackage); -} - -const std::u16string& ScopedXmlPullParser::getElementNamespace() const { - return mParser->getElementNamespace(); -} - -const std::u16string& ScopedXmlPullParser::getElementName() const { - return mParser->getElementName(); -} - -size_t ScopedXmlPullParser::getAttributeCount() const { - return mParser->getAttributeCount(); -} - -XmlPullParser::const_iterator ScopedXmlPullParser::beginAttributes() const { - return mParser->beginAttributes(); -} - -XmlPullParser::const_iterator ScopedXmlPullParser::endAttributes() const { - return mParser->endAttributes(); -} - -} // namespace aapt diff --git a/tools/aapt2/ScopedXmlPullParser.h b/tools/aapt2/ScopedXmlPullParser.h deleted file mode 100644 index a040f6097fc3..000000000000 --- a/tools/aapt2/ScopedXmlPullParser.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AAPT_SCOPED_XML_PULL_PARSER_H -#define AAPT_SCOPED_XML_PULL_PARSER_H - -#include "XmlPullParser.h" - -#include <string> - -namespace aapt { - -/** - * An XmlPullParser that will not read past the depth - * of the underlying parser. When this parser is destroyed, - * it moves the underlying parser to the same depth it - * started with. - * - * You can write code like this: - * - * while (XmlPullParser::isGoodEvent(parser.next())) { - * if (parser.getEvent() != XmlPullParser::Event::StartElement) { - * continue; - * } - * - * ScopedXmlPullParser scoped(parser); - * if (parser.getElementName() == u"id") { - * // do work. - * } else { - * // do nothing, as all the sub elements will be skipped - * // when scoped goes out of scope. - * } - * } - */ -class ScopedXmlPullParser : public XmlPullParser { -public: - ScopedXmlPullParser(XmlPullParser* parser); - ScopedXmlPullParser(const ScopedXmlPullParser&) = delete; - ScopedXmlPullParser& operator=(const ScopedXmlPullParser&) = delete; - ~ScopedXmlPullParser(); - - Event getEvent() const override; - const std::string& getLastError() const override; - Event next() override; - - const std::u16string& getComment() const override; - size_t getLineNumber() const override; - size_t getDepth() const override; - - const std::u16string& getText() const override; - - const std::u16string& getNamespacePrefix() const override; - const std::u16string& getNamespaceUri() const override; - bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage) - const override; - - const std::u16string& getElementNamespace() const override; - const std::u16string& getElementName() const override; - - const_iterator beginAttributes() const override; - const_iterator endAttributes() const override; - size_t getAttributeCount() const override; - -private: - XmlPullParser* mParser; - size_t mDepth; - bool mDone; -}; - -} // namespace aapt - -#endif // AAPT_SCOPED_XML_PULL_PARSER_H diff --git a/tools/aapt2/ScopedXmlPullParser_test.cpp b/tools/aapt2/ScopedXmlPullParser_test.cpp deleted file mode 100644 index 342f305bb11d..000000000000 --- a/tools/aapt2/ScopedXmlPullParser_test.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ScopedXmlPullParser.h" -#include "SourceXmlPullParser.h" - -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -namespace aapt { - -TEST(ScopedXmlPullParserTest, StopIteratingAtNoNZeroDepth) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl - << "<resources><string></string></resources>" << std::endl; - - SourceXmlPullParser sourceParser(input); - EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName()); - - { - ScopedXmlPullParser scopedParser(&sourceParser); - EXPECT_EQ(XmlPullParser::Event::kEndElement, scopedParser.next()); - EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kEndDocument, scopedParser.next()); - } - - EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next()); -} - -TEST(ScopedXmlPullParserTest, FinishCurrentElementOnDestruction) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl - << "<resources><string></string></resources>" << std::endl; - - SourceXmlPullParser sourceParser(input); - EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName()); - - { - ScopedXmlPullParser scopedParser(&sourceParser); - EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName()); - } - - EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next()); -} - -TEST(ScopedXmlPullParserTest, NestedParsersOperateCorrectly) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl - << "<resources><string><foo></foo></string></resources>" << std::endl; - - SourceXmlPullParser sourceParser(input); - EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName()); - - { - ScopedXmlPullParser scopedParser(&sourceParser); - EXPECT_EQ(std::u16string(u"string"), scopedParser.getElementName()); - while (XmlPullParser::isGoodEvent(scopedParser.next())) { - if (scopedParser.getEvent() != XmlPullParser::Event::kStartElement) { - continue; - } - - ScopedXmlPullParser subScopedParser(&scopedParser); - EXPECT_EQ(std::u16string(u"foo"), subScopedParser.getElementName()); - } - } - - EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next()); - EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName()); - - EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next()); -} - -} // namespace aapt diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index 9bdae490412f..c2a22bf2a373 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -34,8 +34,9 @@ static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = { { 0x02bd, SDK_FROYO }, { 0x02cb, SDK_GINGERBREAD }, { 0x0361, SDK_HONEYCOMB }, - { 0x0366, SDK_HONEYCOMB_MR1 }, - { 0x03a6, SDK_HONEYCOMB_MR2 }, + { 0x0363, SDK_HONEYCOMB_MR1 }, + { 0x0366, SDK_HONEYCOMB_MR2 }, + { 0x03a6, SDK_ICE_CREAM_SANDWICH }, { 0x03ae, SDK_JELLY_BEAN }, { 0x03cc, SDK_JELLY_BEAN_MR1 }, { 0x03da, SDK_JELLY_BEAN_MR2 }, diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h index 3606488591ba..8af203cdad0e 100644 --- a/tools/aapt2/Source.h +++ b/tools/aapt2/Source.h @@ -17,72 +17,58 @@ #ifndef AAPT_SOURCE_H #define AAPT_SOURCE_H +#include "util/Maybe.h" +#include "util/StringPiece.h" + #include <ostream> #include <string> -#include <tuple> namespace aapt { -struct SourceLineColumn; -struct SourceLine; - /** * Represents a file on disk. Used for logging and * showing errors. */ struct Source { std::string path; + Maybe<size_t> line; - inline SourceLine line(size_t line) const; -}; + Source() = default; -/** - * Represents a file on disk and a line number in that file. - * Used for logging and showing errors. - */ -struct SourceLine { - std::string path; - size_t line; + inline Source(const StringPiece& path) : path(path.toString()) { + } - inline SourceLineColumn column(size_t column) const; -}; + inline Source(const StringPiece& path, size_t line) : path(path.toString()), line(line) { + } -/** - * Represents a file on disk and a line:column number in that file. - * Used for logging and showing errors. - */ -struct SourceLineColumn { - std::string path; - size_t line; - size_t column; + inline Source withLine(size_t line) const { + return Source(path, line); + } }; // // Implementations // -SourceLine Source::line(size_t line) const { - return SourceLine{ path, line }; -} - -SourceLineColumn SourceLine::column(size_t column) const { - return SourceLineColumn{ path, line, column }; -} - inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) { - return out << source.path; -} - -inline ::std::ostream& operator<<(::std::ostream& out, const SourceLine& source) { - return out << source.path << ":" << source.line; -} - -inline ::std::ostream& operator<<(::std::ostream& out, const SourceLineColumn& source) { - return out << source.path << ":" << source.line << ":" << source.column; + out << source.path; + if (source.line) { + out << ":" << source.line.value(); + } + return out; } -inline bool operator<(const SourceLine& lhs, const SourceLine& rhs) { - return std::tie(lhs.path, lhs.line) < std::tie(rhs.path, rhs.line); +inline bool operator<(const Source& lhs, const Source& rhs) { + int cmp = lhs.path.compare(rhs.path); + if (cmp < 0) return true; + if (cmp > 0) return false; + if (lhs.line) { + if (rhs.line) { + return lhs.line.value() < rhs.line.value(); + } + return false; + } + return bool(rhs.line); } } // namespace aapt diff --git a/tools/aapt2/SourceXmlPullParser.h b/tools/aapt2/SourceXmlPullParser.h deleted file mode 100644 index d8ed45952b31..000000000000 --- a/tools/aapt2/SourceXmlPullParser.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AAPT_SOURCE_XML_PULL_PARSER_H -#define AAPT_SOURCE_XML_PULL_PARSER_H - -#include "XmlPullParser.h" - -#include <istream> -#include <expat.h> -#include <queue> -#include <stack> -#include <string> -#include <vector> - -namespace aapt { - -class SourceXmlPullParser : public XmlPullParser { -public: - SourceXmlPullParser(std::istream& in); - SourceXmlPullParser(const SourceXmlPullParser& rhs) = delete; - ~SourceXmlPullParser(); - - Event getEvent() const override; - const std::string& getLastError() const override ; - Event next() override ; - - const std::u16string& getComment() const override; - size_t getLineNumber() const override; - size_t getDepth() const override; - - const std::u16string& getText() const override; - - const std::u16string& getNamespacePrefix() const override; - const std::u16string& getNamespaceUri() const override; - bool applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const override; - - - const std::u16string& getElementNamespace() const override; - const std::u16string& getElementName() const override; - - const_iterator beginAttributes() const override; - const_iterator endAttributes() const override; - size_t getAttributeCount() const override; - -private: - static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri); - static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs); - static void XMLCALL characterDataHandler(void* userData, const char* s, int len); - static void XMLCALL endElementHandler(void* userData, const char* name); - static void XMLCALL endNamespaceHandler(void* userData, const char* prefix); - static void XMLCALL commentDataHandler(void* userData, const char* comment); - - struct EventData { - Event event; - size_t lineNumber; - size_t depth; - std::u16string data1; - std::u16string data2; - std::u16string comment; - std::vector<Attribute> attributes; - }; - - std::istream& mIn; - XML_Parser mParser; - char mBuffer[16384]; - std::queue<EventData> mEventQueue; - std::string mLastError; - const std::u16string mEmpty; - size_t mDepth; - std::stack<std::u16string> mNamespaceUris; - std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases; -}; - -} // namespace aapt - -#endif // AAPT_SOURCE_XML_PULL_PARSER_H diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp index c19aa98a70ac..8552f470b123 100644 --- a/tools/aapt2/StringPool.cpp +++ b/tools/aapt2/StringPool.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ -#include "BigBuffer.h" -#include "StringPiece.h" +#include "util/BigBuffer.h" +#include "util/StringPiece.h" #include "StringPool.h" -#include "Util.h" +#include "util/Util.h" #include <algorithm> #include <androidfw/ResourceTypes.h> @@ -219,7 +219,7 @@ void StringPool::prune() { auto indexIter = std::begin(mIndexedStrings); while (indexIter != iterEnd) { if (indexIter->second->ref <= 0) { - mIndexedStrings.erase(indexIter++); + indexIter = mIndexedStrings.erase(indexIter); } else { ++indexIter; } @@ -241,6 +241,12 @@ void StringPool::prune() { // a deleted string from the StyleEntry. mStrings.erase(endIter2, std::end(mStrings)); mStyles.erase(endIter3, std::end(mStyles)); + + // Reassign the indices. + const size_t len = mStrings.size(); + for (size_t index = 0; index < len; index++) { + mStrings[index]->index = index; + } } void StringPool::sort(const std::function<bool(const Entry&, const Entry&)>& cmp) { diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h index 14304a6e6b1a..509e3041e081 100644 --- a/tools/aapt2/StringPool.h +++ b/tools/aapt2/StringPool.h @@ -17,9 +17,9 @@ #ifndef AAPT_STRING_POOL_H #define AAPT_STRING_POOL_H -#include "BigBuffer.h" +#include "util/BigBuffer.h" #include "ConfigDescription.h" -#include "StringPiece.h" +#include "util/StringPiece.h" #include <functional> #include <map> diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp index 9552937d4ad4..c722fbeca690 100644 --- a/tools/aapt2/StringPool_test.cpp +++ b/tools/aapt2/StringPool_test.cpp @@ -15,7 +15,7 @@ */ #include "StringPool.h" -#include "Util.h" +#include "util/Util.h" #include <gtest/gtest.h> #include <string> @@ -67,15 +67,23 @@ TEST(StringPoolTest, MaintainInsertionOrderIndex) { TEST(StringPoolTest, PruneStringsWithNoReferences) { StringPool pool; + StringPool::Ref refA = pool.makeRef(u"foo"); { StringPool::Ref ref = pool.makeRef(u"wut"); EXPECT_EQ(*ref, u"wut"); - EXPECT_EQ(1u, pool.size()); + EXPECT_EQ(2u, pool.size()); } + StringPool::Ref refB = pool.makeRef(u"bar"); - EXPECT_EQ(1u, pool.size()); + EXPECT_EQ(3u, pool.size()); pool.prune(); - EXPECT_EQ(0u, pool.size()); + EXPECT_EQ(2u, pool.size()); + StringPool::const_iterator iter = begin(pool); + EXPECT_EQ((*iter)->value, u"foo"); + EXPECT_LT((*iter)->index, 2u); + ++iter; + EXPECT_EQ((*iter)->value, u"bar"); + EXPECT_LT((*iter)->index, 2u); } TEST(StringPoolTest, SortAndMaintainIndexesInReferences) { diff --git a/tools/aapt2/TableFlattener.cpp b/tools/aapt2/TableFlattener.cpp deleted file mode 100644 index b7c04f06cff5..000000000000 --- a/tools/aapt2/TableFlattener.cpp +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "BigBuffer.h" -#include "ConfigDescription.h" -#include "Logger.h" -#include "ResourceTable.h" -#include "ResourceTypeExtensions.h" -#include "ResourceValues.h" -#include "StringPool.h" -#include "TableFlattener.h" -#include "Util.h" - -#include <algorithm> -#include <androidfw/ResourceTypes.h> -#include <sstream> - -namespace aapt { - -struct FlatEntry { - const ResourceEntry* entry; - const Value* value; - uint32_t entryKey; - uint32_t sourcePathKey; - uint32_t sourceLine; -}; - -/** - * Visitor that knows how to encode Map values. - */ -class MapFlattener : public ConstValueVisitor { -public: - MapFlattener(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols) : - mOut(out), mSymbols(symbols) { - mMap = mOut->nextBlock<android::ResTable_map_entry>(); - mMap->key.index = flatEntry.entryKey; - mMap->flags = android::ResTable_entry::FLAG_COMPLEX; - if (flatEntry.entry->publicStatus.isPublic) { - mMap->flags |= android::ResTable_entry::FLAG_PUBLIC; - } - if (flatEntry.value->isWeak()) { - mMap->flags |= android::ResTable_entry::FLAG_WEAK; - } - - ResTable_entry_source* sourceBlock = mOut->nextBlock<ResTable_entry_source>(); - sourceBlock->pathIndex = flatEntry.sourcePathKey; - sourceBlock->line = flatEntry.sourceLine; - - mMap->size = sizeof(*mMap) + sizeof(*sourceBlock); - } - - void flattenParent(const Reference& ref) { - if (!ref.id.isValid()) { - mSymbols->push_back({ - ResourceNameRef(ref.name), - (mOut->size() - mMap->size) + sizeof(*mMap) - sizeof(android::ResTable_entry) - }); - } - mMap->parent.ident = ref.id.id; - } - - void flattenEntry(const Reference& key, const Item& value) { - mMap->count++; - - android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>(); - - // Write the key. - if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) { - assert(!key.name.entry.empty()); - mSymbols->push_back(std::make_pair(ResourceNameRef(key.name), - mOut->size() - sizeof(*outMapEntry))); - } - outMapEntry->name.ident = key.id.id; - - // Write the value. - value.flatten(outMapEntry->value); - - if (outMapEntry->value.data == 0x0) { - visitFunc<Reference>(value, [&](const Reference& reference) { - mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name), - mOut->size() - sizeof(outMapEntry->value.data))); - }); - } - outMapEntry->value.size = sizeof(outMapEntry->value); - } - - void flattenValueOnly(const Item& value) { - mMap->count++; - - android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>(); - - // Write the value. - value.flatten(outMapEntry->value); - - if (outMapEntry->value.data == 0x0) { - visitFunc<Reference>(value, [&](const Reference& reference) { - mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name), - mOut->size() - sizeof(outMapEntry->value.data))); - }); - } - outMapEntry->value.size = sizeof(outMapEntry->value); - } - - static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) { - return lhs->key.id < rhs->key.id; - } - - void visit(const Style& style, ValueVisitorArgs&) override { - if (style.parent.name.isValid()) { - flattenParent(style.parent); - } - - // First sort the entries by ID. - std::vector<const Style::Entry*> sortedEntries; - for (const auto& styleEntry : style.entries) { - auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), - &styleEntry, compareStyleEntries); - sortedEntries.insert(iter, &styleEntry); - } - - for (const Style::Entry* styleEntry : sortedEntries) { - flattenEntry(styleEntry->key, *styleEntry->value); - } - } - - void visit(const Attribute& attr, ValueVisitorArgs&) override { - android::Res_value tempVal; - tempVal.dataType = android::Res_value::TYPE_INT_DEC; - tempVal.data = attr.typeMask; - flattenEntry(Reference(ResourceId{android::ResTable_map::ATTR_TYPE}), - BinaryPrimitive(tempVal)); - - for (const auto& symbol : attr.symbols) { - tempVal.data = symbol.value; - flattenEntry(symbol.symbol, BinaryPrimitive(tempVal)); - } - } - - void visit(const Styleable& styleable, ValueVisitorArgs&) override { - for (const auto& attr : styleable.entries) { - flattenEntry(attr, BinaryPrimitive(android::Res_value{})); - } - } - - void visit(const Array& array, ValueVisitorArgs&) override { - for (const auto& item : array.items) { - flattenValueOnly(*item); - } - } - - void visit(const Plural& plural, ValueVisitorArgs&) override { - const size_t count = plural.values.size(); - for (size_t i = 0; i < count; i++) { - if (!plural.values[i]) { - continue; - } - - ResourceId q; - switch (i) { - case Plural::Zero: - q.id = android::ResTable_map::ATTR_ZERO; - break; - - case Plural::One: - q.id = android::ResTable_map::ATTR_ONE; - break; - - case Plural::Two: - q.id = android::ResTable_map::ATTR_TWO; - break; - - case Plural::Few: - q.id = android::ResTable_map::ATTR_FEW; - break; - - case Plural::Many: - q.id = android::ResTable_map::ATTR_MANY; - break; - - case Plural::Other: - q.id = android::ResTable_map::ATTR_OTHER; - break; - - default: - assert(false); - break; - } - - flattenEntry(Reference(q), *plural.values[i]); - } - } - -private: - BigBuffer* mOut; - SymbolEntryVector* mSymbols; - android::ResTable_map_entry* mMap; -}; - -/** - * Flattens a value, with special handling for References. - */ -struct ValueFlattener : ConstValueVisitor { - ValueFlattener(BigBuffer* out, SymbolEntryVector* symbols) : - result(false), mOut(out), mOutValue(nullptr), mSymbols(symbols) { - mOutValue = mOut->nextBlock<android::Res_value>(); - } - - virtual void visit(const Reference& ref, ValueVisitorArgs& a) override { - visitItem(ref, a); - if (mOutValue->data == 0x0) { - mSymbols->push_back({ - ResourceNameRef(ref.name), - mOut->size() - sizeof(mOutValue->data)}); - } - } - - virtual void visitItem(const Item& item, ValueVisitorArgs&) override { - result = item.flatten(*mOutValue); - mOutValue->res0 = 0; - mOutValue->size = sizeof(*mOutValue); - } - - bool result; - -private: - BigBuffer* mOut; - android::Res_value* mOutValue; - SymbolEntryVector* mSymbols; -}; - -TableFlattener::TableFlattener(Options options) -: mOptions(options) { -} - -bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry, - SymbolEntryVector* symbols) { - if (flatEntry.value->isItem()) { - android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>(); - - if (flatEntry.entry->publicStatus.isPublic) { - entry->flags |= android::ResTable_entry::FLAG_PUBLIC; - } - - if (flatEntry.value->isWeak()) { - entry->flags |= android::ResTable_entry::FLAG_WEAK; - } - - entry->key.index = flatEntry.entryKey; - entry->size = sizeof(*entry); - - if (mOptions.useExtendedChunks) { - // Write the extra source block. This will be ignored by - // the Android runtime. - ResTable_entry_source* sourceBlock = out->nextBlock<ResTable_entry_source>(); - sourceBlock->pathIndex = flatEntry.sourcePathKey; - sourceBlock->line = flatEntry.sourceLine; - entry->size += sizeof(*sourceBlock); - } - - const Item* item = static_cast<const Item*>(flatEntry.value); - ValueFlattener flattener(out, symbols); - item->accept(flattener, {}); - return flattener.result; - } - - MapFlattener flattener(out, flatEntry, symbols); - flatEntry.value->accept(flattener, {}); - return true; -} - -bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) { - const size_t beginning = out->size(); - - if (table.getPackageId() == ResourceTable::kUnsetPackageId) { - Logger::error() - << "ResourceTable has no package ID set." - << std::endl; - return false; - } - - SymbolEntryVector symbolEntries; - - StringPool typePool; - StringPool keyPool; - StringPool sourcePool; - - // Sort the types by their IDs. They will be inserted into the StringPool - // in this order. - std::vector<ResourceTableType*> sortedTypes; - for (const auto& type : table) { - if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) { - continue; - } - - auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(), - [](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool { - return lhs->typeId < rhs->typeId; - }); - sortedTypes.insert(iter, type.get()); - } - - BigBuffer typeBlock(1024); - size_t expectedTypeId = 1; - for (const ResourceTableType* type : sortedTypes) { - if (type->typeId == ResourceTableType::kUnsetTypeId - || type->typeId == 0) { - Logger::error() - << "resource type '" - << type->type - << "' from package '" - << table.getPackage() - << "' has no ID." - << std::endl; - return false; - } - - // If there is a gap in the type IDs, fill in the StringPool - // with empty values until we reach the ID we expect. - while (type->typeId > expectedTypeId) { - std::u16string typeName(u"?"); - typeName += expectedTypeId; - typePool.makeRef(typeName); - expectedTypeId++; - } - expectedTypeId++; - typePool.makeRef(toString(type->type)); - - android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>(); - spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE; - spec->header.headerSize = sizeof(*spec); - spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t)); - spec->id = type->typeId; - spec->entryCount = type->entries.size(); - - if (type->entries.empty()) { - continue; - } - - // Reserve space for the masks of each resource in this type. These - // show for which configuration axis the resource changes. - uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size()); - - // Sort the entries by entry ID and write their configuration masks. - std::vector<ResourceEntry*> entries; - const size_t entryCount = type->entries.size(); - for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) { - const auto& entry = type->entries[entryIndex]; - - if (entry->entryId == ResourceEntry::kUnsetEntryId) { - Logger::error() - << "resource '" - << ResourceName{ table.getPackage(), type->type, entry->name } - << "' has no ID." - << std::endl; - return false; - } - - auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(), - [](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool { - return lhs->entryId < rhs->entryId; - }); - entries.insert(iter, entry.get()); - - // Populate the config masks for this entry. - if (entry->publicStatus.isPublic) { - configMasks[entry->entryId] |= android::ResTable_typeSpec::SPEC_PUBLIC; - } - - const size_t configCount = entry->values.size(); - for (size_t i = 0; i < configCount; i++) { - const ConfigDescription& config = entry->values[i].config; - for (size_t j = i + 1; j < configCount; j++) { - configMasks[entry->entryId] |= config.diff(entry->values[j].config); - } - } - } - - const size_t beforePublicHeader = typeBlock.size(); - Public_header* publicHeader = nullptr; - if (mOptions.useExtendedChunks) { - publicHeader = typeBlock.nextBlock<Public_header>(); - publicHeader->header.type = RES_TABLE_PUBLIC_TYPE; - publicHeader->header.headerSize = sizeof(*publicHeader); - publicHeader->typeId = type->typeId; - } - - // The binary resource table lists resource entries for each configuration. - // We store them inverted, where a resource entry lists the values for each - // configuration available. Here we reverse this to match the binary table. - std::map<ConfigDescription, std::vector<FlatEntry>> data; - for (const ResourceEntry* entry : entries) { - size_t keyIndex = keyPool.makeRef(entry->name).getIndex(); - - if (keyIndex > std::numeric_limits<uint32_t>::max()) { - Logger::error() - << "resource key string pool exceeded max size." - << std::endl; - return false; - } - - if (publicHeader && entry->publicStatus.isPublic) { - // Write the public status of this entry. - Public_entry* publicEntry = typeBlock.nextBlock<Public_entry>(); - publicEntry->entryId = static_cast<uint32_t>(entry->entryId); - publicEntry->key.index = static_cast<uint32_t>(keyIndex); - publicEntry->source.index = static_cast<uint32_t>(sourcePool.makeRef( - util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex()); - publicEntry->sourceLine = static_cast<uint32_t>(entry->publicStatus.source.line); - publicHeader->count += 1; - } - - for (const auto& configValue : entry->values) { - data[configValue.config].push_back(FlatEntry{ - entry, - configValue.value.get(), - static_cast<uint32_t>(keyIndex), - static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16( - configValue.source.path)).getIndex()), - static_cast<uint32_t>(configValue.source.line) - }); - } - } - - if (publicHeader) { - typeBlock.align4(); - publicHeader->header.size = - static_cast<uint32_t>(typeBlock.size() - beforePublicHeader); - } - - // Begin flattening a configuration for the current type. - for (const auto& entry : data) { - const size_t typeHeaderStart = typeBlock.size(); - android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>(); - typeHeader->header.type = android::RES_TABLE_TYPE_TYPE; - typeHeader->header.headerSize = sizeof(*typeHeader); - typeHeader->id = type->typeId; - typeHeader->entryCount = type->entries.size(); - typeHeader->entriesStart = typeHeader->header.headerSize - + (sizeof(uint32_t) * type->entries.size()); - typeHeader->config = entry.first; - - uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size()); - memset(indices, 0xff, type->entries.size() * sizeof(uint32_t)); - - const size_t entryStart = typeBlock.size(); - for (const FlatEntry& flatEntry : entry.second) { - assert(flatEntry.entry->entryId < type->entries.size()); - indices[flatEntry.entry->entryId] = typeBlock.size() - entryStart; - if (!flattenValue(&typeBlock, flatEntry, &symbolEntries)) { - Logger::error() - << "failed to flatten resource '" - << ResourceNameRef { - table.getPackage(), type->type, flatEntry.entry->name } - << "' for configuration '" - << entry.first - << "'." - << std::endl; - return false; - } - } - - typeBlock.align4(); - typeHeader->header.size = typeBlock.size() - typeHeaderStart; - } - } - - const size_t beforeTable = out->size(); - android::ResTable_header* header = out->nextBlock<android::ResTable_header>(); - header->header.type = android::RES_TABLE_TYPE; - header->header.headerSize = sizeof(*header); - header->packageCount = 1; - - SymbolTable_entry* symbolEntryData = nullptr; - if (!symbolEntries.empty() && mOptions.useExtendedChunks) { - const size_t beforeSymbolTable = out->size(); - StringPool symbolPool; - SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>(); - symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE; - symbolHeader->header.headerSize = sizeof(*symbolHeader); - symbolHeader->count = symbolEntries.size(); - - symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count); - - size_t i = 0; - for (const auto& entry : symbolEntries) { - symbolEntryData[i].offset = entry.second; - StringPool::Ref ref = symbolPool.makeRef( - entry.first.package.toString() + u":" + - toString(entry.first.type).toString() + u"/" + - entry.first.entry.toString()); - symbolEntryData[i].stringIndex = ref.getIndex(); - i++; - } - - StringPool::flattenUtf8(out, symbolPool); - out->align4(); - symbolHeader->header.size = out->size() - beforeSymbolTable; - } - - if (sourcePool.size() > 0 && mOptions.useExtendedChunks) { - const size_t beforeSourcePool = out->size(); - android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>(); - sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE; - sourceHeader->headerSize = sizeof(*sourceHeader); - StringPool::flattenUtf8(out, sourcePool); - out->align4(); - sourceHeader->size = out->size() - beforeSourcePool; - } - - StringPool::flattenUtf8(out, table.getValueStringPool()); - - const size_t beforePackageIndex = out->size(); - android::ResTable_package* package = out->nextBlock<android::ResTable_package>(); - package->header.type = android::RES_TABLE_PACKAGE_TYPE; - package->header.headerSize = sizeof(*package); - - if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) { - Logger::error() - << "package ID 0x'" - << std::hex << table.getPackageId() << std::dec - << "' is invalid." - << std::endl; - return false; - } - package->id = table.getPackageId(); - - if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) { - Logger::error() - << "package name '" - << table.getPackage() - << "' is too long." - << std::endl; - return false; - } - memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()), - table.getPackage().length() * sizeof(char16_t)); - package->name[table.getPackage().length()] = 0; - - package->typeStrings = package->header.headerSize; - StringPool::flattenUtf16(out, typePool); - package->keyStrings = out->size() - beforePackageIndex; - StringPool::flattenUtf16(out, keyPool); - - if (symbolEntryData != nullptr) { - for (size_t i = 0; i < symbolEntries.size(); i++) { - symbolEntryData[i].offset += out->size() - beginning; - } - } - - out->appendBuffer(std::move(typeBlock)); - - package->header.size = out->size() - beforePackageIndex; - header->header.size = out->size() - beforeTable; - return true; -} - -} // namespace aapt diff --git a/tools/aapt2/TableFlattener.h b/tools/aapt2/TableFlattener.h deleted file mode 100644 index ccbb737059f9..000000000000 --- a/tools/aapt2/TableFlattener.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AAPT_TABLE_FLATTENER_H -#define AAPT_TABLE_FLATTENER_H - -#include "BigBuffer.h" -#include "ResourceTable.h" - -namespace aapt { - -using SymbolEntryVector = std::vector<std::pair<ResourceNameRef, uint32_t>>; - -struct FlatEntry; - -/** - * Flattens a ResourceTable into a binary format suitable - * for loading into a ResTable on the host or device. - */ -struct TableFlattener { - /** - * A set of options for this TableFlattener. - */ - struct Options { - /** - * Specifies whether to output extended chunks, like - * source information and mising symbol entries. Default - * is true. - * - * Set this to false when emitting the final table to be used - * on device. - */ - bool useExtendedChunks = true; - }; - - TableFlattener(Options options); - - bool flatten(BigBuffer* out, const ResourceTable& table); - -private: - bool flattenValue(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols); - - Options mOptions; -}; - -} // namespace aapt - -#endif // AAPT_TABLE_FLATTENER_H diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h new file mode 100644 index 000000000000..ee058aa1a37b --- /dev/null +++ b/tools/aapt2/ValueVisitor.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_VALUE_VISITOR_H +#define AAPT_VALUE_VISITOR_H + +#include "ResourceValues.h" + +namespace aapt { + +/** + * Visits a value and invokes the appropriate method based on its type. Does not traverse + * into compound types. Use ValueVisitor for that. + */ +struct RawValueVisitor { + virtual ~RawValueVisitor() = default; + + virtual void visitItem(Item* value) {} + virtual void visit(Reference* value) { visitItem(value); } + virtual void visit(RawString* value) { visitItem(value); } + virtual void visit(String* value) { visitItem(value); } + virtual void visit(StyledString* value) { visitItem(value); } + virtual void visit(FileReference* value) { visitItem(value); } + virtual void visit(Id* value) { visitItem(value); } + virtual void visit(BinaryPrimitive* value) { visitItem(value); } + + virtual void visit(Attribute* value) {} + virtual void visit(Style* value) {} + virtual void visit(Array* value) {} + virtual void visit(Plural* value) {} + virtual void visit(Styleable* value) {} +}; + +#define DECL_VISIT_COMPOUND_VALUE(T) \ + virtual void visit(T* value) { \ + visitSubValues(value); \ + } + +/** + * Visits values, and if they are compound values, visits the components as well. + */ +struct ValueVisitor : public RawValueVisitor { + // The compiler will think we're hiding an overload, when we actually intend + // to call into RawValueVisitor. This will expose the visit methods in the super + // class so the compiler knows we are trying to call them. + using RawValueVisitor::visit; + + void visitSubValues(Attribute* attribute) { + for (Attribute::Symbol& symbol : attribute->symbols) { + visit(&symbol.symbol); + } + } + + void visitSubValues(Style* style) { + if (style->parent) { + visit(&style->parent.value()); + } + + for (Style::Entry& entry : style->entries) { + visit(&entry.key); + entry.value->accept(this); + } + } + + void visitSubValues(Array* array) { + for (std::unique_ptr<Item>& item : array->items) { + item->accept(this); + } + } + + void visitSubValues(Plural* plural) { + for (std::unique_ptr<Item>& item : plural->values) { + if (item) { + item->accept(this); + } + } + } + + void visitSubValues(Styleable* styleable) { + for (Reference& reference : styleable->entries) { + visit(&reference); + } + } + + DECL_VISIT_COMPOUND_VALUE(Attribute); + DECL_VISIT_COMPOUND_VALUE(Style); + DECL_VISIT_COMPOUND_VALUE(Array); + DECL_VISIT_COMPOUND_VALUE(Plural); + DECL_VISIT_COMPOUND_VALUE(Styleable); +}; + +/** + * Do not use directly. Helper struct for dyn_cast. + */ +template <typename T> +struct DynCastVisitor : public RawValueVisitor { + T* value = nullptr; + + void visit(T* v) override { + value = v; + } +}; + +/** + * Returns a valid pointer to T if the Value is of subtype T. + * Otherwise, returns nullptr. + */ +template <typename T> +T* valueCast(Value* value) { + if (!value) { + return nullptr; + } + DynCastVisitor<T> visitor; + value->accept(&visitor); + return visitor.value; +} + +} // namespace aapt + +#endif // AAPT_VALUE_VISITOR_H diff --git a/tools/aapt2/ValueVisitor_test.cpp b/tools/aapt2/ValueVisitor_test.cpp new file mode 100644 index 000000000000..1624079727bb --- /dev/null +++ b/tools/aapt2/ValueVisitor_test.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> +#include <string> + +#include "ResourceValues.h" +#include "util/Util.h" +#include "ValueVisitor.h" +#include "test/Builders.h" + +namespace aapt { + +struct SingleReferenceVisitor : public ValueVisitor { + using ValueVisitor::visit; + + Reference* visited = nullptr; + + void visit(Reference* ref) override { + visited = ref; + } +}; + +struct StyleVisitor : public ValueVisitor { + using ValueVisitor::visit; + + std::list<Reference*> visitedRefs; + Style* visitedStyle = nullptr; + + void visit(Reference* ref) override { + visitedRefs.push_back(ref); + } + + void visit(Style* style) override { + visitedStyle = style; + ValueVisitor::visit(style); + } +}; + +TEST(ValueVisitorTest, VisitsReference) { + Reference ref(ResourceName{u"android", ResourceType::kAttr, u"foo"}); + SingleReferenceVisitor visitor; + ref.accept(&visitor); + + EXPECT_EQ(visitor.visited, &ref); +} + +TEST(ValueVisitorTest, VisitsReferencesInStyle) { + std::unique_ptr<Style> style = test::StyleBuilder() + .setParent(u"@android:style/foo") + .addItem(u"@android:attr/one", test::buildReference(u"@android:id/foo")) + .build(); + + StyleVisitor visitor; + style->accept(&visitor); + + ASSERT_EQ(style.get(), visitor.visitedStyle); + + // Entry attribute references, plus the parent reference, plus one value reference. + ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size()); +} + +TEST(ValueVisitorTest, ValueCast) { + std::unique_ptr<Reference> ref = test::buildReference(u"@android:color/white"); + EXPECT_NE(valueCast<Reference>(ref.get()), nullptr); + + std::unique_ptr<Style> style = test::StyleBuilder() + .addItem(u"@android:attr/foo", test::buildReference(u"@android:color/black")) + .build(); + EXPECT_NE(valueCast<Style>(style.get()), nullptr); + EXPECT_EQ(valueCast<Reference>(style.get()), nullptr); +} + +} // namespace aapt diff --git a/tools/aapt2/XliffXmlPullParser.cpp b/tools/aapt2/XliffXmlPullParser.cpp deleted file mode 100644 index 31115f28f58f..000000000000 --- a/tools/aapt2/XliffXmlPullParser.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "XliffXmlPullParser.h" - -#include <string> - -namespace aapt { - -XliffXmlPullParser::XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) : - mParser(parser) { -} - -XmlPullParser::Event XliffXmlPullParser::next() { - while (XmlPullParser::isGoodEvent(mParser->next())) { - Event event = mParser->getEvent(); - if (event != Event::kStartElement && event != Event::kEndElement) { - break; - } - - if (mParser->getElementNamespace() != - u"urn:oasis:names:tc:xliff:document:1.2") { - break; - } - - const std::u16string& name = mParser->getElementName(); - if (name != u"bpt" - && name != u"ept" - && name != u"it" - && name != u"ph" - && name != u"g" - && name != u"bx" - && name != u"ex" - && name != u"x") { - break; - } - - // We hit a tag that was ignored, so get the next event. - } - return mParser->getEvent(); -} - -XmlPullParser::Event XliffXmlPullParser::getEvent() const { - return mParser->getEvent(); -} - -const std::string& XliffXmlPullParser::getLastError() const { - return mParser->getLastError(); -} - -const std::u16string& XliffXmlPullParser::getComment() const { - return mParser->getComment(); -} - -size_t XliffXmlPullParser::getLineNumber() const { - return mParser->getLineNumber(); -} - -size_t XliffXmlPullParser::getDepth() const { - return mParser->getDepth(); -} - -const std::u16string& XliffXmlPullParser::getText() const { - return mParser->getText(); -} - -const std::u16string& XliffXmlPullParser::getNamespacePrefix() const { - return mParser->getNamespacePrefix(); -} - -const std::u16string& XliffXmlPullParser::getNamespaceUri() const { - return mParser->getNamespaceUri(); -} - -bool XliffXmlPullParser::applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const { - return mParser->applyPackageAlias(package, defaultPackage); -} - -const std::u16string& XliffXmlPullParser::getElementNamespace() const { - return mParser->getElementNamespace(); -} - -const std::u16string& XliffXmlPullParser::getElementName() const { - return mParser->getElementName(); -} - -size_t XliffXmlPullParser::getAttributeCount() const { - return mParser->getAttributeCount(); -} - -XmlPullParser::const_iterator XliffXmlPullParser::beginAttributes() const { - return mParser->beginAttributes(); -} - -XmlPullParser::const_iterator XliffXmlPullParser::endAttributes() const { - return mParser->endAttributes(); -} - -} // namespace aapt diff --git a/tools/aapt2/XliffXmlPullParser.h b/tools/aapt2/XliffXmlPullParser.h deleted file mode 100644 index 77912277b31e..000000000000 --- a/tools/aapt2/XliffXmlPullParser.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AAPT_XLIFF_XML_PULL_PARSER_H -#define AAPT_XLIFF_XML_PULL_PARSER_H - -#include "XmlPullParser.h" - -#include <memory> -#include <string> - -namespace aapt { - -/** - * Strips xliff elements and provides the caller with a view of the - * underlying XML without xliff. - */ -class XliffXmlPullParser : public XmlPullParser { -public: - XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser); - XliffXmlPullParser(const XliffXmlPullParser& rhs) = delete; - - Event getEvent() const override; - const std::string& getLastError() const override; - Event next() override; - - const std::u16string& getComment() const override; - size_t getLineNumber() const override; - size_t getDepth() const override; - - const std::u16string& getText() const override; - - const std::u16string& getNamespacePrefix() const override; - const std::u16string& getNamespaceUri() const override; - bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage) - const override; - - const std::u16string& getElementNamespace() const override; - const std::u16string& getElementName() const override; - - const_iterator beginAttributes() const override; - const_iterator endAttributes() const override; - size_t getAttributeCount() const override; - -private: - std::shared_ptr<XmlPullParser> mParser; -}; - -} // namespace aapt - -#endif // AAPT_XLIFF_XML_PULL_PARSER_H diff --git a/tools/aapt2/XliffXmlPullParser_test.cpp b/tools/aapt2/XliffXmlPullParser_test.cpp deleted file mode 100644 index f9030724b80b..000000000000 --- a/tools/aapt2/XliffXmlPullParser_test.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SourceXmlPullParser.h" -#include "XliffXmlPullParser.h" - -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -namespace aapt { - -TEST(XliffXmlPullParserTest, IgnoreXliffTags) { - std::stringstream input; - input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl - << "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">" << std::endl - << "<string name=\"foo\">" - << "Hey <xliff:g><xliff:it>there</xliff:it></xliff:g> world</string>" << std::endl - << "</resources>" << std::endl; - std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input); - XliffXmlPullParser parser(sourceParser); - EXPECT_EQ(XmlPullParser::Event::kStartDocument, parser.getEvent()); - - EXPECT_EQ(XmlPullParser::Event::kStartNamespace, parser.next()); - EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2"); - EXPECT_EQ(parser.getNamespacePrefix(), u"xliff"); - - EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next()); - EXPECT_EQ(parser.getElementNamespace(), u""); - EXPECT_EQ(parser.getElementName(), u"resources"); - EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace. - - EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next()); - EXPECT_EQ(parser.getElementNamespace(), u""); - EXPECT_EQ(parser.getElementName(), u"string"); - - EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); - EXPECT_EQ(parser.getText(), u"Hey "); - - EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); - EXPECT_EQ(parser.getText(), u"there"); - - EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); - EXPECT_EQ(parser.getText(), u" world"); - - EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next()); - EXPECT_EQ(parser.getElementNamespace(), u""); - EXPECT_EQ(parser.getElementName(), u"string"); - EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace. - - EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next()); - EXPECT_EQ(parser.getElementNamespace(), u""); - EXPECT_EQ(parser.getElementName(), u"resources"); - - EXPECT_EQ(XmlPullParser::Event::kEndNamespace, parser.next()); - EXPECT_EQ(parser.getNamespacePrefix(), u"xliff"); - EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2"); - - EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.next()); -} - -} // namespace aapt diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/XmlDom.cpp index b8b2d1295067..d948775cc623 100644 --- a/tools/aapt2/XmlDom.cpp +++ b/tools/aapt2/XmlDom.cpp @@ -14,8 +14,7 @@ * limitations under the License. */ -#include "Logger.h" -#include "Util.h" +#include "util/Util.h" #include "XmlDom.h" #include "XmlPullParser.h" @@ -65,7 +64,7 @@ static void addToStack(Stack* stack, XML_Parser parser, std::unique_ptr<Node> no stack->root = std::move(node); } - if (thisNode->type != NodeType::kText) { + if (!nodeCast<Text>(thisNode)) { stack->nodeStack.push(thisNode); } } @@ -143,8 +142,7 @@ static void XMLCALL characterDataHandler(void* userData, const char* s, int len) Node* currentParent = stack->nodeStack.top(); if (!currentParent->children.empty()) { Node* lastChild = currentParent->children.back().get(); - if (lastChild->type == NodeType::kText) { - Text* text = static_cast<Text*>(lastChild); + if (Text* text = nodeCast<Text>(lastChild)) { text->text += util::utf8ToUtf16(StringPiece(s, len)); return; } @@ -166,7 +164,7 @@ static void XMLCALL commentDataHandler(void* userData, const char* comment) { stack->pendingComment += util::utf8ToUtf16(comment); } -std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger) { +std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source) { Stack stack; XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); @@ -182,20 +180,23 @@ std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger) { in->read(buffer, sizeof(buffer) / sizeof(buffer[0])); if (in->bad() && !in->eof()) { stack.root = {}; - logger->error() << strerror(errno) << std::endl; + diag->error(DiagMessage(source) << strerror(errno)); break; } if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) { stack.root = {}; - logger->error(XML_GetCurrentLineNumber(parser)) - << XML_ErrorString(XML_GetErrorCode(parser)) << std::endl; + diag->error(DiagMessage(source.withLine(XML_GetCurrentLineNumber(parser))) + << XML_ErrorString(XML_GetErrorCode(parser))); break; } } XML_ParserFree(parser); - return std::move(stack.root); + if (stack.root) { + return util::make_unique<XmlResource>(ResourceFile{}, std::move(stack.root)); + } + return {}; } static void copyAttributes(Element* el, android::ResXMLParser* parser) { @@ -224,7 +225,8 @@ static void copyAttributes(Element* el, android::ResXMLParser* parser) { } } -std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger) { +std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag, + const Source& source) { std::unique_ptr<Node> root; std::stack<Node*> nodeStack; @@ -307,15 +309,12 @@ std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* lo nodeStack.top()->addChild(std::move(newNode)); } - if (thisNode->type != NodeType::kText) { + if (!nodeCast<Text>(thisNode)) { nodeStack.push(thisNode); } } } - return root; -} - -Node::Node(NodeType type) : type(type), parent(nullptr), lineNumber(0), columnNumber(0) { + return util::make_unique<XmlResource>(ResourceFile{}, std::move(root)); } void Node::addChild(std::unique_ptr<Node> child) { @@ -323,39 +322,6 @@ void Node::addChild(std::unique_ptr<Node> child) { children.push_back(std::move(child)); } -Namespace::Namespace() : BaseNode(NodeType::kNamespace) { -} - -std::unique_ptr<Node> Namespace::clone() const { - Namespace* ns = new Namespace(); - ns->lineNumber = lineNumber; - ns->columnNumber = columnNumber; - ns->comment = comment; - ns->namespacePrefix = namespacePrefix; - ns->namespaceUri = namespaceUri; - for (auto& child : children) { - ns->addChild(child->clone()); - } - return std::unique_ptr<Node>(ns); -} - -Element::Element() : BaseNode(NodeType::kElement) { -} - -std::unique_ptr<Node> Element::clone() const { - Element* el = new Element(); - el->lineNumber = lineNumber; - el->columnNumber = columnNumber; - el->comment = comment; - el->namespaceUri = namespaceUri; - el->name = name; - el->attributes = attributes; - for (auto& child : children) { - el->addChild(child->clone()); - } - return std::unique_ptr<Node>(el); -} - Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16& name) { for (auto& attr : attributes) { if (ns == attr.namespaceUri && name == attr.name) { @@ -366,29 +332,29 @@ Attribute* Element::findAttribute(const StringPiece16& ns, const StringPiece16& } Element* Element::findChild(const StringPiece16& ns, const StringPiece16& name) { - return findChildWithAttribute(ns, name, nullptr); + return findChildWithAttribute(ns, name, {}, {}, {}); } Element* Element::findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name, - const Attribute* reqAttr) { + const StringPiece16& attrNs, const StringPiece16& attrName, + const StringPiece16& attrValue) { for (auto& childNode : children) { Node* child = childNode.get(); - while (child->type == NodeType::kNamespace) { + while (nodeCast<Namespace>(child)) { if (child->children.empty()) { break; } child = child->children[0].get(); } - if (child->type == NodeType::kElement) { - Element* el = static_cast<Element*>(child); + if (Element* el = nodeCast<Element>(child)) { if (ns == el->namespaceUri && name == el->name) { - if (!reqAttr) { + if (attrNs.empty() && attrName.empty()) { return el; } - Attribute* attrName = el->findAttribute(reqAttr->namespaceUri, reqAttr->name); - if (attrName && attrName->value == reqAttr->value) { + Attribute* attr = el->findAttribute(attrNs, attrName); + if (attr && attrValue == attr->value) { return el; } } @@ -401,31 +367,19 @@ std::vector<Element*> Element::getChildElements() { std::vector<Element*> elements; for (auto& childNode : children) { Node* child = childNode.get(); - while (child->type == NodeType::kNamespace) { + while (nodeCast<Namespace>(child)) { if (child->children.empty()) { break; } child = child->children[0].get(); } - if (child->type == NodeType::kElement) { - elements.push_back(static_cast<Element*>(child)); + if (Element* el = nodeCast<Element>(child)) { + elements.push_back(el); } } return elements; } -Text::Text() : BaseNode(NodeType::kText) { -} - -std::unique_ptr<Node> Text::clone() const { - Text* el = new Text(); - el->lineNumber = lineNumber; - el->columnNumber = columnNumber; - el->comment = comment; - el->text = text; - return std::unique_ptr<Node>(el); -} - } // namespace xml } // namespace aapt diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h index 035e7c46d1b5..c095f085f1fa 100644 --- a/tools/aapt2/XmlDom.h +++ b/tools/aapt2/XmlDom.h @@ -17,8 +17,13 @@ #ifndef AAPT_XML_DOM_H #define AAPT_XML_DOM_H -#include "Logger.h" -#include "StringPiece.h" +#include "Diagnostics.h" +#include "Resource.h" +#include "ResourceValues.h" +#include "util/StringPiece.h" +#include "util/Util.h" + +#include "process/IResourceTableConsumer.h" #include <istream> #include <expat.h> @@ -29,7 +34,7 @@ namespace aapt { namespace xml { -struct Visitor; +struct RawVisitor; /** * The type of node. Can be used to downcast to the concrete XML node @@ -45,17 +50,14 @@ enum class NodeType { * Base class for all XML nodes. */ struct Node { - NodeType type; - Node* parent; - size_t lineNumber; - size_t columnNumber; + Node* parent = nullptr; + size_t lineNumber = 0; + size_t columnNumber = 0; std::u16string comment; std::vector<std::unique_ptr<Node>> children; - Node(NodeType type); void addChild(std::unique_ptr<Node> child); - virtual std::unique_ptr<Node> clone() const = 0; - virtual void accept(Visitor* visitor) = 0; + virtual void accept(RawVisitor* visitor) = 0; virtual ~Node() {} }; @@ -65,8 +67,7 @@ struct Node { */ template <typename Derived> struct BaseNode : public Node { - BaseNode(NodeType t); - virtual void accept(Visitor* visitor) override; + virtual void accept(RawVisitor* visitor) override; }; /** @@ -75,9 +76,11 @@ struct BaseNode : public Node { struct Namespace : public BaseNode<Namespace> { std::u16string namespacePrefix; std::u16string namespaceUri; +}; - Namespace(); - virtual std::unique_ptr<Node> clone() const override; +struct AaptAttribute { + ResourceId id; + aapt::Attribute attribute; }; /** @@ -87,6 +90,9 @@ struct Attribute { std::u16string namespaceUri; std::u16string name; std::u16string value; + + Maybe<AaptAttribute> compiledAttribute; + std::unique_ptr<Item> compiledValue; }; /** @@ -97,12 +103,12 @@ struct Element : public BaseNode<Element> { std::u16string name; std::vector<Attribute> attributes; - Element(); - virtual std::unique_ptr<Node> clone() const override; Attribute* findAttribute(const StringPiece16& ns, const StringPiece16& name); xml::Element* findChild(const StringPiece16& ns, const StringPiece16& name); xml::Element* findChildWithAttribute(const StringPiece16& ns, const StringPiece16& name, - const xml::Attribute* reqAttr); + const StringPiece16& attrNs, + const StringPiece16& attrName, + const StringPiece16& attrValue); std::vector<xml::Element*> getChildElements(); }; @@ -111,41 +117,133 @@ struct Element : public BaseNode<Element> { */ struct Text : public BaseNode<Text> { std::u16string text; - - Text(); - virtual std::unique_ptr<Node> clone() const override; }; /** * Inflates an XML DOM from a text stream, logging errors to the logger. * Returns the root node on success, or nullptr on failure. */ -std::unique_ptr<Node> inflate(std::istream* in, SourceLogger* logger); +std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source); /** * Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger. * Returns the root node on success, or nullptr on failure. */ -std::unique_ptr<Node> inflate(const void* data, size_t dataLen, SourceLogger* logger); +std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag, + const Source& source); + +/** + * A visitor interface for the different XML Node subtypes. This will not traverse into + * children. Use Visitor for that. + */ +struct RawVisitor { + virtual ~RawVisitor() = default; + + virtual void visit(Namespace* node) {} + virtual void visit(Element* node) {} + virtual void visit(Text* text) {} +}; + +/** + * Visitor whose default implementation visits the children nodes of any node. + */ +struct Visitor : public RawVisitor { + using RawVisitor::visit; + + void visit(Namespace* node) override { + visitChildren(node); + } + + void visit(Element* node) override { + visitChildren(node); + } + + void visit(Text* text) override { + visitChildren(text); + } + + void visitChildren(Node* node) { + for (auto& child : node->children) { + child->accept(this); + } + } +}; /** - * A visitor interface for the different XML Node subtypes. + * An XML DOM visitor that will record the package name for a namespace prefix. */ -struct Visitor { - virtual void visit(Namespace* node) = 0; - virtual void visit(Element* node) = 0; - virtual void visit(Text* text) = 0; +class PackageAwareVisitor : public Visitor, public IPackageDeclStack { +private: + struct PackageDecl { + std::u16string prefix; + std::u16string package; + }; + + std::vector<PackageDecl> mPackageDecls; + +public: + using Visitor::visit; + + void visit(Namespace* ns) override { + bool added = false; + { + Maybe<std::u16string> package = util::extractPackageFromNamespace(ns->namespaceUri); + if (package) { + mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, package.value() }); + added = true; + } + } + + Visitor::visit(ns); + + if (added) { + mPackageDecls.pop_back(); + } + } + + Maybe<ResourceName> transformPackage(const ResourceName& name, + const StringPiece16& localPackage) const override { + if (name.package.empty()) { + return ResourceName{ localPackage.toString(), name.type, name.entry }; + } + + const auto rend = mPackageDecls.rend(); + for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) { + if (name.package == iter->prefix) { + if (iter->package.empty()) { + return ResourceName{ localPackage.toString(), name.type, name.entry }; + } else { + return ResourceName{ iter->package, name.type, name.entry }; + } + } + } + return {}; + } }; // Implementations template <typename Derived> -BaseNode<Derived>::BaseNode(NodeType type) : Node(type) { +void BaseNode<Derived>::accept(RawVisitor* visitor) { + visitor->visit(static_cast<Derived*>(this)); } -template <typename Derived> -void BaseNode<Derived>::accept(Visitor* visitor) { - visitor->visit(static_cast<Derived*>(this)); +template <typename T> +struct NodeCastImpl : public RawVisitor { + using RawVisitor::visit; + + T* value = nullptr; + + void visit(T* v) override { + value = v; + } +}; + +template <typename T> +T* nodeCast(Node* node) { + NodeCastImpl<T> visitor; + node->accept(&visitor); + return visitor.value; } } // namespace xml diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/XmlDom_test.cpp index 021714410e75..a1b9ed067aaa 100644 --- a/tools/aapt2/XmlDom_test.cpp +++ b/tools/aapt2/XmlDom_test.cpp @@ -36,12 +36,13 @@ TEST(XmlDomTest, Inflate) { </Layout> )EOF"; - SourceLogger logger(Source{ "/test/path" }); - std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); - ASSERT_NE(root, nullptr); + const Source source = { "test.xml" }; + StdErrDiagnostics diag; + std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, source); + ASSERT_NE(doc, nullptr); - EXPECT_EQ(root->type, xml::NodeType::kNamespace); - xml::Namespace* ns = static_cast<xml::Namespace*>(root.get()); + xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get()); + ASSERT_NE(ns, nullptr); EXPECT_EQ(ns->namespaceUri, u"http://schemas.android.com/apk/res/android"); EXPECT_EQ(ns->namespacePrefix, u"android"); } diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp deleted file mode 100644 index 56b5613d4264..000000000000 --- a/tools/aapt2/XmlFlattener.cpp +++ /dev/null @@ -1,574 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "BigBuffer.h" -#include "Logger.h" -#include "Maybe.h" -#include "Resolver.h" -#include "Resource.h" -#include "ResourceParser.h" -#include "ResourceValues.h" -#include "SdkConstants.h" -#include "Source.h" -#include "StringPool.h" -#include "Util.h" -#include "XmlFlattener.h" - -#include <androidfw/ResourceTypes.h> -#include <limits> -#include <map> -#include <string> -#include <vector> - -namespace aapt { -namespace xml { - -constexpr uint32_t kLowPriority = 0xffffffffu; - -// A vector that maps String refs to their final destination in the out buffer. -using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>; - -struct XmlFlattener : public Visitor { - XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs, - const std::u16string& defaultPackage) : - mOut(outBuffer), mPool(pool), mStringRefs(stringRefs), - mDefaultPackage(defaultPackage) { - } - - // No copying. - XmlFlattener(const XmlFlattener&) = delete; - XmlFlattener& operator=(const XmlFlattener&) = delete; - - void writeNamespace(Namespace* node, uint16_t type) { - const size_t startIndex = mOut->size(); - android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>(); - android::ResXMLTree_namespaceExt* flatNs = - mOut->nextBlock<android::ResXMLTree_namespaceExt>(); - mOut->align4(); - - flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) }; - flatNode->lineNumber = node->lineNumber; - flatNode->comment.index = -1; - addString(node->namespacePrefix, kLowPriority, &flatNs->prefix); - addString(node->namespaceUri, kLowPriority, &flatNs->uri); - } - - virtual void visit(Namespace* node) override { - // Extract the package/prefix from this namespace node. - Maybe<std::u16string> package = util::extractPackageFromNamespace(node->namespaceUri); - if (package) { - mPackageAliases.emplace_back( - node->namespacePrefix, - package.value().empty() ? mDefaultPackage : package.value()); - } - - writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE); - for (const auto& child : node->children) { - child->accept(this); - } - writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE); - - if (package) { - mPackageAliases.pop_back(); - } - } - - virtual void visit(Text* node) override { - if (util::trimWhitespace(node->text).empty()) { - return; - } - - const size_t startIndex = mOut->size(); - android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>(); - android::ResXMLTree_cdataExt* flatText = mOut->nextBlock<android::ResXMLTree_cdataExt>(); - mOut->align4(); - - const uint16_t type = android::RES_XML_CDATA_TYPE; - flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) }; - flatNode->lineNumber = node->lineNumber; - flatNode->comment.index = -1; - addString(node->text, kLowPriority, &flatText->data); - } - - virtual void visit(Element* node) override { - const size_t startIndex = mOut->size(); - android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>(); - android::ResXMLTree_attrExt* flatElem = mOut->nextBlock<android::ResXMLTree_attrExt>(); - - const uint16_t type = android::RES_XML_START_ELEMENT_TYPE; - flatNode->header = { type, sizeof(*flatNode), 0 }; - flatNode->lineNumber = node->lineNumber; - flatNode->comment.index = -1; - - addString(node->namespaceUri, kLowPriority, &flatElem->ns); - addString(node->name, kLowPriority, &flatElem->name); - flatElem->attributeStart = sizeof(*flatElem); - flatElem->attributeSize = sizeof(android::ResXMLTree_attribute); - flatElem->attributeCount = node->attributes.size(); - - if (!writeAttributes(mOut, node, flatElem)) { - mError = true; - } - - mOut->align4(); - flatNode->header.size = (uint32_t)(mOut->size() - startIndex); - - for (const auto& child : node->children) { - child->accept(this); - } - - const size_t startEndIndex = mOut->size(); - android::ResXMLTree_node* flatEndNode = mOut->nextBlock<android::ResXMLTree_node>(); - android::ResXMLTree_endElementExt* flatEndElem = - mOut->nextBlock<android::ResXMLTree_endElementExt>(); - mOut->align4(); - - const uint16_t endType = android::RES_XML_END_ELEMENT_TYPE; - flatEndNode->header = { endType, sizeof(*flatEndNode), - (uint32_t)(mOut->size() - startEndIndex) }; - flatEndNode->lineNumber = node->lineNumber; - flatEndNode->comment.index = -1; - - addString(node->namespaceUri, kLowPriority, &flatEndElem->ns); - addString(node->name, kLowPriority, &flatEndElem->name); - } - - bool success() const { - return !mError; - } - -protected: - void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) { - if (!str.empty()) { - mStringRefs->emplace_back(mPool->makeRef(str, StringPool::Context{ priority }), dest); - } else { - // The device doesn't think a string of size 0 is the same as null. - dest->index = -1; - } - } - - void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) { - mStringRefs->emplace_back(ref, dest); - } - - Maybe<std::u16string> getPackageAlias(const std::u16string& prefix) { - const auto endIter = mPackageAliases.rend(); - for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) { - if (iter->first == prefix) { - return iter->second; - } - } - return {}; - } - - const std::u16string& getDefaultPackage() const { - return mDefaultPackage; - } - - /** - * Subclasses override this to deal with attributes. Attributes can be flattened as - * raw values or as resources. - */ - virtual bool writeAttributes(BigBuffer* out, Element* node, - android::ResXMLTree_attrExt* flatElem) = 0; - -private: - BigBuffer* mOut; - StringPool* mPool; - FlatStringRefList* mStringRefs; - std::u16string mDefaultPackage; - bool mError = false; - std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases; -}; - -/** - * Flattens XML, encoding the attributes as raw strings. This is used in the compile phase. - */ -struct CompileXmlFlattener : public XmlFlattener { - CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs, - const std::u16string& defaultPackage) : - XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) { - } - - virtual bool writeAttributes(BigBuffer* out, Element* node, - android::ResXMLTree_attrExt* flatElem) override { - flatElem->attributeCount = node->attributes.size(); - if (node->attributes.empty()) { - return true; - } - - android::ResXMLTree_attribute* flatAttrs = out->nextBlock<android::ResXMLTree_attribute>( - node->attributes.size()); - for (const Attribute& attr : node->attributes) { - addString(attr.namespaceUri, kLowPriority, &flatAttrs->ns); - addString(attr.name, kLowPriority, &flatAttrs->name); - addString(attr.value, kLowPriority, &flatAttrs->rawValue); - flatAttrs++; - } - return true; - } -}; - -struct AttributeToFlatten { - uint32_t resourceId = 0; - const Attribute* xmlAttr = nullptr; - const ::aapt::Attribute* resourceAttr = nullptr; -}; - -static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) { - return a.resourceId < id; -} - -/** - * Flattens XML, encoding the attributes as resources. - */ -struct LinkedXmlFlattener : public XmlFlattener { - LinkedXmlFlattener(BigBuffer* outBuffer, StringPool* pool, - std::map<std::u16string, StringPool>* packagePools, - FlatStringRefList* stringRefs, - const std::u16string& defaultPackage, - const std::shared_ptr<IResolver>& resolver, - SourceLogger* logger, - const FlattenOptions& options) : - XmlFlattener(outBuffer, pool, stringRefs, defaultPackage), mResolver(resolver), - mLogger(logger), mPackagePools(packagePools), mOptions(options) { - } - - virtual bool writeAttributes(BigBuffer* out, Element* node, - android::ResXMLTree_attrExt* flatElem) override { - bool error = false; - std::vector<AttributeToFlatten> sortedAttributes; - uint32_t nextAttributeId = 0x80000000u; - - // Sort and filter attributes by their resource ID. - for (const Attribute& attr : node->attributes) { - AttributeToFlatten attrToFlatten; - attrToFlatten.xmlAttr = &attr; - - Maybe<std::u16string> package = util::extractPackageFromNamespace(attr.namespaceUri); - if (package) { - // Find the Attribute object via our Resolver. - ResourceName attrName = { package.value(), ResourceType::kAttr, attr.name }; - if (attrName.package.empty()) { - attrName.package = getDefaultPackage(); - } - - Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName); - if (!result || !result.value().id.isValid() || !result.value().attr) { - error = true; - mLogger->error(node->lineNumber) - << "unresolved attribute '" << attrName << "'." - << std::endl; - } else { - attrToFlatten.resourceId = result.value().id.id; - attrToFlatten.resourceAttr = result.value().attr; - - size_t sdk = findAttributeSdkLevel(attrToFlatten.resourceId); - if (mOptions.maxSdkAttribute && sdk > mOptions.maxSdkAttribute.value()) { - // We need to filter this attribute out. - mSmallestFilteredSdk = std::min(mSmallestFilteredSdk, sdk); - continue; - } - } - } - - if (attrToFlatten.resourceId == 0) { - // Attributes that have no resource ID (because they don't belong to a - // package) should appear after those that do have resource IDs. Assign - // them some integer value that will appear after. - attrToFlatten.resourceId = nextAttributeId++; - } - - // Insert the attribute into the sorted vector. - auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(), - attrToFlatten.resourceId, lessAttributeId); - sortedAttributes.insert(iter, std::move(attrToFlatten)); - } - - flatElem->attributeCount = sortedAttributes.size(); - if (sortedAttributes.empty()) { - return true; - } - - android::ResXMLTree_attribute* flatAttr = out->nextBlock<android::ResXMLTree_attribute>( - sortedAttributes.size()); - - // Now that we have sorted the attributes into their final encoded order, it's time - // to actually write them out. - uint16_t attributeIndex = 1; - for (const AttributeToFlatten& attrToFlatten : sortedAttributes) { - Maybe<std::u16string> package = util::extractPackageFromNamespace( - attrToFlatten.xmlAttr->namespaceUri); - - // Assign the indices for specific attributes. - if (package && package.value() == u"android" && attrToFlatten.xmlAttr->name == u"id") { - flatElem->idIndex = attributeIndex; - } else if (attrToFlatten.xmlAttr->namespaceUri.empty()) { - if (attrToFlatten.xmlAttr->name == u"class") { - flatElem->classIndex = attributeIndex; - } else if (attrToFlatten.xmlAttr->name == u"style") { - flatElem->styleIndex = attributeIndex; - } - } - attributeIndex++; - - // Add the namespaceUri and name to the list of StringRefs to encode. - addString(attrToFlatten.xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns); - flatAttr->rawValue.index = -1; - - if (!attrToFlatten.resourceAttr) { - addString(attrToFlatten.xmlAttr->name, kLowPriority, &flatAttr->name); - } else { - // We've already extracted the package successfully before. - assert(package); - - // Attribute names are stored without packages, but we use - // their StringPool index to lookup their resource IDs. - // This will cause collisions, so we can't dedupe - // attribute names from different packages. We use separate - // pools that we later combine. - // - // Lookup the StringPool for this package and make the reference there. - StringPool::Ref nameRef = (*mPackagePools)[package.value()].makeRef( - attrToFlatten.xmlAttr->name, - StringPool::Context{ attrToFlatten.resourceId }); - - // Add it to the list of strings to flatten. - addString(nameRef, &flatAttr->name); - - if (mOptions.keepRawValues) { - // Keep raw values (this is for static libraries). - // TODO(with a smarter inflater for binary XML, we can do without this). - addString(attrToFlatten.xmlAttr->value, kLowPriority, &flatAttr->rawValue); - } - } - - error |= !flattenItem(node, attrToFlatten.xmlAttr->value, attrToFlatten.resourceAttr, - flatAttr); - flatAttr->typedValue.size = sizeof(flatAttr->typedValue); - flatAttr++; - } - return !error; - } - - Maybe<size_t> getSmallestFilteredSdk() const { - if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) { - return {}; - } - return mSmallestFilteredSdk; - } - -private: - bool flattenItem(const Node* el, const std::u16string& value, const ::aapt::Attribute* attr, - android::ResXMLTree_attribute* flatAttr) { - std::unique_ptr<Item> item; - if (!attr) { - bool create = false; - item = ResourceParser::tryParseReference(value, &create); - if (!item) { - flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING; - addString(value, kLowPriority, &flatAttr->rawValue); - addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>( - &flatAttr->typedValue.data)); - return true; - } - } else { - item = ResourceParser::parseItemForAttribute(value, *attr); - if (!item) { - if (!(attr->typeMask & android::ResTable_map::TYPE_STRING)) { - mLogger->error(el->lineNumber) - << "'" - << value - << "' is not compatible with attribute '" - << *attr - << "'." - << std::endl; - return false; - } - - flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING; - addString(value, kLowPriority, &flatAttr->rawValue); - addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>( - &flatAttr->typedValue.data)); - return true; - } - } - - assert(item); - - bool error = false; - - // If this is a reference, resolve the name into an ID. - visitFunc<Reference>(*item, [&](Reference& reference) { - // First see if we can convert the package name from a prefix to a real - // package name. - ResourceName realName = reference.name; - if (!realName.package.empty()) { - Maybe<std::u16string> package = getPackageAlias(realName.package); - if (package) { - realName.package = package.value(); - } - } else { - realName.package = getDefaultPackage(); - } - - Maybe<ResourceId> result = mResolver->findId(realName); - if (!result || !result.value().isValid()) { - std::ostream& out = mLogger->error(el->lineNumber) - << "unresolved reference '" - << reference.name - << "'"; - if (realName != reference.name) { - out << " (aka '" << realName << "')"; - } - out << "'." << std::endl; - error = true; - } else { - reference.id = result.value(); - } - }); - - if (error) { - return false; - } - - item->flatten(flatAttr->typedValue); - return true; - } - - std::shared_ptr<IResolver> mResolver; - SourceLogger* mLogger; - std::map<std::u16string, StringPool>* mPackagePools; - FlattenOptions mOptions; - size_t mSmallestFilteredSdk = std::numeric_limits<size_t>::max(); -}; - -/** - * The binary XML file expects the StringPool to appear first, but we haven't collected the - * strings yet. We write to a temporary BigBuffer while parsing the input, adding strings - * we encounter to the StringPool. At the end, we write the StringPool to the given BigBuffer and - * then move the data from the temporary BigBuffer into the given one. This incurs no - * copies as the given BigBuffer simply takes ownership of the data. - */ -static void flattenXml(StringPool* pool, FlatStringRefList* stringRefs, BigBuffer* outBuffer, - BigBuffer&& xmlTreeBuffer) { - // Sort the string pool so that attribute resource IDs show up first. - pool->sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - return a.context.priority < b.context.priority; - }); - - // Now we flatten the string pool references into the correct places. - for (const auto& refEntry : *stringRefs) { - refEntry.second->index = refEntry.first.getIndex(); - } - - // Write the XML header. - const size_t beforeXmlTreeIndex = outBuffer->size(); - android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>(); - header->header.type = android::RES_XML_TYPE; - header->header.headerSize = sizeof(*header); - - // Flatten the StringPool. - StringPool::flattenUtf16(outBuffer, *pool); - - // Write the array of resource IDs, indexed by StringPool order. - const size_t beforeResIdMapIndex = outBuffer->size(); - android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>(); - resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE; - resIdMapChunk->headerSize = sizeof(*resIdMapChunk); - for (const auto& str : *pool) { - ResourceId id { str->context.priority }; - if (id.id == kLowPriority || !id.isValid()) { - // When we see the first non-resource ID, - // we're done. - break; - } - - *outBuffer->nextBlock<uint32_t>() = id.id; - } - resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex; - - // Move the temporary BigBuffer into outBuffer. - outBuffer->appendBuffer(std::move(xmlTreeBuffer)); - header->header.size = outBuffer->size() - beforeXmlTreeIndex; -} - -bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer) { - StringPool pool; - - // This will hold the StringRefs and the location in which to write the index. - // Once we sort the StringPool, we can assign the updated indices - // to the correct data locations. - FlatStringRefList stringRefs; - - // Since we don't know the size of the final StringPool, we write to this - // temporary BigBuffer, which we will append to outBuffer later. - BigBuffer out(1024); - - CompileXmlFlattener flattener(&out, &pool, &stringRefs, defaultPackage); - root->accept(&flattener); - - if (!flattener.success()) { - return false; - } - - flattenXml(&pool, &stringRefs, outBuffer, std::move(out)); - return true; -}; - -Maybe<size_t> flattenAndLink(const Source& source, Node* root, - const std::u16string& defaultPackage, - const std::shared_ptr<IResolver>& resolver, - const FlattenOptions& options, BigBuffer* outBuffer) { - SourceLogger logger(source); - StringPool pool; - - // Attribute names are stored without packages, but we use - // their StringPool index to lookup their resource IDs. - // This will cause collisions, so we can't dedupe - // attribute names from different packages. We use separate - // pools that we later combine. - std::map<std::u16string, StringPool> packagePools; - - FlatStringRefList stringRefs; - - // Since we don't know the size of the final StringPool, we write to this - // temporary BigBuffer, which we will append to outBuffer later. - BigBuffer out(1024); - - LinkedXmlFlattener flattener(&out, &pool, &packagePools, &stringRefs, defaultPackage, resolver, - &logger, options); - root->accept(&flattener); - - if (!flattener.success()) { - return {}; - } - - // Merge the package pools into the main pool. - for (auto& packagePoolEntry : packagePools) { - pool.merge(std::move(packagePoolEntry.second)); - } - - flattenXml(&pool, &stringRefs, outBuffer, std::move(out)); - - if (flattener.getSmallestFilteredSdk()) { - return flattener.getSmallestFilteredSdk(); - } - return 0; -} - -} // namespace xml -} // namespace aapt diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h deleted file mode 100644 index 4ece0a37869d..000000000000 --- a/tools/aapt2/XmlFlattener.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AAPT_XML_FLATTENER_H -#define AAPT_XML_FLATTENER_H - -#include "BigBuffer.h" -#include "Maybe.h" -#include "Resolver.h" -#include "Source.h" -#include "XmlDom.h" - -#include <string> - -namespace aapt { -namespace xml { - -/** - * Flattens an XML file into a binary representation parseable by - * the Android resource system. - */ -bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer); - -/** - * Options for flattenAndLink. - */ -struct FlattenOptions { - /** - * Keep attribute raw string values along with typed values. - */ - bool keepRawValues = false; - - /** - * If set, any attribute introduced in a later SDK will not be encoded. - */ - Maybe<size_t> maxSdkAttribute; -}; - -/** - * Like flatten(Node*,BigBuffer*), but references to resources are checked - * and string values are transformed to typed data where possible. - * - * `defaultPackage` is used when a reference has no package or the namespace URI - * "http://schemas.android.com/apk/res-auto" is used. - * - * `resolver` is used to resolve references to resources. - */ -Maybe<size_t> flattenAndLink(const Source& source, Node* root, - const std::u16string& defaultPackage, - const std::shared_ptr<IResolver>& resolver, - const FlattenOptions& options, BigBuffer* outBuffer); - -} // namespace xml -} // namespace aapt - -#endif // AAPT_XML_FLATTENER_H diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp deleted file mode 100644 index 8915d2478b64..000000000000 --- a/tools/aapt2/XmlFlattener_test.cpp +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "MockResolver.h" -#include "ResourceTable.h" -#include "ResourceValues.h" -#include "Util.h" -#include "XmlFlattener.h" - -#include <androidfw/AssetManager.h> -#include <androidfw/ResourceTypes.h> -#include <gtest/gtest.h> -#include <sstream> -#include <string> - -using namespace android; - -namespace aapt { -namespace xml { - -constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; - -class XmlFlattenerTest : public ::testing::Test { -public: - virtual void SetUp() override { - mResolver = std::make_shared<MockResolver>( - std::make_shared<ResourceTable>(), - std::map<ResourceName, ResourceId>({ - { ResourceName{ u"android", ResourceType::kAttr, u"attr" }, - ResourceId{ 0x01010000u } }, - { ResourceName{ u"android", ResourceType::kId, u"id" }, - ResourceId{ 0x01020000u } }, - { ResourceName{ u"com.lib", ResourceType::kAttr, u"attr" }, - ResourceId{ 0x01010001u } }, - { ResourceName{ u"com.lib", ResourceType::kId, u"id" }, - ResourceId{ 0x01020001u } }})); - } - - ::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) { - std::stringstream input(kXmlPreamble); - input << in << std::endl; - - SourceLogger logger(Source{ "test.xml" }); - std::unique_ptr<Node> root = inflate(&input, &logger); - if (!root) { - return ::testing::AssertionFailure(); - } - - BigBuffer outBuffer(1024); - if (!flattenAndLink(Source{ "test.xml" }, root.get(), std::u16string(u"android"), - mResolver, {}, &outBuffer)) { - return ::testing::AssertionFailure(); - } - - std::unique_ptr<uint8_t[]> data = util::copy(outBuffer); - if (outTree->setTo(data.get(), outBuffer.size(), true) != NO_ERROR) { - return ::testing::AssertionFailure(); - } - return ::testing::AssertionSuccess(); - } - - std::shared_ptr<IResolver> mResolver; -}; - -TEST_F(XmlFlattenerTest, ParseSimpleView) { - std::string input = R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/android" - android:attr="@id/id" - class="str" - style="@id/id"> - </View> - )EOF"; - ResXMLTree tree; - ASSERT_TRUE(testFlatten(input, &tree)); - - while (tree.next() != ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - } - - const StringPiece16 androidNs = u"http://schemas.android.com/apk/res/android"; - const StringPiece16 attrName = u"attr"; - ssize_t idx = tree.indexOfAttribute(androidNs.data(), androidNs.size(), attrName.data(), - attrName.size()); - ASSERT_GE(idx, 0); - EXPECT_EQ(tree.getAttributeNameResID(idx), 0x01010000u); - EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE); - - const StringPiece16 class16 = u"class"; - idx = tree.indexOfAttribute(nullptr, 0, class16.data(), class16.size()); - ASSERT_GE(idx, 0); - EXPECT_EQ(tree.getAttributeNameResID(idx), 0u); - EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_STRING); - EXPECT_EQ(tree.getAttributeData(idx), tree.getAttributeValueStringID(idx)); - - const StringPiece16 style16 = u"style"; - idx = tree.indexOfAttribute(nullptr, 0, style16.data(), style16.size()); - ASSERT_GE(idx, 0); - EXPECT_EQ(tree.getAttributeNameResID(idx), 0u); - EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE); - EXPECT_EQ((uint32_t) tree.getAttributeData(idx), 0x01020000u); - EXPECT_EQ(tree.getAttributeValueStringID(idx), -1); - - while (tree.next() != ResXMLTree::END_DOCUMENT) { - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - } -} - -TEST_F(XmlFlattenerTest, ParseViewWithPackageAlias) { - std::string input = "<View xmlns:ns1=\"http://schemas.android.com/apk/res/android\"\n" - " xmlns:ns2=\"http://schemas.android.com/apk/res/android\"\n" - " ns1:attr=\"@ns2:id/id\">\n" - "</View>"; - ResXMLTree tree; - ASSERT_TRUE(testFlatten(input, &tree)); - - while (tree.next() != ResXMLTree::END_DOCUMENT) { - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - } -} - -::testing::AssertionResult attributeNameAndValueEquals(ResXMLTree* tree, size_t index, - ResourceId nameId, ResourceId valueId) { - if (index >= tree->getAttributeCount()) { - return ::testing::AssertionFailure() << "index " << index << " is out of bounds (" - << tree->getAttributeCount() << ")"; - } - - if (tree->getAttributeNameResID(index) != nameId.id) { - return ::testing::AssertionFailure() - << "attribute at index " << index << " has ID " - << ResourceId{ (uint32_t) tree->getAttributeNameResID(index) } - << ". Expected ID " << nameId; - } - - if (tree->getAttributeDataType(index) != Res_value::TYPE_REFERENCE) { - return ::testing::AssertionFailure() << "attribute at index " << index << " has value of " - << "type " << std::hex - << tree->getAttributeDataType(index) << std::dec - << ". Expected reference (" << std::hex - << Res_value::TYPE_REFERENCE << std::dec << ")"; - } - - if ((uint32_t) tree->getAttributeData(index) != valueId.id) { - return ::testing::AssertionFailure() - << "attribute at index " << index << " has value " << "with ID " - << ResourceId{ (uint32_t) tree->getAttributeData(index) } - << ". Expected ID " << valueId; - } - return ::testing::AssertionSuccess(); -} - -TEST_F(XmlFlattenerTest, ParseViewWithShadowedPackageAlias) { - std::string input = "<View xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" - " app:attr=\"@app:id/id\">\n" - " <View xmlns:app=\"http://schemas.android.com/apk/res/com.lib\"\n" - " app:attr=\"@app:id/id\"/>\n" - "</View>"; - ResXMLTree tree; - ASSERT_TRUE(testFlatten(input, &tree)); - - while (tree.next() != ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); - } - - ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010000u }, - ResourceId{ 0x01020000u })); - - while (tree.next() != ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); - } - - ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u }, - ResourceId{ 0x01020001u })); -} - -TEST_F(XmlFlattenerTest, ParseViewWithLocalPackageAndAliasOfTheSameName) { - std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/com.lib\"\n" - " android:attr=\"@id/id\"/>"; - ResXMLTree tree; - ASSERT_TRUE(testFlatten(input, &tree)); - - while (tree.next() != ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); - } - - // We expect the 'android:attr' to be converted to 'com.lib:attr' due to the namespace - // assignment. - // However, we didn't give '@id/id' a package, so it should use the default package - // 'android', and not be converted from 'android' to 'com.lib'. - ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u }, - ResourceId{ 0x01020000u })); -} - -/* - * The device ResXMLParser in libandroidfw differentiates between empty namespace and null - * namespace. - */ -TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { - std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" - " package=\"android\"/>"; - - ResXMLTree tree; - ASSERT_TRUE(testFlatten(input, &tree)); - - while (tree.next() != ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); - } - - const StringPiece16 kPackage = u"package"; - EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); -} - -} // namespace xml -} // namespace aapt diff --git a/tools/aapt2/SourceXmlPullParser.cpp b/tools/aapt2/XmlPullParser.cpp index 8099044f616d..1b9499d78efb 100644 --- a/tools/aapt2/SourceXmlPullParser.cpp +++ b/tools/aapt2/XmlPullParser.cpp @@ -14,18 +14,18 @@ * limitations under the License. */ +#include "util/Maybe.h" +#include "util/Util.h" +#include "XmlPullParser.h" + #include <iostream> #include <string> -#include "Maybe.h" -#include "SourceXmlPullParser.h" -#include "Util.h" - namespace aapt { constexpr char kXmlNamespaceSep = 1; -SourceXmlPullParser::SourceXmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) { +XmlPullParser::XmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) { mParser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); XML_SetUserData(mParser, this); XML_SetElementHandler(mParser, startElementHandler, endElementHandler); @@ -35,11 +35,11 @@ SourceXmlPullParser::SourceXmlPullParser(std::istream& in) : mIn(in), mEmpty(), mEventQueue.push(EventData{ Event::kStartDocument, 0, mDepth++ }); } -SourceXmlPullParser::~SourceXmlPullParser() { +XmlPullParser::~XmlPullParser() { XML_ParserFree(mParser); } -SourceXmlPullParser::Event SourceXmlPullParser::next() { +XmlPullParser::Event XmlPullParser::next() { const Event currentEvent = getEvent(); if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) { return currentEvent; @@ -88,34 +88,34 @@ SourceXmlPullParser::Event SourceXmlPullParser::next() { return event; } -SourceXmlPullParser::Event SourceXmlPullParser::getEvent() const { +XmlPullParser::Event XmlPullParser::getEvent() const { return mEventQueue.front().event; } -const std::string& SourceXmlPullParser::getLastError() const { +const std::string& XmlPullParser::getLastError() const { return mLastError; } -const std::u16string& SourceXmlPullParser::getComment() const { +const std::u16string& XmlPullParser::getComment() const { return mEventQueue.front().comment; } -size_t SourceXmlPullParser::getLineNumber() const { +size_t XmlPullParser::getLineNumber() const { return mEventQueue.front().lineNumber; } -size_t SourceXmlPullParser::getDepth() const { +size_t XmlPullParser::getDepth() const { return mEventQueue.front().depth; } -const std::u16string& SourceXmlPullParser::getText() const { +const std::u16string& XmlPullParser::getText() const { if (getEvent() != Event::kText) { return mEmpty; } return mEventQueue.front().data1; } -const std::u16string& SourceXmlPullParser::getNamespacePrefix() const { +const std::u16string& XmlPullParser::getNamespacePrefix() const { const Event currentEvent = getEvent(); if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) { return mEmpty; @@ -123,7 +123,7 @@ const std::u16string& SourceXmlPullParser::getNamespacePrefix() const { return mEventQueue.front().data1; } -const std::u16string& SourceXmlPullParser::getNamespaceUri() const { +const std::u16string& XmlPullParser::getNamespaceUri() const { const Event currentEvent = getEvent(); if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) { return mEmpty; @@ -131,23 +131,26 @@ const std::u16string& SourceXmlPullParser::getNamespaceUri() const { return mEventQueue.front().data2; } -bool SourceXmlPullParser::applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const { +Maybe<ResourceName> XmlPullParser::transformPackage( + const ResourceName& name, const StringPiece16& localPackage) const { + if (name.package.empty()) { + return ResourceName{ localPackage.toString(), name.type, name.entry }; + } + const auto endIter = mPackageAliases.rend(); for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) { - if (iter->first == *package) { + if (name.package == iter->first) { if (iter->second.empty()) { - *package = defaultPackage; + return ResourceName{ localPackage.toString(), name.type, name.entry }; } else { - *package = iter->second; + return ResourceName{ iter->second, name.type, name.entry }; } - return true; } } - return false; + return {}; } -const std::u16string& SourceXmlPullParser::getElementNamespace() const { +const std::u16string& XmlPullParser::getElementNamespace() const { const Event currentEvent = getEvent(); if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) { return mEmpty; @@ -155,7 +158,7 @@ const std::u16string& SourceXmlPullParser::getElementNamespace() const { return mEventQueue.front().data1; } -const std::u16string& SourceXmlPullParser::getElementName() const { +const std::u16string& XmlPullParser::getElementName() const { const Event currentEvent = getEvent(); if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) { return mEmpty; @@ -163,15 +166,15 @@ const std::u16string& SourceXmlPullParser::getElementName() const { return mEventQueue.front().data2; } -XmlPullParser::const_iterator SourceXmlPullParser::beginAttributes() const { +XmlPullParser::const_iterator XmlPullParser::beginAttributes() const { return mEventQueue.front().attributes.begin(); } -XmlPullParser::const_iterator SourceXmlPullParser::endAttributes() const { +XmlPullParser::const_iterator XmlPullParser::endAttributes() const { return mEventQueue.front().attributes.end(); } -size_t SourceXmlPullParser::getAttributeCount() const { +size_t XmlPullParser::getAttributeCount() const { if (getEvent() != Event::kStartElement) { return 0; } @@ -196,9 +199,9 @@ static void splitName(const char* name, std::u16string& outNs, std::u16string& o } } -void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const char* prefix, +void XMLCALL XmlPullParser::startNamespaceHandler(void* userData, const char* prefix, const char* uri) { - SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData); + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); std::u16string namespaceUri = uri != nullptr ? util::utf8ToUtf16(uri) : std::u16string(); parser->mNamespaceUris.push(namespaceUri); parser->mEventQueue.push(EventData{ @@ -210,9 +213,9 @@ void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const ch }); } -void XMLCALL SourceXmlPullParser::startElementHandler(void* userData, const char* name, +void XMLCALL XmlPullParser::startElementHandler(void* userData, const char* name, const char** attrs) { - SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData); + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); EventData data = { Event::kStartElement, XML_GetCurrentLineNumber(parser->mParser), parser->mDepth++ @@ -233,8 +236,8 @@ void XMLCALL SourceXmlPullParser::startElementHandler(void* userData, const char parser->mEventQueue.push(std::move(data)); } -void XMLCALL SourceXmlPullParser::characterDataHandler(void* userData, const char* s, int len) { - SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData); +void XMLCALL XmlPullParser::characterDataHandler(void* userData, const char* s, int len) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); parser->mEventQueue.push(EventData{ Event::kText, @@ -244,8 +247,8 @@ void XMLCALL SourceXmlPullParser::characterDataHandler(void* userData, const cha }); } -void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char* name) { - SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData); +void XMLCALL XmlPullParser::endElementHandler(void* userData, const char* name) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); EventData data = { Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth) @@ -256,8 +259,8 @@ void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char* parser->mEventQueue.push(std::move(data)); } -void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char* prefix) { - SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData); +void XMLCALL XmlPullParser::endNamespaceHandler(void* userData, const char* prefix) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); parser->mEventQueue.push(EventData{ Event::kEndNamespace, @@ -269,8 +272,8 @@ void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char parser->mNamespaceUris.pop(); } -void XMLCALL SourceXmlPullParser::commentDataHandler(void* userData, const char* comment) { - SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData); +void XMLCALL XmlPullParser::commentDataHandler(void* userData, const char* comment) { + XmlPullParser* parser = reinterpret_cast<XmlPullParser*>(userData); parser->mEventQueue.push(EventData{ Event::kComment, diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/XmlPullParser.h index accfd30a4775..f7d7a03a4352 100644 --- a/tools/aapt2/XmlPullParser.h +++ b/tools/aapt2/XmlPullParser.h @@ -17,16 +17,24 @@ #ifndef AAPT_XML_PULL_PARSER_H #define AAPT_XML_PULL_PARSER_H +#include "util/Maybe.h" +#include "Resource.h" +#include "util/StringPiece.h" + +#include "process/IResourceTableConsumer.h" + #include <algorithm> +#include <expat.h> +#include <istream> #include <ostream> +#include <queue> +#include <stack> #include <string> #include <vector> -#include "StringPiece.h" - namespace aapt { -class XmlPullParser { +class XmlPullParser : public IPackageDeclStack { public: enum class Event { kBadDocument, @@ -41,43 +49,51 @@ public: kComment, }; - static void skipCurrentElement(XmlPullParser* parser); + /** + * Skips to the next direct descendant node of the given startDepth, + * skipping namespace nodes. + * + * When nextChildNode returns true, you can expect Comments, Text, and StartElement events. + */ + static bool nextChildNode(XmlPullParser* parser, size_t startDepth); + static bool skipCurrentElement(XmlPullParser* parser); static bool isGoodEvent(Event event); - virtual ~XmlPullParser() {} + XmlPullParser(std::istream& in); + virtual ~XmlPullParser(); /** * Returns the current event that is being processed. */ - virtual Event getEvent() const = 0; + Event getEvent() const; - virtual const std::string& getLastError() const = 0; + const std::string& getLastError() const; /** * Note, unlike XmlPullParser, the first call to next() will return * StartElement of the first element. */ - virtual Event next() = 0; + Event next(); // // These are available for all nodes. // - virtual const std::u16string& getComment() const = 0; - virtual size_t getLineNumber() const = 0; - virtual size_t getDepth() const = 0; + const std::u16string& getComment() const; + size_t getLineNumber() const; + size_t getDepth() const; /** * Returns the character data for a Text event. */ - virtual const std::u16string& getText() const = 0; + const std::u16string& getText() const; // // Namespace prefix and URI are available for StartNamespace and EndNamespace. // - virtual const std::u16string& getNamespacePrefix() const = 0; - virtual const std::u16string& getNamespaceUri() const = 0; + const std::u16string& getNamespacePrefix() const; + const std::u16string& getNamespaceUri() const; /* * Uses the current stack of namespaces to resolve the package. Eg: @@ -90,15 +106,17 @@ public: * If xmlns:app="http://schemas.android.com/apk/res-auto", then * 'package' will be set to 'defaultPackage'. */ - virtual bool applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const = 0; + // // // These are available for StartElement and EndElement. // - virtual const std::u16string& getElementNamespace() const = 0; - virtual const std::u16string& getElementName() const = 0; + const std::u16string& getElementNamespace() const; + const std::u16string& getElementName() const; + + Maybe<ResourceName> transformPackage(const ResourceName& name, + const StringPiece16& localPackage) const override; // // Remaining methods are for retrieving information about attributes @@ -121,10 +139,38 @@ public: using const_iterator = std::vector<Attribute>::const_iterator; - virtual const_iterator beginAttributes() const = 0; - virtual const_iterator endAttributes() const = 0; - virtual size_t getAttributeCount() const = 0; + const_iterator beginAttributes() const; + const_iterator endAttributes() const; + size_t getAttributeCount() const; const_iterator findAttribute(StringPiece16 namespaceUri, StringPiece16 name) const; + +private: + static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri); + static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs); + static void XMLCALL characterDataHandler(void* userData, const char* s, int len); + static void XMLCALL endElementHandler(void* userData, const char* name); + static void XMLCALL endNamespaceHandler(void* userData, const char* prefix); + static void XMLCALL commentDataHandler(void* userData, const char* comment); + + struct EventData { + Event event; + size_t lineNumber; + size_t depth; + std::u16string data1; + std::u16string data2; + std::u16string comment; + std::vector<Attribute> attributes; + }; + + std::istream& mIn; + XML_Parser mParser; + char mBuffer[16384]; + std::queue<EventData> mEventQueue; + std::string mLastError; + const std::u16string mEmpty; + size_t mDepth; + std::stack<std::u16string> mNamespaceUris; + std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases; }; // @@ -146,13 +192,35 @@ inline ::std::ostream& operator<<(::std::ostream& out, XmlPullParser::Event even return out; } -inline void XmlPullParser::skipCurrentElement(XmlPullParser* parser) { +inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, size_t startDepth) { + Event event; + + // First get back to the start depth. + while (isGoodEvent(event = parser->next()) && parser->getDepth() > startDepth + 1) {} + + // Now look for the first good node. + while ((event != Event::kEndElement || parser->getDepth() > startDepth) && isGoodEvent(event)) { + switch (event) { + case Event::kText: + case Event::kComment: + case Event::kStartElement: + return true; + default: + break; + } + event = parser->next(); + } + return false; +} + +inline bool XmlPullParser::skipCurrentElement(XmlPullParser* parser) { int depth = 1; while (depth > 0) { switch (parser->next()) { case Event::kEndDocument: + return true; case Event::kBadDocument: - return; + return false; case Event::kStartElement: depth++; break; @@ -163,6 +231,7 @@ inline void XmlPullParser::skipCurrentElement(XmlPullParser* parser) { break; } } + return true; } inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) { diff --git a/tools/aapt2/XmlPullParser_test.cpp b/tools/aapt2/XmlPullParser_test.cpp new file mode 100644 index 000000000000..1c99a432f597 --- /dev/null +++ b/tools/aapt2/XmlPullParser_test.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "util/StringPiece.h" +#include "XmlPullParser.h" + +#include <gtest/gtest.h> +#include <sstream> + +namespace aapt { + +TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) { + std::stringstream str; + str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>"; + XmlPullParser parser(str); + + const size_t depthOuter = parser.getDepth(); + ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthOuter)); + + EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent()); + EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName())); + + const size_t depthA = parser.getDepth(); + ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthA)); + EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent()); + EXPECT_EQ(StringPiece16(u"b"), StringPiece16(parser.getElementName())); + + const size_t depthB = parser.getDepth(); + ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB)); + EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent()); + EXPECT_EQ(StringPiece16(u"c"), StringPiece16(parser.getElementName())); + + ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB)); + EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent()); + EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName())); + + ASSERT_FALSE(XmlPullParser::nextChildNode(&parser, depthOuter)); + EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.getEvent()); +} + +} // namespace aapt diff --git a/tools/aapt2/ZipEntry.cpp b/tools/aapt2/ZipEntry.cpp deleted file mode 100644 index 891b4e1e2d3c..000000000000 --- a/tools/aapt2/ZipEntry.cpp +++ /dev/null @@ -1,745 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// -// Access to entries in a Zip archive. -// - -#define LOG_TAG "zip" - -#include "ZipEntry.h" -#include <utils/Log.h> - -#include <stdio.h> -#include <string.h> -#include <assert.h> - -namespace aapt { - -using namespace android; - -/* - * Initialize a new ZipEntry structure from a FILE* positioned at a - * CentralDirectoryEntry. - * - * On exit, the file pointer will be at the start of the next CDE or - * at the EOCD. - */ -status_t ZipEntry::initFromCDE(FILE* fp) -{ - status_t result; - long posn; - bool hasDD; - - //ALOGV("initFromCDE ---\n"); - - /* read the CDE */ - result = mCDE.read(fp); - if (result != NO_ERROR) { - ALOGD("mCDE.read failed\n"); - return result; - } - - //mCDE.dump(); - - /* using the info in the CDE, go load up the LFH */ - posn = ftell(fp); - if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) { - ALOGD("local header seek failed (%ld)\n", - mCDE.mLocalHeaderRelOffset); - return UNKNOWN_ERROR; - } - - result = mLFH.read(fp); - if (result != NO_ERROR) { - ALOGD("mLFH.read failed\n"); - return result; - } - - if (fseek(fp, posn, SEEK_SET) != 0) - return UNKNOWN_ERROR; - - //mLFH.dump(); - - /* - * We *might* need to read the Data Descriptor at this point and - * integrate it into the LFH. If this bit is set, the CRC-32, - * compressed size, and uncompressed size will be zero. In practice - * these seem to be rare. - */ - hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0; - if (hasDD) { - // do something clever - //ALOGD("+++ has data descriptor\n"); - } - - /* - * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr" - * flag is set, because the LFH is incomplete. (Not a problem, since we - * prefer the CDE values.) - */ - if (!hasDD && !compareHeaders()) { - ALOGW("warning: header mismatch\n"); - // keep going? - } - - /* - * If the mVersionToExtract is greater than 20, we may have an - * issue unpacking the record -- could be encrypted, compressed - * with something we don't support, or use Zip64 extensions. We - * can defer worrying about that to when we're extracting data. - */ - - return NO_ERROR; -} - -/* - * Initialize a new entry. Pass in the file name and an optional comment. - * - * Initializes the CDE and the LFH. - */ -void ZipEntry::initNew(const char* fileName, const char* comment) -{ - assert(fileName != NULL && *fileName != '\0'); // name required - - /* most fields are properly initialized by constructor */ - mCDE.mVersionMadeBy = kDefaultMadeBy; - mCDE.mVersionToExtract = kDefaultVersion; - mCDE.mCompressionMethod = kCompressStored; - mCDE.mFileNameLength = strlen(fileName); - if (comment != NULL) - mCDE.mFileCommentLength = strlen(comment); - mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does - - if (mCDE.mFileNameLength > 0) { - mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; - strcpy((char*) mCDE.mFileName, fileName); - } - if (mCDE.mFileCommentLength > 0) { - /* TODO: stop assuming null-terminated ASCII here? */ - mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; - strcpy((char*) mCDE.mFileComment, comment); - } - - copyCDEtoLFH(); -} - -/* - * Initialize a new entry, starting with the ZipEntry from a different - * archive. - * - * Initializes the CDE and the LFH. - */ -status_t ZipEntry::initFromExternal(const ZipFile* /* pZipFile */, - const ZipEntry* pEntry, const char* storageName) -{ - mCDE = pEntry->mCDE; - if (storageName && *storageName != 0) { - mCDE.mFileNameLength = strlen(storageName); - mCDE.mFileName = new unsigned char[mCDE.mFileNameLength + 1]; - strcpy((char*) mCDE.mFileName, storageName); - } - - // Check whether we got all the memory needed. - if ((mCDE.mFileNameLength > 0 && mCDE.mFileName == NULL) || - (mCDE.mFileCommentLength > 0 && mCDE.mFileComment == NULL) || - (mCDE.mExtraFieldLength > 0 && mCDE.mExtraField == NULL)) { - return NO_MEMORY; - } - - /* construct the LFH from the CDE */ - copyCDEtoLFH(); - - /* - * The LFH "extra" field is independent of the CDE "extra", so we - * handle it here. - */ - assert(mLFH.mExtraField == NULL); - mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength; - if (mLFH.mExtraFieldLength > 0) { - mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1]; - if (mLFH.mExtraField == NULL) - return NO_MEMORY; - memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField, - mLFH.mExtraFieldLength+1); - } - - return NO_ERROR; -} - -/* - * Insert pad bytes in the LFH by tweaking the "extra" field. This will - * potentially confuse something that put "extra" data in here earlier, - * but I can't find an actual problem. - */ -status_t ZipEntry::addPadding(int padding) -{ - if (padding <= 0) - return INVALID_OPERATION; - - //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n", - // padding, mLFH.mExtraFieldLength, mCDE.mFileName); - - if (mLFH.mExtraFieldLength > 0) { - /* extend existing field */ - unsigned char* newExtra; - - newExtra = new unsigned char[mLFH.mExtraFieldLength + padding]; - if (newExtra == NULL) - return NO_MEMORY; - memset(newExtra + mLFH.mExtraFieldLength, 0, padding); - memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength); - - delete[] mLFH.mExtraField; - mLFH.mExtraField = newExtra; - mLFH.mExtraFieldLength += padding; - } else { - /* create new field */ - mLFH.mExtraField = new unsigned char[padding]; - memset(mLFH.mExtraField, 0, padding); - mLFH.mExtraFieldLength = padding; - } - - return NO_ERROR; -} - -/* - * Set the fields in the LFH equal to the corresponding fields in the CDE. - * - * This does not touch the LFH "extra" field. - */ -void ZipEntry::copyCDEtoLFH(void) -{ - mLFH.mVersionToExtract = mCDE.mVersionToExtract; - mLFH.mGPBitFlag = mCDE.mGPBitFlag; - mLFH.mCompressionMethod = mCDE.mCompressionMethod; - mLFH.mLastModFileTime = mCDE.mLastModFileTime; - mLFH.mLastModFileDate = mCDE.mLastModFileDate; - mLFH.mCRC32 = mCDE.mCRC32; - mLFH.mCompressedSize = mCDE.mCompressedSize; - mLFH.mUncompressedSize = mCDE.mUncompressedSize; - mLFH.mFileNameLength = mCDE.mFileNameLength; - // the "extra field" is independent - - delete[] mLFH.mFileName; - if (mLFH.mFileNameLength > 0) { - mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1]; - strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName); - } else { - mLFH.mFileName = NULL; - } -} - -/* - * Set some information about a file after we add it. - */ -void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32, - int compressionMethod) -{ - mCDE.mCompressionMethod = compressionMethod; - mCDE.mCRC32 = crc32; - mCDE.mCompressedSize = compLen; - mCDE.mUncompressedSize = uncompLen; - mCDE.mCompressionMethod = compressionMethod; - if (compressionMethod == kCompressDeflated) { - mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used - } - copyCDEtoLFH(); -} - -/* - * See if the data in mCDE and mLFH match up. This is mostly useful for - * debugging these classes, but it can be used to identify damaged - * archives. - * - * Returns "false" if they differ. - */ -bool ZipEntry::compareHeaders(void) const -{ - if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) { - ALOGV("cmp: VersionToExtract\n"); - return false; - } - if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) { - ALOGV("cmp: GPBitFlag\n"); - return false; - } - if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) { - ALOGV("cmp: CompressionMethod\n"); - return false; - } - if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) { - ALOGV("cmp: LastModFileTime\n"); - return false; - } - if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) { - ALOGV("cmp: LastModFileDate\n"); - return false; - } - if (mCDE.mCRC32 != mLFH.mCRC32) { - ALOGV("cmp: CRC32\n"); - return false; - } - if (mCDE.mCompressedSize != mLFH.mCompressedSize) { - ALOGV("cmp: CompressedSize\n"); - return false; - } - if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) { - ALOGV("cmp: UncompressedSize\n"); - return false; - } - if (mCDE.mFileNameLength != mLFH.mFileNameLength) { - ALOGV("cmp: FileNameLength\n"); - return false; - } -#if 0 // this seems to be used for padding, not real data - if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) { - ALOGV("cmp: ExtraFieldLength\n"); - return false; - } -#endif - if (mCDE.mFileName != NULL) { - if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) { - ALOGV("cmp: FileName\n"); - return false; - } - } - - return true; -} - - -/* - * Convert the DOS date/time stamp into a UNIX time stamp. - */ -time_t ZipEntry::getModWhen(void) const -{ - struct tm parts; - - parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1; - parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5; - parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11; - parts.tm_mday = (mCDE.mLastModFileDate & 0x001f); - parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1; - parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80; - parts.tm_wday = parts.tm_yday = 0; - parts.tm_isdst = -1; // DST info "not available" - - return mktime(&parts); -} - -/* - * Set the CDE/LFH timestamp from UNIX time. - */ -void ZipEntry::setModWhen(time_t when) -{ -#if !defined(_WIN32) - struct tm tmResult; -#endif - time_t even; - unsigned short zdate, ztime; - - struct tm* ptm; - - /* round up to an even number of seconds */ - even = (time_t)(((unsigned long)(when) + 1) & (~1)); - - /* expand */ -#if !defined(_WIN32) - ptm = localtime_r(&even, &tmResult); -#else - ptm = localtime(&even); -#endif - - int year; - year = ptm->tm_year; - if (year < 80) - year = 80; - - zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday; - ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; - - mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime; - mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate; -} - - -/* - * =========================================================================== - * ZipEntry::LocalFileHeader - * =========================================================================== - */ - -/* - * Read a local file header. - * - * On entry, "fp" points to the signature at the start of the header. - * On exit, "fp" points to the start of data. - */ -status_t ZipEntry::LocalFileHeader::read(FILE* fp) -{ - status_t result = NO_ERROR; - unsigned char buf[kLFHLen]; - - assert(mFileName == NULL); - assert(mExtraField == NULL); - - if (fread(buf, 1, kLFHLen, fp) != kLFHLen) { - result = UNKNOWN_ERROR; - goto bail; - } - - if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { - ALOGD("whoops: didn't find expected signature\n"); - result = UNKNOWN_ERROR; - goto bail; - } - - mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]); - mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]); - mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]); - mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]); - mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]); - mCRC32 = ZipEntry::getLongLE(&buf[0x0e]); - mCompressedSize = ZipEntry::getLongLE(&buf[0x12]); - mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]); - mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]); - mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]); - - // TODO: validate sizes - - /* grab filename */ - if (mFileNameLength != 0) { - mFileName = new unsigned char[mFileNameLength+1]; - if (mFileName == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mFileName[mFileNameLength] = '\0'; - } - - /* grab extra field */ - if (mExtraFieldLength != 0) { - mExtraField = new unsigned char[mExtraFieldLength+1]; - if (mExtraField == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mExtraField[mExtraFieldLength] = '\0'; - } - -bail: - return result; -} - -/* - * Write a local file header. - */ -status_t ZipEntry::LocalFileHeader::write(FILE* fp) -{ - unsigned char buf[kLFHLen]; - - ZipEntry::putLongLE(&buf[0x00], kSignature); - ZipEntry::putShortLE(&buf[0x04], mVersionToExtract); - ZipEntry::putShortLE(&buf[0x06], mGPBitFlag); - ZipEntry::putShortLE(&buf[0x08], mCompressionMethod); - ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime); - ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate); - ZipEntry::putLongLE(&buf[0x0e], mCRC32); - ZipEntry::putLongLE(&buf[0x12], mCompressedSize); - ZipEntry::putLongLE(&buf[0x16], mUncompressedSize); - ZipEntry::putShortLE(&buf[0x1a], mFileNameLength); - ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength); - - if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen) - return UNKNOWN_ERROR; - - /* write filename */ - if (mFileNameLength != 0) { - if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) - return UNKNOWN_ERROR; - } - - /* write "extra field" */ - if (mExtraFieldLength != 0) { - if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - - -/* - * Dump the contents of a LocalFileHeader object. - */ -void ZipEntry::LocalFileHeader::dump(void) const -{ - ALOGD(" LocalFileHeader contents:\n"); - ALOGD(" versToExt=%u gpBits=0x%04x compression=%u\n", - mVersionToExtract, mGPBitFlag, mCompressionMethod); - ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", - mLastModFileTime, mLastModFileDate, mCRC32); - ALOGD(" compressedSize=%lu uncompressedSize=%lu\n", - mCompressedSize, mUncompressedSize); - ALOGD(" filenameLen=%u extraLen=%u\n", - mFileNameLength, mExtraFieldLength); - if (mFileName != NULL) - ALOGD(" filename: '%s'\n", mFileName); -} - - -/* - * =========================================================================== - * ZipEntry::CentralDirEntry - * =========================================================================== - */ - -/* - * Read the central dir entry that appears next in the file. - * - * On entry, "fp" should be positioned on the signature bytes for the - * entry. On exit, "fp" will point at the signature word for the next - * entry or for the EOCD. - */ -status_t ZipEntry::CentralDirEntry::read(FILE* fp) -{ - status_t result = NO_ERROR; - unsigned char buf[kCDELen]; - - /* no re-use */ - assert(mFileName == NULL); - assert(mExtraField == NULL); - assert(mFileComment == NULL); - - if (fread(buf, 1, kCDELen, fp) != kCDELen) { - result = UNKNOWN_ERROR; - goto bail; - } - - if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { - ALOGD("Whoops: didn't find expected signature\n"); - result = UNKNOWN_ERROR; - goto bail; - } - - mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]); - mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]); - mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]); - mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]); - mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]); - mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]); - mCRC32 = ZipEntry::getLongLE(&buf[0x10]); - mCompressedSize = ZipEntry::getLongLE(&buf[0x14]); - mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]); - mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]); - mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]); - mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]); - mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]); - mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]); - mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]); - mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]); - - // TODO: validate sizes and offsets - - /* grab filename */ - if (mFileNameLength != 0) { - mFileName = new unsigned char[mFileNameLength+1]; - if (mFileName == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mFileName[mFileNameLength] = '\0'; - } - - /* read "extra field" */ - if (mExtraFieldLength != 0) { - mExtraField = new unsigned char[mExtraFieldLength+1]; - if (mExtraField == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mExtraField[mExtraFieldLength] = '\0'; - } - - - /* grab comment, if any */ - if (mFileCommentLength != 0) { - mFileComment = new unsigned char[mFileCommentLength+1]; - if (mFileComment == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) - { - result = UNKNOWN_ERROR; - goto bail; - } - mFileComment[mFileCommentLength] = '\0'; - } - -bail: - return result; -} - -/* - * Write a central dir entry. - */ -status_t ZipEntry::CentralDirEntry::write(FILE* fp) -{ - unsigned char buf[kCDELen]; - - ZipEntry::putLongLE(&buf[0x00], kSignature); - ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy); - ZipEntry::putShortLE(&buf[0x06], mVersionToExtract); - ZipEntry::putShortLE(&buf[0x08], mGPBitFlag); - ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod); - ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime); - ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate); - ZipEntry::putLongLE(&buf[0x10], mCRC32); - ZipEntry::putLongLE(&buf[0x14], mCompressedSize); - ZipEntry::putLongLE(&buf[0x18], mUncompressedSize); - ZipEntry::putShortLE(&buf[0x1c], mFileNameLength); - ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength); - ZipEntry::putShortLE(&buf[0x20], mFileCommentLength); - ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart); - ZipEntry::putShortLE(&buf[0x24], mInternalAttrs); - ZipEntry::putLongLE(&buf[0x26], mExternalAttrs); - ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset); - - if (fwrite(buf, 1, kCDELen, fp) != kCDELen) - return UNKNOWN_ERROR; - - /* write filename */ - if (mFileNameLength != 0) { - if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) - return UNKNOWN_ERROR; - } - - /* write "extra field" */ - if (mExtraFieldLength != 0) { - if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) - return UNKNOWN_ERROR; - } - - /* write comment */ - if (mFileCommentLength != 0) { - if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - -/* - * Dump the contents of a CentralDirEntry object. - */ -void ZipEntry::CentralDirEntry::dump(void) const -{ - ALOGD(" CentralDirEntry contents:\n"); - ALOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n", - mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod); - ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", - mLastModFileTime, mLastModFileDate, mCRC32); - ALOGD(" compressedSize=%lu uncompressedSize=%lu\n", - mCompressedSize, mUncompressedSize); - ALOGD(" filenameLen=%u extraLen=%u commentLen=%u\n", - mFileNameLength, mExtraFieldLength, mFileCommentLength); - ALOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n", - mDiskNumberStart, mInternalAttrs, mExternalAttrs, - mLocalHeaderRelOffset); - - if (mFileName != NULL) - ALOGD(" filename: '%s'\n", mFileName); - if (mFileComment != NULL) - ALOGD(" comment: '%s'\n", mFileComment); -} - -/* - * Copy-assignment operator for CentralDirEntry. - */ -ZipEntry::CentralDirEntry& ZipEntry::CentralDirEntry::operator=(const ZipEntry::CentralDirEntry& src) { - if (this == &src) { - return *this; - } - - // Free up old data. - delete[] mFileName; - delete[] mExtraField; - delete[] mFileComment; - - // Copy scalars. - mVersionMadeBy = src.mVersionMadeBy; - mVersionToExtract = src.mVersionToExtract; - mGPBitFlag = src.mGPBitFlag; - mCompressionMethod = src.mCompressionMethod; - mLastModFileTime = src.mLastModFileTime; - mLastModFileDate = src.mLastModFileDate; - mCRC32 = src.mCRC32; - mCompressedSize = src.mCompressedSize; - mUncompressedSize = src.mUncompressedSize; - mFileNameLength = src.mFileNameLength; - mExtraFieldLength = src.mExtraFieldLength; - mFileCommentLength = src.mFileCommentLength; - mDiskNumberStart = src.mDiskNumberStart; - mInternalAttrs = src.mInternalAttrs; - mExternalAttrs = src.mExternalAttrs; - mLocalHeaderRelOffset = src.mLocalHeaderRelOffset; - - // Copy strings, if necessary. - if (mFileNameLength > 0) { - mFileName = new unsigned char[mFileNameLength + 1]; - if (mFileName != NULL) - strcpy((char*)mFileName, (char*)src.mFileName); - } else { - mFileName = NULL; - } - if (mFileCommentLength > 0) { - mFileComment = new unsigned char[mFileCommentLength + 1]; - if (mFileComment != NULL) - strcpy((char*)mFileComment, (char*)src.mFileComment); - } else { - mFileComment = NULL; - } - if (mExtraFieldLength > 0) { - /* we null-terminate this, though it may not be a string */ - mExtraField = new unsigned char[mExtraFieldLength + 1]; - if (mExtraField != NULL) - memcpy(mExtraField, src.mExtraField, mExtraFieldLength + 1); - } else { - mExtraField = NULL; - } - - return *this; -} - -} // namespace aapt diff --git a/tools/aapt2/ZipEntry.h b/tools/aapt2/ZipEntry.h deleted file mode 100644 index 2745a4386278..000000000000 --- a/tools/aapt2/ZipEntry.h +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// -// Zip archive entries. -// -// The ZipEntry class is tightly meshed with the ZipFile class. -// -#ifndef __LIBS_ZIPENTRY_H -#define __LIBS_ZIPENTRY_H - -#include <utils/Errors.h> - -#include <stdlib.h> -#include <stdio.h> - -namespace aapt { - -using android::status_t; - -class ZipFile; - -/* - * ZipEntry objects represent a single entry in a Zip archive. - * - * You can use one of these to get or set information about an entry, but - * there are no functions here for accessing the data itself. (We could - * tuck a pointer to the ZipFile in here for convenience, but that raises - * the likelihood of using ZipEntry objects after discarding the ZipFile.) - * - * File information is stored in two places: next to the file data (the Local - * File Header, and possibly a Data Descriptor), and at the end of the file - * (the Central Directory Entry). The two must be kept in sync. - */ -class ZipEntry { -public: - friend class ZipFile; - - ZipEntry(void) - : mDeleted(false), mMarked(false) - {} - ~ZipEntry(void) {} - - /* - * Returns "true" if the data is compressed. - */ - bool isCompressed(void) const { - return mCDE.mCompressionMethod != kCompressStored; - } - int getCompressionMethod(void) const { return mCDE.mCompressionMethod; } - - /* - * Return the uncompressed length. - */ - off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; } - - /* - * Return the compressed length. For uncompressed data, this returns - * the same thing as getUncompresesdLen(). - */ - off_t getCompressedLen(void) const { return mCDE.mCompressedSize; } - - /* - * Return the offset of the local file header. - */ - off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } - - /* - * Return the absolute file offset of the start of the compressed or - * uncompressed data. - */ - off_t getFileOffset(void) const { - return mCDE.mLocalHeaderRelOffset + - LocalFileHeader::kLFHLen + - mLFH.mFileNameLength + - mLFH.mExtraFieldLength; - } - - /* - * Return the data CRC. - */ - unsigned long getCRC32(void) const { return mCDE.mCRC32; } - - /* - * Return file modification time in UNIX seconds-since-epoch. - */ - time_t getModWhen(void) const; - - /* - * Return the archived file name. - */ - const char* getFileName(void) const { return (const char*) mCDE.mFileName; } - - /* - * Application-defined "mark". Can be useful when synchronizing the - * contents of an archive with contents on disk. - */ - bool getMarked(void) const { return mMarked; } - void setMarked(bool val) { mMarked = val; } - - /* - * Some basic functions for raw data manipulation. "LE" means - * Little Endian. - */ - static inline unsigned short getShortLE(const unsigned char* buf) { - return buf[0] | (buf[1] << 8); - } - static inline unsigned long getLongLE(const unsigned char* buf) { - return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); - } - static inline void putShortLE(unsigned char* buf, short val) { - buf[0] = (unsigned char) val; - buf[1] = (unsigned char) (val >> 8); - } - static inline void putLongLE(unsigned char* buf, long val) { - buf[0] = (unsigned char) val; - buf[1] = (unsigned char) (val >> 8); - buf[2] = (unsigned char) (val >> 16); - buf[3] = (unsigned char) (val >> 24); - } - - /* defined for Zip archives */ - enum { - kCompressStored = 0, // no compression - // shrunk = 1, - // reduced 1 = 2, - // reduced 2 = 3, - // reduced 3 = 4, - // reduced 4 = 5, - // imploded = 6, - // tokenized = 7, - kCompressDeflated = 8, // standard deflate - // Deflate64 = 9, - // lib imploded = 10, - // reserved = 11, - // bzip2 = 12, - }; - - /* - * Deletion flag. If set, the entry will be removed on the next - * call to "flush". - */ - bool getDeleted(void) const { return mDeleted; } - -protected: - /* - * Initialize the structure from the file, which is pointing at - * our Central Directory entry. - */ - status_t initFromCDE(FILE* fp); - - /* - * Initialize the structure for a new file. We need the filename - * and comment so that we can properly size the LFH area. The - * filename is mandatory, the comment is optional. - */ - void initNew(const char* fileName, const char* comment); - - /* - * Initialize the structure with the contents of a ZipEntry from - * another file. If fileName is non-NULL, override the name with fileName. - */ - status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry, - const char* fileName); - - /* - * Add some pad bytes to the LFH. We do this by adding or resizing - * the "extra" field. - */ - status_t addPadding(int padding); - - /* - * Set information about the data for this entry. - */ - void setDataInfo(long uncompLen, long compLen, unsigned long crc32, - int compressionMethod); - - /* - * Set the modification date. - */ - void setModWhen(time_t when); - - /* - * Set the offset of the local file header, relative to the start of - * the current file. - */ - void setLFHOffset(off_t offset) { - mCDE.mLocalHeaderRelOffset = (long) offset; - } - - /* mark for deletion; used by ZipFile::remove() */ - void setDeleted(void) { mDeleted = true; } - -private: - /* these are private and not defined */ - ZipEntry(const ZipEntry& src); - ZipEntry& operator=(const ZipEntry& src); - - /* returns "true" if the CDE and the LFH agree */ - bool compareHeaders(void) const; - void copyCDEtoLFH(void); - - bool mDeleted; // set if entry is pending deletion - bool mMarked; // app-defined marker - - /* - * Every entry in the Zip archive starts off with one of these. - */ - class LocalFileHeader { - public: - LocalFileHeader(void) : - mVersionToExtract(0), - mGPBitFlag(0), - mCompressionMethod(0), - mLastModFileTime(0), - mLastModFileDate(0), - mCRC32(0), - mCompressedSize(0), - mUncompressedSize(0), - mFileNameLength(0), - mExtraFieldLength(0), - mFileName(NULL), - mExtraField(NULL) - {} - virtual ~LocalFileHeader(void) { - delete[] mFileName; - delete[] mExtraField; - } - - status_t read(FILE* fp); - status_t write(FILE* fp); - - // unsigned long mSignature; - unsigned short mVersionToExtract; - unsigned short mGPBitFlag; - unsigned short mCompressionMethod; - unsigned short mLastModFileTime; - unsigned short mLastModFileDate; - unsigned long mCRC32; - unsigned long mCompressedSize; - unsigned long mUncompressedSize; - unsigned short mFileNameLength; - unsigned short mExtraFieldLength; - unsigned char* mFileName; - unsigned char* mExtraField; - - enum { - kSignature = 0x04034b50, - kLFHLen = 30, // LocalFileHdr len, excl. var fields - }; - - void dump(void) const; - }; - - /* - * Every entry in the Zip archive has one of these in the "central - * directory" at the end of the file. - */ - class CentralDirEntry { - public: - CentralDirEntry(void) : - mVersionMadeBy(0), - mVersionToExtract(0), - mGPBitFlag(0), - mCompressionMethod(0), - mLastModFileTime(0), - mLastModFileDate(0), - mCRC32(0), - mCompressedSize(0), - mUncompressedSize(0), - mFileNameLength(0), - mExtraFieldLength(0), - mFileCommentLength(0), - mDiskNumberStart(0), - mInternalAttrs(0), - mExternalAttrs(0), - mLocalHeaderRelOffset(0), - mFileName(NULL), - mExtraField(NULL), - mFileComment(NULL) - {} - virtual ~CentralDirEntry(void) { - delete[] mFileName; - delete[] mExtraField; - delete[] mFileComment; - } - - status_t read(FILE* fp); - status_t write(FILE* fp); - - CentralDirEntry& operator=(const CentralDirEntry& src); - - // unsigned long mSignature; - unsigned short mVersionMadeBy; - unsigned short mVersionToExtract; - unsigned short mGPBitFlag; - unsigned short mCompressionMethod; - unsigned short mLastModFileTime; - unsigned short mLastModFileDate; - unsigned long mCRC32; - unsigned long mCompressedSize; - unsigned long mUncompressedSize; - unsigned short mFileNameLength; - unsigned short mExtraFieldLength; - unsigned short mFileCommentLength; - unsigned short mDiskNumberStart; - unsigned short mInternalAttrs; - unsigned long mExternalAttrs; - unsigned long mLocalHeaderRelOffset; - unsigned char* mFileName; - unsigned char* mExtraField; - unsigned char* mFileComment; - - void dump(void) const; - - enum { - kSignature = 0x02014b50, - kCDELen = 46, // CentralDirEnt len, excl. var fields - }; - }; - - enum { - //kDataDescriptorSignature = 0x08074b50, // currently unused - kDataDescriptorLen = 16, // four 32-bit fields - - kDefaultVersion = 20, // need deflate, nothing much else - kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3 - kUsesDataDescr = 0x0008, // GPBitFlag bit 3 - }; - - LocalFileHeader mLFH; - CentralDirEntry mCDE; -}; - -}; // namespace aapt - -#endif // __LIBS_ZIPENTRY_H diff --git a/tools/aapt2/ZipFile.cpp b/tools/aapt2/ZipFile.cpp deleted file mode 100644 index 268c15efd9b9..000000000000 --- a/tools/aapt2/ZipFile.cpp +++ /dev/null @@ -1,1306 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// -// Access to Zip archives. -// - -#define LOG_TAG "zip" - -#include <androidfw/ZipUtils.h> -#include <utils/Log.h> - -#include "ZipFile.h" -#include "Util.h" - -#include <zlib.h> -#define DEF_MEM_LEVEL 8 // normally in zutil.h? - -#include <memory.h> -#include <sys/stat.h> -#include <errno.h> -#include <assert.h> - -namespace aapt { - -using namespace android; - -/* - * Some environments require the "b", some choke on it. - */ -#define FILE_OPEN_RO "rb" -#define FILE_OPEN_RW "r+b" -#define FILE_OPEN_RW_CREATE "w+b" - -/* should live somewhere else? */ -static status_t errnoToStatus(int err) -{ - if (err == ENOENT) - return NAME_NOT_FOUND; - else if (err == EACCES) - return PERMISSION_DENIED; - else - return UNKNOWN_ERROR; -} - -/* - * Open a file and parse its guts. - */ -status_t ZipFile::open(const char* zipFileName, int flags) -{ - bool newArchive = false; - - assert(mZipFp == NULL); // no reopen - - if ((flags & kOpenTruncate)) - flags |= kOpenCreate; // trunc implies create - - if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite)) - return INVALID_OPERATION; // not both - if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite))) - return INVALID_OPERATION; // not neither - if ((flags & kOpenCreate) && !(flags & kOpenReadWrite)) - return INVALID_OPERATION; // create requires write - - if (flags & kOpenTruncate) { - newArchive = true; - } else { - newArchive = (access(zipFileName, F_OK) != 0); - if (!(flags & kOpenCreate) && newArchive) { - /* not creating, must already exist */ - ALOGD("File %s does not exist", zipFileName); - return NAME_NOT_FOUND; - } - } - - /* open the file */ - const char* openflags; - if (flags & kOpenReadWrite) { - if (newArchive) - openflags = FILE_OPEN_RW_CREATE; - else - openflags = FILE_OPEN_RW; - } else { - openflags = FILE_OPEN_RO; - } - mZipFp = fopen(zipFileName, openflags); - if (mZipFp == NULL) { - int err = errno; - ALOGD("fopen failed: %d\n", err); - return errnoToStatus(err); - } - - status_t result; - if (!newArchive) { - /* - * Load the central directory. If that fails, then this probably - * isn't a Zip archive. - */ - result = readCentralDir(); - } else { - /* - * Newly-created. The EndOfCentralDir constructor actually - * sets everything to be the way we want it (all zeroes). We - * set mNeedCDRewrite so that we create *something* if the - * caller doesn't add any files. (We could also just unlink - * the file if it's brand new and nothing was added, but that's - * probably doing more than we really should -- the user might - * have a need for empty zip files.) - */ - mNeedCDRewrite = true; - result = NO_ERROR; - } - - if (flags & kOpenReadOnly) - mReadOnly = true; - else - assert(!mReadOnly); - - return result; -} - -/* - * Return the Nth entry in the archive. - */ -ZipEntry* ZipFile::getEntryByIndex(int idx) const -{ - if (idx < 0 || idx >= (int) mEntries.size()) - return NULL; - - return mEntries[idx]; -} - -/* - * Find an entry by name. - */ -ZipEntry* ZipFile::getEntryByName(const char* fileName) const -{ - /* - * Do a stupid linear string-compare search. - * - * There are various ways to speed this up, especially since it's rare - * to intermingle changes to the archive with "get by name" calls. We - * don't want to sort the mEntries vector itself, however, because - * it's used to recreate the Central Directory. - * - * (Hash table works, parallel list of pointers in sorted order is good.) - */ - int idx; - - for (idx = mEntries.size()-1; idx >= 0; idx--) { - ZipEntry* pEntry = mEntries[idx]; - if (!pEntry->getDeleted() && - strcmp(fileName, pEntry->getFileName()) == 0) - { - return pEntry; - } - } - - return NULL; -} - -/* - * Empty the mEntries vector. - */ -void ZipFile::discardEntries(void) -{ - int count = mEntries.size(); - - while (--count >= 0) - delete mEntries[count]; - - mEntries.clear(); -} - - -/* - * Find the central directory and read the contents. - * - * The fun thing about ZIP archives is that they may or may not be - * readable from start to end. In some cases, notably for archives - * that were written to stdout, the only length information is in the - * central directory at the end of the file. - * - * Of course, the central directory can be followed by a variable-length - * comment field, so we have to scan through it backwards. The comment - * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff - * itself, plus apparently sometimes people throw random junk on the end - * just for the fun of it. - * - * This is all a little wobbly. If the wrong value ends up in the EOCD - * area, we're hosed. This appears to be the way that everbody handles - * it though, so we're in pretty good company if this fails. - */ -status_t ZipFile::readCentralDir(void) -{ - status_t result = NO_ERROR; - unsigned char* buf = NULL; - off_t fileLength, seekStart; - long readAmount; - int i; - - fseek(mZipFp, 0, SEEK_END); - fileLength = ftell(mZipFp); - rewind(mZipFp); - - /* too small to be a ZIP archive? */ - if (fileLength < EndOfCentralDir::kEOCDLen) { - ALOGD("Length is %ld -- too small\n", (long)fileLength); - result = INVALID_OPERATION; - goto bail; - } - - buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch]; - if (buf == NULL) { - ALOGD("Failure allocating %d bytes for EOCD search", - EndOfCentralDir::kMaxEOCDSearch); - result = NO_MEMORY; - goto bail; - } - - if (fileLength > EndOfCentralDir::kMaxEOCDSearch) { - seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch; - readAmount = EndOfCentralDir::kMaxEOCDSearch; - } else { - seekStart = 0; - readAmount = (long) fileLength; - } - if (fseek(mZipFp, seekStart, SEEK_SET) != 0) { - ALOGD("Failure seeking to end of zip at %ld", (long) seekStart); - result = UNKNOWN_ERROR; - goto bail; - } - - /* read the last part of the file into the buffer */ - if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) { - ALOGD("short file? wanted %ld\n", readAmount); - result = UNKNOWN_ERROR; - goto bail; - } - - /* find the end-of-central-dir magic */ - for (i = readAmount - 4; i >= 0; i--) { - if (buf[i] == 0x50 && - ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature) - { - ALOGV("+++ Found EOCD at buf+%d\n", i); - break; - } - } - if (i < 0) { - ALOGD("EOCD not found, not Zip\n"); - result = INVALID_OPERATION; - goto bail; - } - - /* extract eocd values */ - result = mEOCD.readBuf(buf + i, readAmount - i); - if (result != NO_ERROR) { - ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i); - goto bail; - } - //mEOCD.dump(); - - if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 || - mEOCD.mNumEntries != mEOCD.mTotalNumEntries) - { - ALOGD("Archive spanning not supported\n"); - result = INVALID_OPERATION; - goto bail; - } - - /* - * So far so good. "mCentralDirSize" is the size in bytes of the - * central directory, so we can just seek back that far to find it. - * We can also seek forward mCentralDirOffset bytes from the - * start of the file. - * - * We're not guaranteed to have the rest of the central dir in the - * buffer, nor are we guaranteed that the central dir will have any - * sort of convenient size. We need to skip to the start of it and - * read the header, then the other goodies. - * - * The only thing we really need right now is the file comment, which - * we're hoping to preserve. - */ - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { - ALOGD("Failure seeking to central dir offset %ld\n", - mEOCD.mCentralDirOffset); - result = UNKNOWN_ERROR; - goto bail; - } - - /* - * Loop through and read the central dir entries. - */ - ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries); - int entry; - for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) { - ZipEntry* pEntry = new ZipEntry; - - result = pEntry->initFromCDE(mZipFp); - if (result != NO_ERROR) { - ALOGD("initFromCDE failed\n"); - delete pEntry; - goto bail; - } - - mEntries.push_back(pEntry); - } - - - /* - * If all went well, we should now be back at the EOCD. - */ - { - unsigned char checkBuf[4]; - if (fread(checkBuf, 1, 4, mZipFp) != 4) { - ALOGD("EOCD check read failed\n"); - result = INVALID_OPERATION; - goto bail; - } - if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) { - ALOGD("EOCD read check failed\n"); - result = UNKNOWN_ERROR; - goto bail; - } - ALOGV("+++ EOCD read check passed\n"); - } - -bail: - delete[] buf; - return result; -} - -status_t ZipFile::add(const BigBuffer& buffer, const char* storageName, int compressionMethod, - ZipEntry** ppEntry) { - std::unique_ptr<uint8_t[]> data = util::copy(buffer); - return add(data.get(), buffer.size(), storageName, compressionMethod, ppEntry); -} - - -/* - * Add a new file to the archive. - * - * This requires creating and populating a ZipEntry structure, and copying - * the data into the file at the appropriate position. The "appropriate - * position" is the current location of the central directory, which we - * casually overwrite (we can put it back later). - * - * If we were concerned about safety, we would want to make all changes - * in a temp file and then overwrite the original after everything was - * safely written. Not really a concern for us. - */ -status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, - const char* storageName, int sourceType, int compressionMethod, - ZipEntry** ppEntry) -{ - ZipEntry* pEntry = NULL; - status_t result = NO_ERROR; - long lfhPosn, startPosn, endPosn, uncompressedLen; - FILE* inputFp = NULL; - unsigned long crc; - time_t modWhen; - - if (mReadOnly) - return INVALID_OPERATION; - - assert(compressionMethod == ZipEntry::kCompressDeflated || - compressionMethod == ZipEntry::kCompressStored); - - /* make sure we're in a reasonable state */ - assert(mZipFp != NULL); - assert(mEntries.size() == mEOCD.mTotalNumEntries); - - /* make sure it doesn't already exist */ - if (getEntryByName(storageName) != NULL) - return ALREADY_EXISTS; - - if (!data) { - inputFp = fopen(fileName, FILE_OPEN_RO); - if (inputFp == NULL) - return errnoToStatus(errno); - } - - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { - result = UNKNOWN_ERROR; - goto bail; - } - - pEntry = new ZipEntry; - pEntry->initNew(storageName, NULL); - - /* - * From here on out, failures are more interesting. - */ - mNeedCDRewrite = true; - - /* - * Write the LFH, even though it's still mostly blank. We need it - * as a place-holder. In theory the LFH isn't necessary, but in - * practice some utilities demand it. - */ - lfhPosn = ftell(mZipFp); - pEntry->mLFH.write(mZipFp); - startPosn = ftell(mZipFp); - - /* - * Copy the data in, possibly compressing it as we go. - */ - if (sourceType == ZipEntry::kCompressStored) { - if (compressionMethod == ZipEntry::kCompressDeflated) { - bool failed = false; - result = compressFpToFp(mZipFp, inputFp, data, size, &crc); - if (result != NO_ERROR) { - ALOGD("compression failed, storing\n"); - failed = true; - } else { - /* - * Make sure it has compressed "enough". This probably ought - * to be set through an API call, but I don't expect our - * criteria to change over time. - */ - long src = inputFp ? ftell(inputFp) : size; - long dst = ftell(mZipFp) - startPosn; - if (dst + (dst / 10) > src) { - ALOGD("insufficient compression (src=%ld dst=%ld), storing\n", - src, dst); - failed = true; - } - } - - if (failed) { - compressionMethod = ZipEntry::kCompressStored; - if (inputFp) rewind(inputFp); - fseek(mZipFp, startPosn, SEEK_SET); - /* fall through to kCompressStored case */ - } - } - /* handle "no compression" request, or failed compression from above */ - if (compressionMethod == ZipEntry::kCompressStored) { - if (inputFp) { - result = copyFpToFp(mZipFp, inputFp, &crc); - } else { - result = copyDataToFp(mZipFp, data, size, &crc); - } - if (result != NO_ERROR) { - // don't need to truncate; happens in CDE rewrite - ALOGD("failed copying data in\n"); - goto bail; - } - } - - // currently seeked to end of file - uncompressedLen = inputFp ? ftell(inputFp) : size; - } else if (sourceType == ZipEntry::kCompressDeflated) { - /* we should support uncompressed-from-compressed, but it's not - * important right now */ - assert(compressionMethod == ZipEntry::kCompressDeflated); - - bool scanResult; - int method; - long compressedLen; - - scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen, - &compressedLen, &crc); - if (!scanResult || method != ZipEntry::kCompressDeflated) { - ALOGD("this isn't a deflated gzip file?"); - result = UNKNOWN_ERROR; - goto bail; - } - - result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL); - if (result != NO_ERROR) { - ALOGD("failed copying gzip data in\n"); - goto bail; - } - } else { - assert(false); - result = UNKNOWN_ERROR; - goto bail; - } - - /* - * We could write the "Data Descriptor", but there doesn't seem to - * be any point since we're going to go back and write the LFH. - * - * Update file offsets. - */ - endPosn = ftell(mZipFp); // seeked to end of compressed data - - /* - * Success! Fill out new values. - */ - pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, - compressionMethod); - modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); - pEntry->setModWhen(modWhen); - pEntry->setLFHOffset(lfhPosn); - mEOCD.mNumEntries++; - mEOCD.mTotalNumEntries++; - mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() - mEOCD.mCentralDirOffset = endPosn; - - /* - * Go back and write the LFH. - */ - if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { - result = UNKNOWN_ERROR; - goto bail; - } - pEntry->mLFH.write(mZipFp); - - /* - * Add pEntry to the list. - */ - mEntries.push_back(pEntry); - if (ppEntry != NULL) - *ppEntry = pEntry; - pEntry = NULL; - -bail: - if (inputFp != NULL) - fclose(inputFp); - delete pEntry; - return result; -} - -/* - * Add an entry by copying it from another zip file. If "padding" is - * nonzero, the specified number of bytes will be added to the "extra" - * field in the header. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ -status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, - const char* storageName, int padding, ZipEntry** ppEntry) -{ - ZipEntry* pEntry = NULL; - status_t result; - long lfhPosn, endPosn; - - if (mReadOnly) - return INVALID_OPERATION; - - /* make sure we're in a reasonable state */ - assert(mZipFp != NULL); - assert(mEntries.size() == mEOCD.mTotalNumEntries); - - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { - result = UNKNOWN_ERROR; - goto bail; - } - - pEntry = new ZipEntry; - if (pEntry == NULL) { - result = NO_MEMORY; - goto bail; - } - - result = pEntry->initFromExternal(pSourceZip, pSourceEntry, storageName); - if (result != NO_ERROR) { - goto bail; - } - if (padding != 0) { - result = pEntry->addPadding(padding); - if (result != NO_ERROR) - goto bail; - } - - /* - * From here on out, failures are more interesting. - */ - mNeedCDRewrite = true; - - /* - * Write the LFH. Since we're not recompressing the data, we already - * have all of the fields filled out. - */ - lfhPosn = ftell(mZipFp); - pEntry->mLFH.write(mZipFp); - - /* - * Copy the data over. - * - * If the "has data descriptor" flag is set, we want to copy the DD - * fields as well. This is a fixed-size area immediately following - * the data. - */ - if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) - { - result = UNKNOWN_ERROR; - goto bail; - } - - off_t copyLen; - copyLen = pSourceEntry->getCompressedLen(); - if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) - copyLen += ZipEntry::kDataDescriptorLen; - - if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) - != NO_ERROR) - { - ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); - result = UNKNOWN_ERROR; - goto bail; - } - - /* - * Update file offsets. - */ - endPosn = ftell(mZipFp); - - /* - * Success! Fill out new values. - */ - pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset - mEOCD.mNumEntries++; - mEOCD.mTotalNumEntries++; - mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() - mEOCD.mCentralDirOffset = endPosn; - - /* - * Add pEntry to the list. - */ - mEntries.push_back(pEntry); - if (ppEntry != NULL) - *ppEntry = pEntry; - pEntry = NULL; - - result = NO_ERROR; - -bail: - delete pEntry; - return result; -} - -/* - * Copy all of the bytes in "src" to "dst". - * - * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" - * will be seeked immediately past the data. - */ -status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32) -{ - unsigned char tmpBuf[32768]; - size_t count; - - *pCRC32 = crc32(0L, Z_NULL, 0); - - while (1) { - count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp); - if (ferror(srcFp) || ferror(dstFp)) - return errnoToStatus(errno); - if (count == 0) - break; - - *pCRC32 = crc32(*pCRC32, tmpBuf, count); - - if (fwrite(tmpBuf, 1, count, dstFp) != count) { - ALOGD("fwrite %d bytes failed\n", (int) count); - return UNKNOWN_ERROR; - } - } - - return NO_ERROR; -} - -/* - * Copy all of the bytes in "src" to "dst". - * - * On exit, "dstFp" will be seeked immediately past the data. - */ -status_t ZipFile::copyDataToFp(FILE* dstFp, - const void* data, size_t size, unsigned long* pCRC32) -{ - *pCRC32 = crc32(0L, Z_NULL, 0); - if (size > 0) { - *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size); - if (fwrite(data, 1, size, dstFp) != size) { - ALOGD("fwrite %d bytes failed\n", (int) size); - return UNKNOWN_ERROR; - } - } - - return NO_ERROR; -} - -/* - * Copy some of the bytes in "src" to "dst". - * - * If "pCRC32" is NULL, the CRC will not be computed. - * - * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" - * will be seeked immediately past the data just written. - */ -status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, - unsigned long* pCRC32) -{ - unsigned char tmpBuf[32768]; - size_t count; - - if (pCRC32 != NULL) - *pCRC32 = crc32(0L, Z_NULL, 0); - - while (length) { - long readSize; - - readSize = sizeof(tmpBuf); - if (readSize > length) - readSize = length; - - count = fread(tmpBuf, 1, readSize, srcFp); - if ((long) count != readSize) { // error or unexpected EOF - ALOGD("fread %d bytes failed\n", (int) readSize); - return UNKNOWN_ERROR; - } - - if (pCRC32 != NULL) - *pCRC32 = crc32(*pCRC32, tmpBuf, count); - - if (fwrite(tmpBuf, 1, count, dstFp) != count) { - ALOGD("fwrite %d bytes failed\n", (int) count); - return UNKNOWN_ERROR; - } - - length -= readSize; - } - - return NO_ERROR; -} - -/* - * Compress all of the data in "srcFp" and write it to "dstFp". - * - * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" - * will be seeked immediately past the compressed data. - */ -status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp, - const void* data, size_t size, unsigned long* pCRC32) -{ - status_t result = NO_ERROR; - const size_t kBufSize = 32768; - unsigned char* inBuf = NULL; - unsigned char* outBuf = NULL; - z_stream zstream; - bool atEof = false; // no feof() aviailable yet - unsigned long crc; - int zerr; - - /* - * Create an input buffer and an output buffer. - */ - inBuf = new unsigned char[kBufSize]; - outBuf = new unsigned char[kBufSize]; - if (inBuf == NULL || outBuf == NULL) { - result = NO_MEMORY; - goto bail; - } - - /* - * Initialize the zlib stream. - */ - memset(&zstream, 0, sizeof(zstream)); - zstream.zalloc = Z_NULL; - zstream.zfree = Z_NULL; - zstream.opaque = Z_NULL; - zstream.next_in = NULL; - zstream.avail_in = 0; - zstream.next_out = outBuf; - zstream.avail_out = kBufSize; - zstream.data_type = Z_UNKNOWN; - - zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION, - Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); - if (zerr != Z_OK) { - result = UNKNOWN_ERROR; - if (zerr == Z_VERSION_ERROR) { - ALOGE("Installed zlib is not compatible with linked version (%s)\n", - ZLIB_VERSION); - } else { - ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr); - } - goto bail; - } - - crc = crc32(0L, Z_NULL, 0); - - /* - * Loop while we have data. - */ - do { - size_t getSize; - int flush; - - /* only read if the input buffer is empty */ - if (zstream.avail_in == 0 && !atEof) { - ALOGV("+++ reading %d bytes\n", (int)kBufSize); - if (data) { - getSize = size > kBufSize ? kBufSize : size; - memcpy(inBuf, data, getSize); - data = ((const char*)data) + getSize; - size -= getSize; - } else { - getSize = fread(inBuf, 1, kBufSize, srcFp); - if (ferror(srcFp)) { - ALOGD("deflate read failed (errno=%d)\n", errno); - goto z_bail; - } - } - if (getSize < kBufSize) { - ALOGV("+++ got %d bytes, EOF reached\n", - (int)getSize); - atEof = true; - } - - crc = crc32(crc, inBuf, getSize); - - zstream.next_in = inBuf; - zstream.avail_in = getSize; - } - - if (atEof) - flush = Z_FINISH; /* tell zlib that we're done */ - else - flush = Z_NO_FLUSH; /* more to come! */ - - zerr = deflate(&zstream, flush); - if (zerr != Z_OK && zerr != Z_STREAM_END) { - ALOGD("zlib deflate call failed (zerr=%d)\n", zerr); - result = UNKNOWN_ERROR; - goto z_bail; - } - - /* write when we're full or when we're done */ - if (zstream.avail_out == 0 || - (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize)) - { - ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf)); - if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) != - (size_t)(zstream.next_out - outBuf)) - { - ALOGD("write %d failed in deflate\n", - (int) (zstream.next_out - outBuf)); - goto z_bail; - } - - zstream.next_out = outBuf; - zstream.avail_out = kBufSize; - } - } while (zerr == Z_OK); - - assert(zerr == Z_STREAM_END); /* other errors should've been caught */ - - *pCRC32 = crc; - -z_bail: - deflateEnd(&zstream); /* free up any allocated structures */ - -bail: - delete[] inBuf; - delete[] outBuf; - - return result; -} - -/* - * Mark an entry as deleted. - * - * We will eventually need to crunch the file down, but if several files - * are being removed (perhaps as part of an "update" process) we can make - * things considerably faster by deferring the removal to "flush" time. - */ -status_t ZipFile::remove(ZipEntry* pEntry) -{ - /* - * Should verify that pEntry is actually part of this archive, and - * not some stray ZipEntry from a different file. - */ - - /* mark entry as deleted, and mark archive as dirty */ - pEntry->setDeleted(); - mNeedCDRewrite = true; - return NO_ERROR; -} - -/* - * Flush any pending writes. - * - * In particular, this will crunch out deleted entries, and write the - * Central Directory and EOCD if we have stomped on them. - */ -status_t ZipFile::flush(void) -{ - status_t result = NO_ERROR; - long eocdPosn; - int i, count; - - if (mReadOnly) - return INVALID_OPERATION; - if (!mNeedCDRewrite) - return NO_ERROR; - - assert(mZipFp != NULL); - - result = crunchArchive(); - if (result != NO_ERROR) - return result; - - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) - return UNKNOWN_ERROR; - - count = mEntries.size(); - for (i = 0; i < count; i++) { - ZipEntry* pEntry = mEntries[i]; - pEntry->mCDE.write(mZipFp); - } - - eocdPosn = ftell(mZipFp); - mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset; - - mEOCD.write(mZipFp); - - /* - * If we had some stuff bloat up during compression and get replaced - * with plain files, or if we deleted some entries, there's a lot - * of wasted space at the end of the file. Remove it now. - */ - if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) { - ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno)); - // not fatal - } - - /* should we clear the "newly added" flag in all entries now? */ - - mNeedCDRewrite = false; - return NO_ERROR; -} - -/* - * Crunch deleted files out of an archive by shifting the later files down. - * - * Because we're not using a temp file, we do the operation inside the - * current file. - */ -status_t ZipFile::crunchArchive(void) -{ - status_t result = NO_ERROR; - int i, count; - long delCount, adjust; - -#if 0 - printf("CONTENTS:\n"); - for (i = 0; i < (int) mEntries.size(); i++) { - printf(" %d: lfhOff=%ld del=%d\n", - i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted()); - } - printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset); -#endif - - /* - * Roll through the set of files, shifting them as appropriate. We - * could probably get a slight performance improvement by sliding - * multiple files down at once (because we could use larger reads - * when operating on batches of small files), but it's not that useful. - */ - count = mEntries.size(); - delCount = adjust = 0; - for (i = 0; i < count; i++) { - ZipEntry* pEntry = mEntries[i]; - long span; - - if (pEntry->getLFHOffset() != 0) { - long nextOffset; - - /* Get the length of this entry by finding the offset - * of the next entry. Directory entries don't have - * file offsets, so we need to find the next non-directory - * entry. - */ - nextOffset = 0; - for (int ii = i+1; nextOffset == 0 && ii < count; ii++) - nextOffset = mEntries[ii]->getLFHOffset(); - if (nextOffset == 0) - nextOffset = mEOCD.mCentralDirOffset; - span = nextOffset - pEntry->getLFHOffset(); - - assert(span >= ZipEntry::LocalFileHeader::kLFHLen); - } else { - /* This is a directory entry. It doesn't have - * any actual file contents, so there's no need to - * move anything. - */ - span = 0; - } - - //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n", - // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count); - - if (pEntry->getDeleted()) { - adjust += span; - delCount++; - - delete pEntry; - mEntries.erase(mEntries.begin() + i); - - /* adjust loop control */ - count--; - i--; - } else if (span != 0 && adjust > 0) { - /* shuffle this entry back */ - //printf("+++ Shuffling '%s' back %ld\n", - // pEntry->getFileName(), adjust); - result = filemove(mZipFp, pEntry->getLFHOffset() - adjust, - pEntry->getLFHOffset(), span); - if (result != NO_ERROR) { - /* this is why you use a temp file */ - ALOGE("error during crunch - archive is toast\n"); - return result; - } - - pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust); - } - } - - /* - * Fix EOCD info. We have to wait until the end to do some of this - * because we use mCentralDirOffset to determine "span" for the - * last entry. - */ - mEOCD.mCentralDirOffset -= adjust; - mEOCD.mNumEntries -= delCount; - mEOCD.mTotalNumEntries -= delCount; - mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() - - assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries); - assert(mEOCD.mNumEntries == count); - - return result; -} - -/* - * Works like memmove(), but on pieces of a file. - */ -status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n) -{ - if (dst == src || n <= 0) - return NO_ERROR; - - unsigned char readBuf[32768]; - - if (dst < src) { - /* shift stuff toward start of file; must read from start */ - while (n != 0) { - size_t getSize = sizeof(readBuf); - if (getSize > n) - getSize = n; - - if (fseek(fp, (long) src, SEEK_SET) != 0) { - ALOGD("filemove src seek %ld failed\n", (long) src); - return UNKNOWN_ERROR; - } - - if (fread(readBuf, 1, getSize, fp) != getSize) { - ALOGD("filemove read %ld off=%ld failed\n", - (long) getSize, (long) src); - return UNKNOWN_ERROR; - } - - if (fseek(fp, (long) dst, SEEK_SET) != 0) { - ALOGD("filemove dst seek %ld failed\n", (long) dst); - return UNKNOWN_ERROR; - } - - if (fwrite(readBuf, 1, getSize, fp) != getSize) { - ALOGD("filemove write %ld off=%ld failed\n", - (long) getSize, (long) dst); - return UNKNOWN_ERROR; - } - - src += getSize; - dst += getSize; - n -= getSize; - } - } else { - /* shift stuff toward end of file; must read from end */ - assert(false); // write this someday, maybe - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - - -/* - * Get the modification time from a file descriptor. - */ -time_t ZipFile::getModTime(int fd) -{ - struct stat sb; - - if (fstat(fd, &sb) < 0) { - ALOGD("HEY: fstat on fd %d failed\n", fd); - return (time_t) -1; - } - - return sb.st_mtime; -} - - -#if 0 /* this is a bad idea */ -/* - * Get a copy of the Zip file descriptor. - * - * We don't allow this if the file was opened read-write because we tend - * to leave the file contents in an uncertain state between calls to - * flush(). The duplicated file descriptor should only be valid for reads. - */ -int ZipFile::getZipFd(void) const -{ - if (!mReadOnly) - return INVALID_OPERATION; - assert(mZipFp != NULL); - - int fd; - fd = dup(fileno(mZipFp)); - if (fd < 0) { - ALOGD("didn't work, errno=%d\n", errno); - } - - return fd; -} -#endif - - -#if 0 -/* - * Expand data. - */ -bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const -{ - return false; -} -#endif - -// free the memory when you're done -void* ZipFile::uncompress(const ZipEntry* entry) -{ - size_t unlen = entry->getUncompressedLen(); - size_t clen = entry->getCompressedLen(); - - void* buf = malloc(unlen); - if (buf == NULL) { - return NULL; - } - - fseek(mZipFp, 0, SEEK_SET); - - off_t offset = entry->getFileOffset(); - if (fseek(mZipFp, offset, SEEK_SET) != 0) { - goto bail; - } - - switch (entry->getCompressionMethod()) - { - case ZipEntry::kCompressStored: { - ssize_t amt = fread(buf, 1, unlen, mZipFp); - if (amt != (ssize_t)unlen) { - goto bail; - } -#if 0 - printf("data...\n"); - const unsigned char* p = (unsigned char*)buf; - const unsigned char* end = p+unlen; - for (int i=0; i<32 && p < end; i++) { - printf("0x%08x ", (int)(offset+(i*0x10))); - for (int j=0; j<0x10 && p < end; j++) { - printf(" %02x", *p); - p++; - } - printf("\n"); - } -#endif - - } - break; - case ZipEntry::kCompressDeflated: { - if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) { - goto bail; - } - } - break; - default: - goto bail; - } - return buf; - -bail: - free(buf); - return NULL; -} - - -/* - * =========================================================================== - * ZipFile::EndOfCentralDir - * =========================================================================== - */ - -/* - * Read the end-of-central-dir fields. - * - * "buf" should be positioned at the EOCD signature, and should contain - * the entire EOCD area including the comment. - */ -status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len) -{ - /* don't allow re-use */ - assert(mComment == NULL); - - if (len < kEOCDLen) { - /* looks like ZIP file got truncated */ - ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n", - kEOCDLen, len); - return INVALID_OPERATION; - } - - /* this should probably be an assert() */ - if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) - return UNKNOWN_ERROR; - - mDiskNumber = ZipEntry::getShortLE(&buf[0x04]); - mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]); - mNumEntries = ZipEntry::getShortLE(&buf[0x08]); - mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]); - mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]); - mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]); - mCommentLen = ZipEntry::getShortLE(&buf[0x14]); - - // TODO: validate mCentralDirOffset - - if (mCommentLen > 0) { - if (kEOCDLen + mCommentLen > len) { - ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n", - kEOCDLen, mCommentLen, len); - return UNKNOWN_ERROR; - } - mComment = new unsigned char[mCommentLen]; - memcpy(mComment, buf + kEOCDLen, mCommentLen); - } - - return NO_ERROR; -} - -/* - * Write an end-of-central-directory section. - */ -status_t ZipFile::EndOfCentralDir::write(FILE* fp) -{ - unsigned char buf[kEOCDLen]; - - ZipEntry::putLongLE(&buf[0x00], kSignature); - ZipEntry::putShortLE(&buf[0x04], mDiskNumber); - ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir); - ZipEntry::putShortLE(&buf[0x08], mNumEntries); - ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries); - ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize); - ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset); - ZipEntry::putShortLE(&buf[0x14], mCommentLen); - - if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen) - return UNKNOWN_ERROR; - if (mCommentLen > 0) { - assert(mComment != NULL); - if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen) - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - -/* - * Dump the contents of an EndOfCentralDir object. - */ -void ZipFile::EndOfCentralDir::dump(void) const -{ - ALOGD(" EndOfCentralDir contents:\n"); - ALOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n", - mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries); - ALOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n", - mCentralDirSize, mCentralDirOffset, mCommentLen); -} - -} // namespace aapt diff --git a/tools/aapt2/ZipFile.h b/tools/aapt2/ZipFile.h deleted file mode 100644 index 9de92ddc0872..000000000000 --- a/tools/aapt2/ZipFile.h +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// -// General-purpose Zip archive access. This class allows both reading and -// writing to Zip archives, including deletion of existing entries. -// -#ifndef __LIBS_ZIPFILE_H -#define __LIBS_ZIPFILE_H - -#include "BigBuffer.h" -#include "ZipEntry.h" - -#include <stdio.h> -#include <utils/Errors.h> -#include <vector> - -namespace aapt { - -using android::status_t; - -/* - * Manipulate a Zip archive. - * - * Some changes will not be visible in the until until "flush" is called. - * - * The correct way to update a file archive is to make all changes to a - * copy of the archive in a temporary file, and then unlink/rename over - * the original after everything completes. Because we're only interested - * in using this for packaging, we don't worry about such things. Crashing - * after making changes and before flush() completes could leave us with - * an unusable Zip archive. - */ -class ZipFile { -public: - ZipFile(void) - : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false) - {} - ~ZipFile(void) { - if (!mReadOnly) - flush(); - if (mZipFp != NULL) - fclose(mZipFp); - discardEntries(); - } - - /* - * Open a new or existing archive. - */ - enum { - kOpenReadOnly = 0x01, - kOpenReadWrite = 0x02, - kOpenCreate = 0x04, // create if it doesn't exist - kOpenTruncate = 0x08, // if it exists, empty it - }; - status_t open(const char* zipFileName, int flags); - - /* - * Add a file to the end of the archive. Specify whether you want the - * library to try to store it compressed. - * - * If "storageName" is specified, the archive will use that instead - * of "fileName". - * - * If there is already an entry with the same name, the call fails. - * Existing entries with the same name must be removed first. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t add(const char* fileName, int compressionMethod, - ZipEntry** ppEntry) - { - return add(fileName, fileName, compressionMethod, ppEntry); - } - status_t add(const char* fileName, const char* storageName, - int compressionMethod, ZipEntry** ppEntry) - { - return addCommon(fileName, NULL, 0, storageName, - ZipEntry::kCompressStored, - compressionMethod, ppEntry); - } - - /* - * Add a file that is already compressed with gzip. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t addGzip(const char* fileName, const char* storageName, - ZipEntry** ppEntry) - { - return addCommon(fileName, NULL, 0, storageName, - ZipEntry::kCompressDeflated, - ZipEntry::kCompressDeflated, ppEntry); - } - - /* - * Add a file from an in-memory data buffer. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t add(const void* data, size_t size, const char* storageName, - int compressionMethod, ZipEntry** ppEntry) - { - return addCommon(NULL, data, size, storageName, - ZipEntry::kCompressStored, - compressionMethod, ppEntry); - } - - status_t add(const BigBuffer& data, const char* storageName, - int compressionMethod, ZipEntry** ppEntry); - - /* - * Add an entry by copying it from another zip file. If storageName is - * non-NULL, the entry will be inserted with the name storageName, otherwise - * it will have the same name as the source entry. If "padding" is - * nonzero, the specified number of bytes will be added to the "extra" - * field in the header. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, - const char* storageName, int padding, ZipEntry** ppEntry); - - /* - * Mark an entry as having been removed. It is not actually deleted - * from the archive or our internal data structures until flush() is - * called. - */ - status_t remove(ZipEntry* pEntry); - - /* - * Flush changes. If mNeedCDRewrite is set, this writes the central dir. - */ - status_t flush(void); - - /* - * Expand the data into the buffer provided. The buffer must hold - * at least <uncompressed len> bytes. Variation expands directly - * to a file. - * - * Returns "false" if an error was encountered in the compressed data. - */ - //bool uncompress(const ZipEntry* pEntry, void* buf) const; - //bool uncompress(const ZipEntry* pEntry, FILE* fp) const; - void* uncompress(const ZipEntry* pEntry); - - /* - * Get an entry, by name. Returns NULL if not found. - * - * Does not return entries pending deletion. - */ - ZipEntry* getEntryByName(const char* fileName) const; - - /* - * Get the Nth entry in the archive. - * - * This will return an entry that is pending deletion. - */ - int getNumEntries(void) const { return mEntries.size(); } - ZipEntry* getEntryByIndex(int idx) const; - -private: - /* these are private and not defined */ - ZipFile(const ZipFile& src); - ZipFile& operator=(const ZipFile& src); - - class EndOfCentralDir { - public: - EndOfCentralDir(void) : - mDiskNumber(0), - mDiskWithCentralDir(0), - mNumEntries(0), - mTotalNumEntries(0), - mCentralDirSize(0), - mCentralDirOffset(0), - mCommentLen(0), - mComment(NULL) - {} - virtual ~EndOfCentralDir(void) { - delete[] mComment; - } - - status_t readBuf(const unsigned char* buf, int len); - status_t write(FILE* fp); - - //unsigned long mSignature; - unsigned short mDiskNumber; - unsigned short mDiskWithCentralDir; - unsigned short mNumEntries; - unsigned short mTotalNumEntries; - unsigned long mCentralDirSize; - unsigned long mCentralDirOffset; // offset from first disk - unsigned short mCommentLen; - unsigned char* mComment; - - enum { - kSignature = 0x06054b50, - kEOCDLen = 22, // EndOfCentralDir len, excl. comment - - kMaxCommentLen = 65535, // longest possible in ushort - kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen, - - }; - - void dump(void) const; - }; - - - /* read all entries in the central dir */ - status_t readCentralDir(void); - - /* crunch deleted entries out */ - status_t crunchArchive(void); - - /* clean up mEntries */ - void discardEntries(void); - - /* common handler for all "add" functions */ - status_t addCommon(const char* fileName, const void* data, size_t size, - const char* storageName, int sourceType, int compressionMethod, - ZipEntry** ppEntry); - - /* copy all of "srcFp" into "dstFp" */ - status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32); - /* copy all of "data" into "dstFp" */ - status_t copyDataToFp(FILE* dstFp, - const void* data, size_t size, unsigned long* pCRC32); - /* copy some of "srcFp" into "dstFp" */ - status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, - unsigned long* pCRC32); - /* like memmove(), but on parts of a single file */ - status_t filemove(FILE* fp, off_t dest, off_t src, size_t n); - /* compress all of "srcFp" into "dstFp", using Deflate */ - status_t compressFpToFp(FILE* dstFp, FILE* srcFp, - const void* data, size_t size, unsigned long* pCRC32); - - /* get modification date from a file descriptor */ - time_t getModTime(int fd); - - /* - * We use stdio FILE*, which gives us buffering but makes dealing - * with files >2GB awkward. Until we support Zip64, we're fine. - */ - FILE* mZipFp; // Zip file pointer - - /* one of these per file */ - EndOfCentralDir mEOCD; - - /* did we open this read-only? */ - bool mReadOnly; - - /* set this when we trash the central dir */ - bool mNeedCDRewrite; - - /* - * One ZipEntry per entry in the zip file. I'm using pointers instead - * of objects because it's easier than making operator= work for the - * classes and sub-classes. - */ - std::vector<ZipEntry*> mEntries; -}; - -}; // namespace aapt - -#endif // __LIBS_ZIPFILE_H diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp new file mode 100644 index 000000000000..498bc9c162d3 --- /dev/null +++ b/tools/aapt2/compile/Compile.cpp @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ConfigDescription.h" +#include "Diagnostics.h" +#include "Flags.h" +#include "ResourceParser.h" +#include "ResourceTable.h" +#include "XmlDom.h" +#include "XmlPullParser.h" + +#include "compile/IdAssigner.h" +#include "compile/Png.h" +#include "compile/XmlIdCollector.h" +#include "flatten/FileExportWriter.h" +#include "flatten/TableFlattener.h" +#include "flatten/XmlFlattener.h" +#include "util/Files.h" +#include "util/Maybe.h" +#include "util/Util.h" + +#include <fstream> +#include <string> + +namespace aapt { + +struct ResourcePathData { + Source source; + std::u16string resourceDir; + std::u16string name; + std::string extension; + + // Original config str. We keep this because when we parse the config, we may add on + // version qualifiers. We want to preserve the original input so the output is easily + // computed before hand. + std::string configStr; + ConfigDescription config; +}; + +/** + * Resource file paths are expected to look like: + * [--/res/]type[-config]/name + */ +static Maybe<ResourcePathData> extractResourcePathData(const std::string& path, + std::string* outError) { + std::vector<std::string> parts = util::split(path, file::sDirSep); + if (parts.size() < 2) { + if (outError) *outError = "bad resource path"; + return {}; + } + + std::string& dir = parts[parts.size() - 2]; + StringPiece dirStr = dir; + + StringPiece configStr; + ConfigDescription config; + size_t dashPos = dir.find('-'); + if (dashPos != std::string::npos) { + configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1)); + if (!ConfigDescription::parse(configStr, &config)) { + if (outError) { + std::stringstream errStr; + errStr << "invalid configuration '" << configStr << "'"; + *outError = errStr.str(); + } + return {}; + } + dirStr = dirStr.substr(0, dashPos); + } + + std::string& filename = parts[parts.size() - 1]; + StringPiece name = filename; + StringPiece extension; + size_t dotPos = filename.find('.'); + if (dotPos != std::string::npos) { + extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1)); + name = name.substr(0, dotPos); + } + + return ResourcePathData{ + Source{ path }, + util::utf8ToUtf16(dirStr), + util::utf8ToUtf16(name), + extension.toString(), + configStr.toString(), + config + }; +} + +struct CompileOptions { + std::string outputPath; + bool verbose = false; +}; + +static std::string buildIntermediateFilename(const std::string outDir, + const ResourcePathData& data) { + std::stringstream name; + name << data.resourceDir; + if (!data.configStr.empty()) { + name << "-" << data.configStr; + } + name << "_" << data.name << "." << data.extension << ".flat"; + std::string outPath = outDir; + file::appendPath(&outPath, name.str()); + return outPath; +} + +static bool compileTable(IAaptContext* context, const CompileOptions& options, + const ResourcePathData& pathData, const std::string& outputPath) { + ResourceTable table; + table.createPackage(u"", 0x7f); + + { + std::ifstream fin(pathData.source.path, std::ifstream::binary); + if (!fin) { + context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno)); + return false; + } + + + // Parse the values file from XML. + XmlPullParser xmlParser(fin); + ResourceParser resParser(context->getDiagnostics(), &table, pathData.source, + pathData.config); + if (!resParser.parse(&xmlParser)) { + return false; + } + + fin.close(); + } + + // Assign IDs to prepare the table for flattening. + IdAssigner idAssigner; + if (!idAssigner.consume(context, &table)) { + return false; + } + + // Flatten the table. + BigBuffer buffer(1024); + TableFlattenerOptions tableFlattenerOptions; + tableFlattenerOptions.useExtendedChunks = true; + TableFlattener flattener(&buffer, tableFlattenerOptions); + if (!flattener.consume(context, &table)) { + return false; + } + + // Build the output filename. + std::ofstream fout(outputPath, std::ofstream::binary); + if (!fout) { + context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + return false; + } + + // Write it to disk. + if (!util::writeAll(fout, buffer)) { + context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + return false; + } + return true; +} + +static bool compileXml(IAaptContext* context, const CompileOptions& options, + const ResourcePathData& pathData, const std::string& outputPath) { + + std::unique_ptr<XmlResource> xmlRes; + + { + std::ifstream fin(pathData.source.path, std::ifstream::binary); + if (!fin) { + context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno)); + return false; + } + + xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source); + + fin.close(); + } + + if (!xmlRes) { + return false; + } + + // Collect IDs that are defined here. + XmlIdCollector collector; + if (!collector.consume(context, xmlRes.get())) { + return false; + } + + xmlRes->file.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name }; + xmlRes->file.config = pathData.config; + xmlRes->file.source = pathData.source; + + BigBuffer buffer(1024); + ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &xmlRes->file); + + XmlFlattenerOptions xmlFlattenerOptions; + xmlFlattenerOptions.keepRawValues = true; + XmlFlattener flattener(fileExportWriter.getBuffer(), xmlFlattenerOptions); + if (!flattener.consume(context, xmlRes.get())) { + return false; + } + + fileExportWriter.finish(); + + std::ofstream fout(outputPath, std::ofstream::binary); + if (!fout) { + context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + return false; + } + + // Write it to disk. + if (!util::writeAll(fout, buffer)) { + context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + return false; + } + return true; +} + +static bool compilePng(IAaptContext* context, const CompileOptions& options, + const ResourcePathData& pathData, const std::string& outputPath) { + BigBuffer buffer(4096); + ResourceFile resFile; + resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name }; + resFile.config = pathData.config; + resFile.source = pathData.source; + + ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile); + + { + std::ifstream fin(pathData.source.path, std::ifstream::binary); + if (!fin) { + context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno)); + return false; + } + + Png png(context->getDiagnostics()); + if (!png.process(pathData.source, &fin, fileExportWriter.getBuffer(), {})) { + return false; + } + } + + fileExportWriter.finish(); + + std::ofstream fout(outputPath, std::ofstream::binary); + if (!fout) { + context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + return false; + } + + if (!util::writeAll(fout, buffer)) { + context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + return false; + } + return true; +} + +static bool compileFile(IAaptContext* context, const CompileOptions& options, + const ResourcePathData& pathData, const std::string& outputPath) { + BigBuffer buffer(256); + ResourceFile resFile; + resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name }; + resFile.config = pathData.config; + resFile.source = pathData.source; + + ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile); + + std::string errorStr; + Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr); + if (!f) { + context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr); + return false; + } + + std::ofstream fout(outputPath, std::ofstream::binary); + if (!fout) { + context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + return false; + } + + // Manually set the size and don't call finish(). This is because we are not copying from + // the buffer the entire file. + fileExportWriter.getChunkHeader()->size = + util::hostToDevice32(buffer.size() + f.value().getDataLength()); + if (!util::writeAll(fout, buffer)) { + context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + return false; + } + + if (!fout.write((const char*) f.value().getDataPtr(), f.value().getDataLength())) { + context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno)); + return false; + } + return true; +} + +class CompileContext : public IAaptContext { +private: + StdErrDiagnostics mDiagnostics; + +public: + IDiagnostics* getDiagnostics() override { + return &mDiagnostics; + } + + NameMangler* getNameMangler() override { + abort(); + return nullptr; + } + + StringPiece16 getCompilationPackage() override { + return {}; + } + + uint8_t getPackageId() override { + return 0x7f; + } + + ISymbolTable* getExternalSymbols() override { + abort(); + return nullptr; + } +}; + +/** + * Entry point for compilation phase. Parses arguments and dispatches to the correct steps. + */ +int compile(const std::vector<StringPiece>& args) { + CompileOptions options; + + Flags flags = Flags() + .requiredFlag("-o", "Output path", &options.outputPath) + .optionalSwitch("-v", "Enables verbose logging", &options.verbose); + if (!flags.parse("aapt2 compile", args, &std::cerr)) { + return 1; + } + + CompileContext context; + + std::vector<ResourcePathData> inputData; + inputData.reserve(flags.getArgs().size()); + + // Collect data from the path for each input file. + for (const std::string& arg : flags.getArgs()) { + std::string errorStr; + if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) { + inputData.push_back(std::move(pathData.value())); + } else { + context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")"); + return 1; + } + } + + bool error = false; + for (ResourcePathData& pathData : inputData) { + if (options.verbose) { + context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing"); + } + + if (pathData.resourceDir == u"values") { + // Overwrite the extension. + pathData.extension = "arsc"; + + const std::string outputFilename = buildIntermediateFilename( + options.outputPath, pathData); + if (!compileTable(&context, options, pathData, outputFilename)) { + error = true; + } + + } else { + const std::string outputFilename = buildIntermediateFilename(options.outputPath, + pathData); + if (const ResourceType* type = parseResourceType(pathData.resourceDir)) { + if (*type != ResourceType::kRaw) { + if (pathData.extension == "xml") { + if (!compileXml(&context, options, pathData, outputFilename)) { + error = true; + } + } else if (pathData.extension == "png" || pathData.extension == "9.png") { + if (!compilePng(&context, options, pathData, outputFilename)) { + error = true; + } + } else { + if (!compileFile(&context, options, pathData, outputFilename)) { + error = true; + } + } + } else { + if (!compileFile(&context, options, pathData, outputFilename)) { + error = true; + } + } + } else { + context.getDiagnostics()->error( + DiagMessage() << "invalid file path '" << pathData.source << "'"); + error = true; + } + } + } + + if (error) { + return 1; + } + return 0; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp new file mode 100644 index 000000000000..80c6bbc1abca --- /dev/null +++ b/tools/aapt2/compile/IdAssigner.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ResourceTable.h" + +#include "compile/IdAssigner.h" +#include "process/IResourceTableConsumer.h" +#include "util/Util.h" + +#include <bitset> +#include <cassert> +#include <set> + +namespace aapt { + +bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) { + std::bitset<256> usedTypeIds; + std::set<uint16_t> usedEntryIds; + + for (auto& package : table->packages) { + assert(package->id && "packages must have manually assigned IDs"); + + usedTypeIds.reset(); + + // Type ID 0 is invalid, reserve it. + usedTypeIds.set(0); + + // Collect used type IDs. + for (auto& type : package->types) { + if (type->id) { + usedEntryIds.clear(); + + if (usedTypeIds[type->id.value()]) { + // This ID is already taken! + context->getDiagnostics()->error(DiagMessage() + << "type '" << type->type << "' in " + << "package '" << package->name << "' has " + << "duplicate ID " + << std::hex << (int) type->id.value() + << std::dec); + return false; + } + + // Mark the type ID as taken. + usedTypeIds.set(type->id.value()); + } + + // Collect used entry IDs. + for (auto& entry : type->entries) { + if (entry->id) { + // Mark entry ID as taken. + if (!usedEntryIds.insert(entry->id.value()).second) { + // This ID existed before! + ResourceNameRef nameRef = + { package->name, type->type, entry->name }; + ResourceId takenId(package->id.value(), type->id.value(), + entry->id.value()); + context->getDiagnostics()->error(DiagMessage() + << "resource '" << nameRef << "' " + << "has duplicate ID '" + << takenId << "'"); + return false; + } + } + } + + // Assign unused entry IDs. + const auto endUsedEntryIter = usedEntryIds.end(); + auto nextUsedEntryIter = usedEntryIds.begin(); + uint16_t nextId = 0; + for (auto& entry : type->entries) { + if (!entry->id) { + // Assign the next available entryID. + while (nextUsedEntryIter != endUsedEntryIter && + nextId == *nextUsedEntryIter) { + nextId++; + ++nextUsedEntryIter; + } + entry->id = nextId++; + } + } + } + + // Assign unused type IDs. + size_t nextTypeId = 0; + for (auto& type : package->types) { + if (!type->id) { + while (nextTypeId < usedTypeIds.size() && usedTypeIds[nextTypeId]) { + nextTypeId++; + } + type->id = static_cast<uint8_t>(nextTypeId); + nextTypeId++; + } + } + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/ManifestParser.h b/tools/aapt2/compile/IdAssigner.h index f2e43d4bb695..514df3ad3861 100644 --- a/tools/aapt2/ManifestParser.h +++ b/tools/aapt2/compile/IdAssigner.h @@ -14,32 +14,21 @@ * limitations under the License. */ -#ifndef AAPT_MANIFEST_PARSER_H -#define AAPT_MANIFEST_PARSER_H +#ifndef AAPT_COMPILE_IDASSIGNER_H +#define AAPT_COMPILE_IDASSIGNER_H -#include "AppInfo.h" -#include "Logger.h" -#include "Source.h" -#include "XmlPullParser.h" +#include "process/IResourceTableConsumer.h" namespace aapt { -/* - * Parses an AndroidManifest.xml file and fills in an AppInfo structure with - * app data. +/** + * Assigns IDs to each resource in the table, respecting existing IDs and filling in gaps + * in between fixed ID assignments. */ -class ManifestParser { -public: - ManifestParser() = default; - ManifestParser(const ManifestParser&) = delete; - - bool parse(const Source& source, std::shared_ptr<XmlPullParser> parser, AppInfo* outInfo); - -private: - bool parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser, - AppInfo* outInfo); +struct IdAssigner : public IResourceTableConsumer { + bool consume(IAaptContext* context, ResourceTable* table) override; }; } // namespace aapt -#endif // AAPT_MANIFEST_PARSER_H +#endif /* AAPT_COMPILE_IDASSIGNER_H */ diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp new file mode 100644 index 000000000000..e25a17ab125e --- /dev/null +++ b/tools/aapt2/compile/IdAssigner_test.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compile/IdAssigner.h" + +#include "test/Context.h" +#include "test/Builders.h" + +#include <gtest/gtest.h> + +namespace aapt { + +::testing::AssertionResult verifyIds(ResourceTable* table); + +TEST(IdAssignerTest, AssignIds) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addSimple(u"@android:attr/foo") + .addSimple(u"@android:attr/bar") + .addSimple(u"@android:id/foo") + .setPackageId(u"android", 0x01) + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + IdAssigner assigner; + + ASSERT_TRUE(assigner.consume(context.get(), table.get())); + ASSERT_TRUE(verifyIds(table.get())); +} + +TEST(IdAssignerTest, AssignIdsWithReservedIds) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addSimple(u"@android:attr/foo", ResourceId(0x01040006)) + .addSimple(u"@android:attr/bar") + .addSimple(u"@android:id/foo") + .addSimple(u"@app:id/biz") + .setPackageId(u"android", 0x01) + .setPackageId(u"app", 0x7f) + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + IdAssigner assigner; + + ASSERT_TRUE(assigner.consume(context.get(), table.get())); + ASSERT_TRUE(verifyIds(table.get())); +} + +TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addSimple(u"@android:attr/foo", ResourceId(0x01040006)) + .addSimple(u"@android:attr/bar", ResourceId(0x01040006)) + .setPackageId(u"android", 0x01) + .setPackageId(u"app", 0x7f) + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + IdAssigner assigner; + + ASSERT_FALSE(assigner.consume(context.get(), table.get())); +} + +::testing::AssertionResult verifyIds(ResourceTable* table) { + std::set<uint8_t> packageIds; + for (auto& package : table->packages) { + if (!package->id) { + return ::testing::AssertionFailure() << "package " << package->name << " has no ID"; + } + + if (!packageIds.insert(package->id.value()).second) { + return ::testing::AssertionFailure() << "package " << package->name + << " has non-unique ID " << std::hex << (int) package->id.value() << std::dec; + } + } + + for (auto& package : table->packages) { + std::set<uint8_t> typeIds; + for (auto& type : package->types) { + if (!type->id) { + return ::testing::AssertionFailure() << "type " << type->type << " of package " + << package->name << " has no ID"; + } + + if (!typeIds.insert(type->id.value()).second) { + return ::testing::AssertionFailure() << "type " << type->type + << " of package " << package->name << " has non-unique ID " + << std::hex << (int) type->id.value() << std::dec; + } + } + + + for (auto& type : package->types) { + std::set<uint16_t> entryIds; + for (auto& entry : type->entries) { + if (!entry->id) { + return ::testing::AssertionFailure() << "entry " << entry->name << " of type " + << type->type << " of package " << package->name << " has no ID"; + } + + if (!entryIds.insert(entry->id.value()).second) { + return ::testing::AssertionFailure() << "entry " << entry->name + << " of type " << type->type << " of package " << package->name + << " has non-unique ID " + << std::hex << (int) entry->id.value() << std::dec; + } + } + } + } + return ::testing::AssertionSuccess() << "all IDs are unique and assigned"; +} + +} // namespace aapt diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/compile/Png.cpp index 4e9b68e8c95f..9837c4efc4d2 100644 --- a/tools/aapt2/Png.cpp +++ b/tools/aapt2/compile/Png.cpp @@ -14,11 +14,10 @@ * limitations under the License. */ -#include "BigBuffer.h" -#include "Logger.h" +#include "util/BigBuffer.h" #include "Png.h" #include "Source.h" -#include "Util.h" +#include "util/Util.h" #include <androidfw/ResourceTypes.h> #include <iostream> @@ -95,15 +94,14 @@ static void flushDataToStream(png_structp /*writePtr*/) { } static void logWarning(png_structp readPtr, png_const_charp warningMessage) { - SourceLogger* logger = reinterpret_cast<SourceLogger*>(png_get_error_ptr(readPtr)); - logger->warn() << warningMessage << "." << std::endl; + IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr)); + diag->warn(DiagMessage() << warningMessage); } -static bool readPng(png_structp readPtr, png_infop infoPtr, PngInfo* outInfo, - std::string* outError) { +static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) { if (setjmp(png_jmpbuf(readPtr))) { - *outError = "failed reading png"; + diag->error(DiagMessage() << "failed reading png"); return false; } @@ -229,7 +227,7 @@ static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void* #define MAX(a,b) ((a)>(b)?(a):(b)) #define ABS(a) ((a)<0?-(a):(a)) -static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int grayscaleTolerance, +static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance, png_colorp rgbPalette, png_bytep alphaPalette, int *paletteEntries, bool *hasTransparency, int *colorType, png_bytepp outRows) { @@ -363,9 +361,9 @@ static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int gr *colorType = PNG_COLOR_TYPE_PALETTE; } else { if (maxGrayDeviation <= grayscaleTolerance) { - logger->note() << "forcing image to gray (max deviation = " << maxGrayDeviation - << ")." - << std::endl; + diag->note(DiagMessage() + << "forcing image to gray (max deviation = " + << maxGrayDeviation << ")"); *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA; } else { *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; @@ -411,10 +409,10 @@ static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int gr } } -static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info, - int grayScaleTolerance, SourceLogger* logger, std::string* outError) { +static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info, + int grayScaleTolerance) { if (setjmp(png_jmpbuf(writePtr))) { - *outError = "failed to write png"; + diag->error(DiagMessage() << "failed to write png"); return false; } @@ -442,9 +440,9 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info, png_set_compression_level(writePtr, Z_BEST_COMPRESSION); if (kDebug) { - logger->note() << "writing image: w = " << info->width - << ", h = " << info->height - << std::endl; + diag->note(DiagMessage() + << "writing image: w = " << info->width + << ", h = " << info->height); } png_color rgbPalette[256]; @@ -452,7 +450,7 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info, bool hasTransparency; int paletteEntries; - analyze_image(logger, *info, grayScaleTolerance, rgbPalette, alphaPalette, + analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette, &paletteEntries, &hasTransparency, &colorType, outRows); // If the image is a 9-patch, we need to preserve it as a ARGB file to make @@ -465,22 +463,22 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info, if (kDebug) { switch (colorType) { case PNG_COLOR_TYPE_PALETTE: - logger->note() << "has " << paletteEntries - << " colors" << (hasTransparency ? " (with alpha)" : "") - << ", using PNG_COLOR_TYPE_PALLETTE." - << std::endl; + diag->note(DiagMessage() + << "has " << paletteEntries + << " colors" << (hasTransparency ? " (with alpha)" : "") + << ", using PNG_COLOR_TYPE_PALLETTE"); break; case PNG_COLOR_TYPE_GRAY: - logger->note() << "is opaque gray, using PNG_COLOR_TYPE_GRAY." << std::endl; + diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY"); break; case PNG_COLOR_TYPE_GRAY_ALPHA: - logger->note() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA." << std::endl; + diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA"); break; case PNG_COLOR_TYPE_RGB: - logger->note() << "is opaque RGB, using PNG_COLOR_TYPE_RGB." << std::endl; + diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB"); break; case PNG_COLOR_TYPE_RGB_ALPHA: - logger->note() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA." << std::endl; + diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA"); break; } } @@ -511,7 +509,7 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info, // base 9 patch data if (kDebug) { - logger->note() << "adding 9-patch info..." << std::endl; + diag->note(DiagMessage() << "adding 9-patch info.."); } strcpy((char*)unknowns[pIndex].name, "npTc"); unknowns[pIndex].data = (png_byte*) info->serialize9Patch(); @@ -587,10 +585,10 @@ static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info, &compressionType, nullptr); if (kDebug) { - logger->note() << "image written: w = " << width << ", h = " << height - << ", d = " << bitDepth << ", colors = " << colorType - << ", inter = " << interlaceType << ", comp = " << compressionType - << std::endl; + diag->note(DiagMessage() + << "image written: w = " << width << ", h = " << height + << ", d = " << bitDepth << ", colors = " << colorType + << ", inter = " << interlaceType << ", comp = " << compressionType); } return true; } @@ -1192,23 +1190,22 @@ getout: } -bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffer, - const Options& options, std::string* outError) { +bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer, + const PngOptions& options) { png_byte signature[kPngSignatureSize]; // Read the PNG signature first. - if (!input.read(reinterpret_cast<char*>(signature), kPngSignatureSize)) { - *outError = strerror(errno); + if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) { + mDiag->error(DiagMessage() << strerror(errno)); return false; } // If the PNG signature doesn't match, bail early. if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { - *outError = "not a valid png file"; + mDiag->error(DiagMessage() << "not a valid png file"); return false; } - SourceLogger logger(source); bool result = false; png_structp readPtr = nullptr; png_infop infoPtr = nullptr; @@ -1218,40 +1215,42 @@ bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffe readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); if (!readPtr) { - *outError = "failed to allocate read ptr"; + mDiag->error(DiagMessage() << "failed to allocate read ptr"); goto bail; } infoPtr = png_create_info_struct(readPtr); if (!infoPtr) { - *outError = "failed to allocate info ptr"; + mDiag->error(DiagMessage() << "failed to allocate info ptr"); goto bail; } - png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(&logger), nullptr, logWarning); + png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning); // Set the read function to read from std::istream. - png_set_read_fn(readPtr, (png_voidp)&input, readDataFromStream); + png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream); - if (!readPng(readPtr, infoPtr, &pngInfo, outError)) { + if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) { goto bail; } if (util::stringEndsWith<char>(source.path, ".9.png")) { - if (!do9Patch(&pngInfo, outError)) { + std::string errorMsg; + if (!do9Patch(&pngInfo, &errorMsg)) { + mDiag->error(DiagMessage() << errorMsg); goto bail; } } writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); if (!writePtr) { - *outError = "failed to allocate write ptr"; + mDiag->error(DiagMessage() << "failed to allocate write ptr"); goto bail; } writeInfoPtr = png_create_info_struct(writePtr); if (!writeInfoPtr) { - *outError = "failed to allocate write info ptr"; + mDiag->error(DiagMessage() << "failed to allocate write info ptr"); goto bail; } @@ -1260,8 +1259,7 @@ bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffe // Set the write function to write to std::ostream. png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream); - if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger, - outError)) { + if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) { goto bail; } diff --git a/tools/aapt2/Png.h b/tools/aapt2/compile/Png.h index 4577ab89d2d9..345ff6c56870 100644 --- a/tools/aapt2/Png.h +++ b/tools/aapt2/compile/Png.h @@ -17,7 +17,8 @@ #ifndef AAPT_PNG_H #define AAPT_PNG_H -#include "BigBuffer.h" +#include "util/BigBuffer.h" +#include "Diagnostics.h" #include "Source.h" #include <iostream> @@ -25,13 +26,20 @@ namespace aapt { -struct Png { - struct Options { - int grayScaleTolerance = 0; - }; +struct PngOptions { + int grayScaleTolerance = 0; +}; + +class Png { +public: + Png(IDiagnostics* diag) : mDiag(diag) { + } + + bool process(const Source& source, std::istream* input, BigBuffer* outBuffer, + const PngOptions& options); - bool process(const Source& source, std::istream& input, BigBuffer* outBuffer, - const Options& options, std::string* outError); +private: + IDiagnostics* mDiag; }; } // namespace aapt diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp new file mode 100644 index 000000000000..dfdf710967f9 --- /dev/null +++ b/tools/aapt2/compile/XmlIdCollector.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ResourceUtils.h" +#include "ResourceValues.h" +#include "XmlDom.h" + +#include "compile/XmlIdCollector.h" + +#include <algorithm> +#include <vector> + +namespace aapt { + +namespace { + +static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) { + return a.name < b; +} + +struct IdCollector : public xml::Visitor { + using xml::Visitor::visit; + + std::vector<SourcedResourceName>* mOutSymbols; + + IdCollector(std::vector<SourcedResourceName>* outSymbols) : mOutSymbols(outSymbols) { + } + + void visit(xml::Element* element) override { + for (xml::Attribute& attr : element->attributes) { + ResourceNameRef name; + bool create = false; + if (ResourceUtils::tryParseReference(attr.value, &name, &create, nullptr)) { + if (create && name.type == ResourceType::kId) { + auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(), + name, cmpName); + if (iter == mOutSymbols->end() || iter->name != name) { + mOutSymbols->insert(iter, SourcedResourceName{ name.toResourceName(), + element->lineNumber }); + } + } + } + } + + xml::Visitor::visit(element); + } +}; + +} // namespace + +bool XmlIdCollector::consume(IAaptContext* context, XmlResource* xmlRes) { + xmlRes->file.exportedSymbols.clear(); + IdCollector collector(&xmlRes->file.exportedSymbols); + xmlRes->root->accept(&collector); + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/Compat_test.cpp b/tools/aapt2/compile/XmlIdCollector.h index 96aee44c6c95..96a58f22fab6 100644 --- a/tools/aapt2/Compat_test.cpp +++ b/tools/aapt2/compile/XmlIdCollector.h @@ -14,20 +14,17 @@ * limitations under the License. */ -#include <gtest/gtest.h> +#ifndef AAPT_XMLIDCOLLECTOR_H +#define AAPT_XMLIDCOLLECTOR_H -namespace aapt { - -TEST(CompatTest, VersionAttributesInStyle) { -} - -TEST(CompatTest, VersionAttributesInXML) { -} +#include "process/IResourceTableConsumer.h" -TEST(CompatTest, DoNotOverrideExistingVersionedFiles) { -} +namespace aapt { -TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) { -} +struct XmlIdCollector : public IXmlResourceConsumer { + bool consume(IAaptContext* context, XmlResource* xmlRes) override; +}; } // namespace aapt + +#endif /* AAPT_XMLIDCOLLECTOR_H */ diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp new file mode 100644 index 000000000000..c703f451f05e --- /dev/null +++ b/tools/aapt2/compile/XmlIdCollector_test.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compile/XmlIdCollector.h" +#include "test/Builders.h" +#include "test/Context.h" + +#include <algorithm> +#include <gtest/gtest.h> + +namespace aapt { + +TEST(XmlIdCollectorTest, CollectsIds) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/foo" + text="@+id/bar"> + <SubView android:id="@+id/car" + class="@+id/bar"/> + </View>)EOF"); + + XmlIdCollector collector; + ASSERT_TRUE(collector.consume(context.get(), doc.get())); + + EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(), + SourcedResourceName{ test::parseNameOrDie(u"@id/foo"), 3u })); + + EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(), + SourcedResourceName{ test::parseNameOrDie(u"@id/bar"), 3u })); + + EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(), + SourcedResourceName{ test::parseNameOrDie(u"@id/car"), 6u })); +} + +TEST(XmlIdCollectorTest, DontCollectNonIds) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>"); + + XmlIdCollector collector; + ASSERT_TRUE(collector.consume(context.get(), doc.get())); + + EXPECT_TRUE(doc->file.exportedSymbols.empty()); +} + +} // namespace aapt diff --git a/tools/aapt2/data/AndroidManifest.xml b/tools/aapt2/data/AndroidManifest.xml index 8533c28c24bb..d3b2fbe515ae 100644 --- a/tools/aapt2/data/AndroidManifest.xml +++ b/tools/aapt2/data/AndroidManifest.xml @@ -2,6 +2,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.app"> <application - android:name=".Activity"> + android:name=".ActivityMain"> </application> </manifest> diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile index 91ff5fee6477..37012decdf05 100644 --- a/tools/aapt2/data/Makefile +++ b/tools/aapt2/data/Makefile @@ -21,63 +21,41 @@ LOCAL_PROGUARD := out/proguard.rule # AAPT2 custom rules. ## -PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk -PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk +PRIVATE_R_FILE := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java +$(info PRIVATE_R_FILE = $(PRIVATE_R_FILE)) # Eg: framework.apk, etc. PRIVATE_INCLUDES := $(FRAMEWORK) $(info PRIVATE_INCLUDES = $(PRIVATE_INCLUDES)) -# Eg: gen/com/android/app/R.java -PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java -$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA)) - # Eg: res/drawable/icon.png, res/values/styles.xml PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f) $(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES)) -# Eg: drawable, values, layouts -PRIVATE_RESOURCE_TYPES := \ - $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES)))) -$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES)) - -# Eg: out/values-v4.apk, out/drawable-xhdpi.apk -PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES)) -$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES)) +PRIVATE_RESOURCE_OBJECTS := $(subst /,_,$(patsubst $(LOCAL_RESOURCE_DIR)/%,%,$(filter $(LOCAL_RESOURCE_DIR)/values%,$(PRIVATE_RESOURCES)))) +PRIVATE_RESOURCE_OBJECTS := $(addprefix $(LOCAL_OUT)/,$(PRIVATE_RESOURCE_OBJECTS:.xml=.arsc.flat)) +$(info PRIVATE_RESOURCE_OBJECTS = $(PRIVATE_RESOURCE_OBJECTS)) -# Generates rules for collect phase. -# $1: Resource type (values-v4) -# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml -define make-collect-rule -$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES)) - $(AAPT) compile -o $$@ $$^ -endef +PRIVATE_FILE_OBJECTS := $(subst /,_,$(patsubst $(LOCAL_RESOURCE_DIR)/%,%,$(filter-out $(LOCAL_RESOURCE_DIR)/values%,$(PRIVATE_RESOURCES)))) +PRIVATE_FILE_OBJECTS := $(addprefix $(LOCAL_OUT)/,$(addsuffix .flat,$(PRIVATE_FILE_OBJECTS))) +$(info PRIVATE_FILE_OBJECTS = $(PRIVATE_FILE_OBJECTS)) -# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml -$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d))) +.SECONDEXPANSION: -# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk -$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml - $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) --proguard $(LOCAL_PROGUARD) -v +$(LOCAL_OUT)/%.arsc.flat: $(LOCAL_RESOURCE_DIR)/$$(subst _,/,%).xml + $(AAPT) compile -o $(LOCAL_OUT) $< -# R.java: gen/com/android/app/R.java <- out/resources.arsc -# No action since R.java is generated when out/resources.arsc is. -$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED) +$(LOCAL_OUT)/%.flat: $(LOCAL_RESOURCE_DIR)/$$(subst _,/,%) + $(AAPT) compile -o $(LOCAL_OUT) $< -# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/* -$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED) - $(ZIPALIGN) $< $@ +$(LOCAL_PROGUARD) $(LOCAL_OUT)/package.apk: AndroidManifest.xml +$(PRIVATE_R_FILE) $(LOCAL_PROGUARD) $(LOCAL_OUT)/package.apk: $(PRIVATE_FILE_OBJECTS) $(PRIVATE_RESOURCE_OBJECTS) + $(AAPT) link -o $(LOCAL_OUT)/package.apk --manifest AndroidManifest.xml --java $(LOCAL_GEN) --proguard $(LOCAL_PROGUARD) -I $(PRIVATE_INCLUDES) $(filter-out AndroidManifest.xml,$^) -v # Create the out directory if needed. dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT)) -.PHONY: java -java: $(PRIVATE_R_JAVA) - -.PHONY: assemble -assemble: $(PRIVATE_APK_ALIGNED) - .PHONY: all -all: assemble java +all: $(LOCAL_OUT)/package.apk $(LOCAL_PROGUARD) $(PRIVATE_R_FILE) .DEFAULT_GOAL := all diff --git a/tools/aapt2/data/res/layout-v21/main.xml b/tools/aapt2/data/res/layout-v21/main.xml new file mode 100644 index 000000000000..959b349bfaff --- /dev/null +++ b/tools/aapt2/data/res/layout-v21/main.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:support="http://schemas.android.com/apk/res/android.appcompat" + android:id="@+id/view" + android:layout_width="match_parent" + android:layout_height="wrap_content"> +</LinearLayout> diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/data/res/layout/main.xml index 50a51d99ad0a..8a5e9e8ee58d 100644 --- a/tools/aapt2/data/res/layout/main.xml +++ b/tools/aapt2/data/res/layout/main.xml @@ -14,8 +14,8 @@ android:layout_width="1dp" android:onClick="doClick" android:text="@{user.name}" + android:background="#ffffff" android:layout_height="match_parent" - app:layout_width="@support:bool/allow" app:flags="complex|weak" android:colorAccent="#ffffff"/> </LinearLayout> diff --git a/tools/aapt2/data/res/raw/test.txt b/tools/aapt2/data/res/raw/test.txt new file mode 100644 index 000000000000..b14df6442ea5 --- /dev/null +++ b/tools/aapt2/data/res/raw/test.txt @@ -0,0 +1 @@ +Hi diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/data/res/values/styles.xml index d0b19a3a881b..2bbdad1bcbf1 100644 --- a/tools/aapt2/data/res/values/styles.xml +++ b/tools/aapt2/data/res/values/styles.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources xmlns:lib="http://schemas.android.com/apk/res/android.appcompat"> - <style name="App" parent="android.appcompat:Platform.AppCompat"> + <style name="App"> <item name="android:background">@color/primary</item> <item name="android:colorPrimary">@color/primary</item> <item name="android:colorPrimaryDark">@color/primary_dark</item> @@ -8,8 +8,8 @@ </style> <attr name="custom" format="reference" /> <style name="Pop"> - <item name="custom">@drawable/image</item> - <item name="android:focusable">@lib:bool/allow</item> + <item name="custom">@android:drawable/btn_default</item> + <item name="android:focusable">true</item> </style> <string name="yo">@string/wow</string> diff --git a/tools/aapt2/data/res/values/test.xml b/tools/aapt2/data/res/values/test.xml index d3ead34d043c..d7ab1c8ddde9 100644 --- a/tools/aapt2/data/res/values/test.xml +++ b/tools/aapt2/data/res/values/test.xml @@ -3,7 +3,7 @@ <string name="hooha"><font bgcolor="#ffffff">Hey guys!</font> <xliff:g>My</xliff:g> name is <b>Adam</b>. How <b><i>are</i></b> you?</string> <public name="hooha" type="string" id="0x7f020001"/> <string name="wow">@android:string/ok</string> - <public name="image" type="drawable" id="0x7f060000" /> + <public name="layout_width" type="attr" /> <attr name="layout_width" format="boolean" /> <attr name="flags"> <flag name="complex" value="1" /> diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp new file mode 100644 index 000000000000..6db13b86fbfa --- /dev/null +++ b/tools/aapt2/flatten/Archive.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "flatten/Archive.h" +#include "util/Files.h" +#include "util/StringPiece.h" + +#include <fstream> +#include <memory> +#include <string> +#include <vector> +#include <ziparchive/zip_writer.h> + +namespace aapt { + +namespace { + +struct DirectoryWriter : public IArchiveWriter { + std::string mOutDir; + std::vector<std::unique_ptr<ArchiveEntry>> mEntries; + + explicit DirectoryWriter(const StringPiece& outDir) : mOutDir(outDir.toString()) { + } + + ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, + const BigBuffer& buffer) override { + std::string fullPath = mOutDir; + file::appendPath(&fullPath, path); + file::mkdirs(file::getStem(fullPath)); + + std::ofstream fout(fullPath, std::ofstream::binary); + if (!fout) { + return nullptr; + } + + if (!util::writeAll(fout, buffer)) { + return nullptr; + } + + mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, buffer.size())); + return mEntries.back().get(); + } + + ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap, + size_t offset, size_t len) override { + std::string fullPath = mOutDir; + file::appendPath(&fullPath, path); + file::mkdirs(file::getStem(fullPath)); + + std::ofstream fout(fullPath, std::ofstream::binary); + if (!fout) { + return nullptr; + } + + if (!fout.write((const char*) fileMap->getDataPtr() + offset, len)) { + return nullptr; + } + + mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, len)); + return mEntries.back().get(); + } + + virtual ~DirectoryWriter() { + + } +}; + +struct ZipFileWriter : public IArchiveWriter { + FILE* mFile; + std::unique_ptr<ZipWriter> mWriter; + std::vector<std::unique_ptr<ArchiveEntry>> mEntries; + + explicit ZipFileWriter(const StringPiece& path) { + mFile = fopen(path.data(), "w+b"); + if (mFile) { + mWriter = util::make_unique<ZipWriter>(mFile); + } + } + + ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, + const BigBuffer& buffer) override { + if (!mWriter) { + return nullptr; + } + + size_t zipFlags = 0; + if (flags & ArchiveEntry::kCompress) { + zipFlags |= ZipWriter::kCompress; + } + + if (flags & ArchiveEntry::kAlign) { + zipFlags |= ZipWriter::kAlign32; + } + + int32_t result = mWriter->StartEntry(path.data(), zipFlags); + if (result != 0) { + return nullptr; + } + + for (const BigBuffer::Block& b : buffer) { + result = mWriter->WriteBytes(reinterpret_cast<const uint8_t*>(b.buffer.get()), b.size); + if (result != 0) { + return nullptr; + } + } + + result = mWriter->FinishEntry(); + if (result != 0) { + return nullptr; + } + + mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, buffer.size())); + return mEntries.back().get(); + } + + ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap, + size_t offset, size_t len) override { + if (!mWriter) { + return nullptr; + } + + size_t zipFlags = 0; + if (flags & ArchiveEntry::kCompress) { + zipFlags |= ZipWriter::kCompress; + } + + if (flags & ArchiveEntry::kAlign) { + zipFlags |= ZipWriter::kAlign32; + } + + int32_t result = mWriter->StartEntry(path.data(), zipFlags); + if (result != 0) { + return nullptr; + } + + result = mWriter->WriteBytes((const char*) fileMap->getDataPtr() + offset, len); + if (result != 0) { + return nullptr; + } + + result = mWriter->FinishEntry(); + if (result != 0) { + return nullptr; + } + + mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, len)); + return mEntries.back().get(); + } + + virtual ~ZipFileWriter() { + if (mWriter) { + mWriter->Finish(); + fclose(mFile); + } + } +}; + +} // namespace + +std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path) { + return util::make_unique<DirectoryWriter>(path); +} + +std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path) { + return util::make_unique<ZipFileWriter>(path); +} + +} // namespace aapt diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h new file mode 100644 index 000000000000..c4ddeb3163c0 --- /dev/null +++ b/tools/aapt2/flatten/Archive.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_FLATTEN_ARCHIVE_H +#define AAPT_FLATTEN_ARCHIVE_H + +#include "util/BigBuffer.h" +#include "util/Files.h" +#include "util/StringPiece.h" + +#include <fstream> +#include <memory> +#include <string> +#include <vector> + +namespace aapt { + +struct ArchiveEntry { + enum : uint32_t { + kCompress = 0x01, + kAlign = 0x02, + }; + + std::string path; + uint32_t flags; + size_t uncompressedSize; +}; + +struct IArchiveWriter { + virtual ~IArchiveWriter() = default; + + virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, + const BigBuffer& buffer) = 0; + virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, + android::FileMap* fileMap, size_t offset, size_t len) = 0; +}; + +std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path); + +std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path); + +} // namespace aapt + +#endif /* AAPT_FLATTEN_ARCHIVE_H */ diff --git a/tools/aapt2/flatten/ChunkWriter.h b/tools/aapt2/flatten/ChunkWriter.h new file mode 100644 index 000000000000..de1d87a57e6d --- /dev/null +++ b/tools/aapt2/flatten/ChunkWriter.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_FLATTEN_CHUNKWRITER_H +#define AAPT_FLATTEN_CHUNKWRITER_H + +#include "util/BigBuffer.h" +#include "util/Util.h" + +#include <androidfw/ResourceTypes.h> + +namespace aapt { + +class ChunkWriter { +private: + BigBuffer* mBuffer; + size_t mStartSize = 0; + android::ResChunk_header* mHeader = nullptr; + +public: + explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) { + } + + ChunkWriter(const ChunkWriter&) = delete; + ChunkWriter& operator=(const ChunkWriter&) = delete; + ChunkWriter(ChunkWriter&&) = default; + ChunkWriter& operator=(ChunkWriter&&) = default; + + template <typename T> + inline T* startChunk(uint16_t type) { + mStartSize = mBuffer->size(); + T* chunk = mBuffer->nextBlock<T>(); + mHeader = &chunk->header; + mHeader->type = util::hostToDevice16(type); + mHeader->headerSize = util::hostToDevice16(sizeof(T)); + return chunk; + } + + template <typename T> + inline T* nextBlock(size_t count = 1) { + return mBuffer->nextBlock<T>(count); + } + + inline BigBuffer* getBuffer() { + return mBuffer; + } + + inline android::ResChunk_header* getChunkHeader() { + return mHeader; + } + + inline size_t size() { + return mBuffer->size() - mStartSize; + } + + inline android::ResChunk_header* finish() { + mBuffer->align4(); + mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize); + return mHeader; + } +}; + +template <> +inline android::ResChunk_header* ChunkWriter::startChunk(uint16_t type) { + mStartSize = mBuffer->size(); + mHeader = mBuffer->nextBlock<android::ResChunk_header>(); + mHeader->type = util::hostToDevice16(type); + mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header)); + return mHeader; +} + +} // namespace aapt + +#endif /* AAPT_FLATTEN_CHUNKWRITER_H */ diff --git a/tools/aapt2/flatten/FileExportWriter.h b/tools/aapt2/flatten/FileExportWriter.h new file mode 100644 index 000000000000..7688fa71246e --- /dev/null +++ b/tools/aapt2/flatten/FileExportWriter.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_FLATTEN_FILEEXPORTWRITER_H +#define AAPT_FLATTEN_FILEEXPORTWRITER_H + +#include "StringPool.h" + +#include "flatten/ResourceTypeExtensions.h" +#include "flatten/ChunkWriter.h" +#include "process/IResourceTableConsumer.h" +#include "util/BigBuffer.h" +#include "util/Util.h" + +#include <androidfw/ResourceTypes.h> +#include <utils/misc.h> + +namespace aapt { + +static ChunkWriter wrapBufferWithFileExportHeader(BigBuffer* buffer, ResourceFile* res) { + ChunkWriter fileExportWriter(buffer); + FileExport_header* fileExport = fileExportWriter.startChunk<FileExport_header>( + RES_FILE_EXPORT_TYPE); + + ExportedSymbol* symbolRefs = nullptr; + if (!res->exportedSymbols.empty()) { + symbolRefs = fileExportWriter.nextBlock<ExportedSymbol>( + res->exportedSymbols.size()); + } + fileExport->exportedSymbolCount = util::hostToDevice32(res->exportedSymbols.size()); + + StringPool symbolExportPool; + memcpy(fileExport->magic, "AAPT", NELEM(fileExport->magic)); + fileExport->config = res->config; + fileExport->config.swapHtoD(); + fileExport->name.index = util::hostToDevice32(symbolExportPool.makeRef(res->name.toString()) + .getIndex()); + fileExport->source.index = util::hostToDevice32(symbolExportPool.makeRef(util::utf8ToUtf16( + res->source.path)).getIndex()); + + for (const SourcedResourceName& name : res->exportedSymbols) { + symbolRefs->name.index = util::hostToDevice32(symbolExportPool.makeRef(name.name.toString()) + .getIndex()); + symbolRefs->line = util::hostToDevice32(name.line); + symbolRefs++; + } + + StringPool::flattenUtf16(fileExportWriter.getBuffer(), symbolExportPool); + return fileExportWriter; +} + +} // namespace aapt + +#endif /* AAPT_FLATTEN_FILEEXPORTWRITER_H */ diff --git a/tools/aapt2/flatten/FileExportWriter_test.cpp b/tools/aapt2/flatten/FileExportWriter_test.cpp new file mode 100644 index 000000000000..32fc203c4dee --- /dev/null +++ b/tools/aapt2/flatten/FileExportWriter_test.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Resource.h" + +#include "flatten/FileExportWriter.h" +#include "util/BigBuffer.h" +#include "util/Util.h" + +#include "test/Common.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(FileExportWriterTest, FlattenResourceFileDataWithNoExports) { + ResourceFile resFile = { + test::parseNameOrDie(u"@android:layout/main.xml"), + test::parseConfigOrDie("sw600dp-v4"), + Source{ "res/layout/main.xml" }, + }; + + BigBuffer buffer(1024); + ChunkWriter writer = wrapBufferWithFileExportHeader(&buffer, &resFile); + *writer.getBuffer()->nextBlock<uint32_t>() = 42u; + writer.finish(); + + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + + // There should be more data (string pool) besides the header and our data. + ASSERT_GT(buffer.size(), sizeof(FileExport_header) + sizeof(uint32_t)); + + // Write at the end of this chunk is our data. + uint32_t* val = (uint32_t*)(data.get() + buffer.size()) - 1; + EXPECT_EQ(*val, 42u); +} + +} // namespace aapt diff --git a/tools/aapt2/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h index dcbe9233f6b0..af0afefe1676 100644 --- a/tools/aapt2/ResourceTypeExtensions.h +++ b/tools/aapt2/flatten/ResourceTypeExtensions.h @@ -30,6 +30,12 @@ namespace aapt { * future collisions. */ enum { + /** + * A chunk that contains an entire file that + * has been compiled. + */ + RES_FILE_EXPORT_TYPE = 0x000c, + RES_TABLE_PUBLIC_TYPE = 0x000d, /** @@ -60,6 +66,48 @@ struct ExtendedTypes { }; }; +/** + * Followed by exportedSymbolCount ExportedSymbol structs, followed by the string pool. + */ +struct FileExport_header { + android::ResChunk_header header; + + /** + * MAGIC value. Must be 'AAPT' (0x41415054) + */ + uint8_t magic[4]; + + /** + * Version of AAPT that built this file. + */ + uint32_t version; + + /** + * The resource name. + */ + android::ResStringPool_ref name; + + /** + * Configuration of this file. + */ + android::ResTable_config config; + + /** + * Original source path of this file. + */ + android::ResStringPool_ref source; + + /** + * Number of symbols exported by this file. + */ + uint32_t exportedSymbolCount; +}; + +struct ExportedSymbol { + android::ResStringPool_ref name; + uint32_t line; +}; + struct Public_header { android::ResChunk_header header; @@ -142,6 +190,16 @@ struct ResTable_entry_source { uint32_t line; }; +/** + * An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout + * struct. + */ +struct ResTable_entry_ext { + android::ResTable_entry entry; + android::ResTable_ref parent; + uint32_t count; +}; + } // namespace aapt #endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp new file mode 100644 index 000000000000..427ab18567bd --- /dev/null +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -0,0 +1,644 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" + +#include "flatten/ChunkWriter.h" +#include "flatten/ResourceTypeExtensions.h" +#include "flatten/TableFlattener.h" +#include "util/BigBuffer.h" + +#include <type_traits> +#include <numeric> +#include <utils/misc.h> + +using namespace android; + +namespace aapt { + +namespace { + +template <typename T> +static bool cmpIds(const T* a, const T* b) { + return a->id.value() < b->id.value(); +} + +static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) { + if (len == 0) { + return; + } + + size_t i; + const char16_t* srcData = src.data(); + for (i = 0; i < len - 1 && i < src.size(); i++) { + dst[i] = util::hostToDevice16((uint16_t) srcData[i]); + } + dst[i] = 0; +} + +struct FlatEntry { + ResourceEntry* entry; + Value* value; + uint32_t entryKey; + uint32_t sourcePathKey; + uint32_t sourceLine; +}; + +struct SymbolWriter { + struct Entry { + StringPool::Ref name; + size_t offset; + }; + + StringPool pool; + std::vector<Entry> symbols; + + void addSymbol(const ResourceNameRef& name, size_t offset) { + symbols.push_back(Entry{ pool.makeRef(name.package.toString() + u":" + + toString(name.type).toString() + u"/" + + name.entry.toString()), offset }); + } +}; + +struct MapFlattenVisitor : public RawValueVisitor { + using RawValueVisitor::visit; + + SymbolWriter* mSymbols; + FlatEntry* mEntry; + BigBuffer* mBuffer; + size_t mEntryCount = 0; + Maybe<uint32_t> mParentIdent; + Maybe<ResourceNameRef> mParentName; + + MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer) : + mSymbols(symbols), mEntry(entry), mBuffer(buffer) { + } + + void flattenKey(Reference* key, ResTable_map* outEntry) { + if (!key->id) { + assert(key->name && "reference must have a name"); + + outEntry->name.ident = util::hostToDevice32(0); + mSymbols->addSymbol(key->name.value(), (mBuffer->size() - sizeof(ResTable_map)) + + offsetof(ResTable_map, name)); + } else { + outEntry->name.ident = util::hostToDevice32(key->id.value().id); + } + } + + void flattenValue(Item* value, ResTable_map* outEntry) { + if (Reference* ref = valueCast<Reference>(value)) { + if (!ref->id) { + assert(ref->name && "reference must have a name"); + + mSymbols->addSymbol(ref->name.value(), (mBuffer->size() - sizeof(ResTable_map)) + + offsetof(ResTable_map, value) + offsetof(Res_value, data)); + } + } + + bool result = value->flatten(&outEntry->value); + assert(result && "flatten failed"); + } + + void flattenEntry(Reference* key, Item* value) { + ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>(); + flattenKey(key, outEntry); + flattenValue(value, outEntry); + outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value)); + mEntryCount++; + } + + void visit(Attribute* attr) override { + { + Reference key(ResourceId{ ResTable_map::ATTR_TYPE }); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask); + flattenEntry(&key, &val); + } + + for (Attribute::Symbol& s : attr->symbols) { + BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value); + flattenEntry(&s.symbol, &val); + } + } + + static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) { + if (a.key.id) { + if (b.key.id) { + return a.key.id.value() < b.key.id.value(); + } + return true; + } else if (!b.key.id) { + return a.key.name.value() < b.key.name.value(); + } + return false; + } + + void visit(Style* style) override { + if (style->parent) { + if (!style->parent.value().id) { + assert(style->parent.value().name && "reference must have a name"); + mParentName = style->parent.value().name; + } else { + mParentIdent = style->parent.value().id.value().id; + } + } + + // Sort the style. + std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries); + + for (Style::Entry& entry : style->entries) { + flattenEntry(&entry.key, entry.value.get()); + } + } + + void visit(Styleable* styleable) override { + for (auto& attrRef : styleable->entries) { + BinaryPrimitive val(Res_value{}); + flattenEntry(&attrRef, &val); + } + } + + void visit(Array* array) override { + for (auto& item : array->items) { + ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>(); + flattenValue(item.get(), outEntry); + outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value)); + mEntryCount++; + } + } + + void visit(Plural* plural) override { + const size_t count = plural->values.size(); + for (size_t i = 0; i < count; i++) { + if (!plural->values[i]) { + continue; + } + + ResourceId q; + switch (i) { + case Plural::Zero: + q.id = android::ResTable_map::ATTR_ZERO; + break; + + case Plural::One: + q.id = android::ResTable_map::ATTR_ONE; + break; + + case Plural::Two: + q.id = android::ResTable_map::ATTR_TWO; + break; + + case Plural::Few: + q.id = android::ResTable_map::ATTR_FEW; + break; + + case Plural::Many: + q.id = android::ResTable_map::ATTR_MANY; + break; + + case Plural::Other: + q.id = android::ResTable_map::ATTR_OTHER; + break; + + default: + assert(false); + break; + } + + Reference key(q); + flattenEntry(&key, plural->values[i].get()); + } + } +}; + +struct PackageFlattener { + IDiagnostics* mDiag; + TableFlattenerOptions mOptions; + ResourceTable* mTable; + ResourceTablePackage* mPackage; + SymbolWriter mSymbols; + StringPool mTypePool; + StringPool mKeyPool; + StringPool mSourcePool; + + template <typename T> + T* writeEntry(FlatEntry* entry, BigBuffer* buffer) { + static_assert(std::is_same<ResTable_entry, T>::value || + std::is_same<ResTable_entry_ext, T>::value, + "T must be ResTable_entry or ResTable_entry_ext"); + + T* result = buffer->nextBlock<T>(); + ResTable_entry* outEntry = (ResTable_entry*)(result); + if (entry->entry->publicStatus.isPublic) { + outEntry->flags |= ResTable_entry::FLAG_PUBLIC; + } + + if (entry->value->isWeak()) { + outEntry->flags |= ResTable_entry::FLAG_WEAK; + } + + if (!entry->value->isItem()) { + outEntry->flags |= ResTable_entry::FLAG_COMPLEX; + } + + outEntry->key.index = util::hostToDevice32(entry->entryKey); + outEntry->size = sizeof(T); + + if (mOptions.useExtendedChunks) { + // Write the extra source block. This will be ignored by the Android runtime. + ResTable_entry_source* sourceBlock = buffer->nextBlock<ResTable_entry_source>(); + sourceBlock->pathIndex = util::hostToDevice32(entry->sourcePathKey); + sourceBlock->line = util::hostToDevice32(entry->sourceLine); + outEntry->size += sizeof(*sourceBlock); + } + + outEntry->flags = util::hostToDevice16(outEntry->flags); + outEntry->size = util::hostToDevice16(outEntry->size); + return result; + } + + bool flattenValue(FlatEntry* entry, BigBuffer* buffer) { + if (entry->value->isItem()) { + writeEntry<ResTable_entry>(entry, buffer); + if (Reference* ref = valueCast<Reference>(entry->value)) { + if (!ref->id) { + assert(ref->name && "reference must have at least a name"); + mSymbols.addSymbol(ref->name.value(), + buffer->size() + offsetof(Res_value, data)); + } + } + Res_value* outValue = buffer->nextBlock<Res_value>(); + bool result = static_cast<Item*>(entry->value)->flatten(outValue); + assert(result && "flatten failed"); + outValue->size = util::hostToDevice16(sizeof(*outValue)); + } else { + const size_t beforeEntry = buffer->size(); + ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext>(entry, buffer); + MapFlattenVisitor visitor(&mSymbols, entry, buffer); + entry->value->accept(&visitor); + outEntry->count = util::hostToDevice32(visitor.mEntryCount); + if (visitor.mParentName) { + mSymbols.addSymbol(visitor.mParentName.value(), + beforeEntry + offsetof(ResTable_entry_ext, parent)); + } else if (visitor.mParentIdent) { + outEntry->parent.ident = util::hostToDevice32(visitor.mParentIdent.value()); + } + } + return true; + } + + bool flattenConfig(const ResourceTableType* type, const ConfigDescription& config, + std::vector<FlatEntry>* entries, BigBuffer* buffer) { + ChunkWriter typeWriter(buffer); + ResTable_type* typeHeader = typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE); + typeHeader->id = type->id.value(); + typeHeader->config = config; + typeHeader->config.swapHtoD(); + + auto maxAccum = [](uint32_t max, const std::unique_ptr<ResourceEntry>& a) -> uint32_t { + return std::max(max, (uint32_t) a->id.value()); + }; + + // Find the largest entry ID. That is how many entries we will have. + const uint32_t entryCount = + std::accumulate(type->entries.begin(), type->entries.end(), 0, maxAccum) + 1; + + typeHeader->entryCount = util::hostToDevice32(entryCount); + uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount); + + assert((size_t) entryCount <= std::numeric_limits<uint16_t>::max() + 1); + memset(indices, 0xff, entryCount * sizeof(uint32_t)); + + typeHeader->entriesStart = util::hostToDevice32(typeWriter.size()); + + const size_t entryStart = typeWriter.getBuffer()->size(); + for (FlatEntry& flatEntry : *entries) { + assert(flatEntry.entry->id.value() < entryCount); + indices[flatEntry.entry->id.value()] = util::hostToDevice32( + typeWriter.getBuffer()->size() - entryStart); + if (!flattenValue(&flatEntry, typeWriter.getBuffer())) { + mDiag->error(DiagMessage() + << "failed to flatten resource '" + << ResourceNameRef(mPackage->name, type->type, flatEntry.entry->name) + << "' for configuration '" << config << "'"); + return false; + } + } + typeWriter.finish(); + return true; + } + + std::vector<ResourceTableType*> collectAndSortTypes() { + std::vector<ResourceTableType*> sortedTypes; + for (auto& type : mPackage->types) { + if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) { + // Styleables aren't real Resource Types, they are represented in the R.java + // file. + continue; + } + + assert(type->id && "type must have an ID set"); + + sortedTypes.push_back(type.get()); + } + std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds<ResourceTableType>); + return sortedTypes; + } + + std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) { + // Sort the entries by entry ID. + std::vector<ResourceEntry*> sortedEntries; + for (auto& entry : type->entries) { + assert(entry->id && "entry must have an ID set"); + sortedEntries.push_back(entry.get()); + } + std::sort(sortedEntries.begin(), sortedEntries.end(), cmpIds<ResourceEntry>); + return sortedEntries; + } + + bool flattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries, + BigBuffer* buffer) { + ChunkWriter typeSpecWriter(buffer); + ResTable_typeSpec* specHeader = typeSpecWriter.startChunk<ResTable_typeSpec>( + RES_TABLE_TYPE_SPEC_TYPE); + specHeader->id = type->id.value(); + + if (sortedEntries->empty()) { + typeSpecWriter.finish(); + return true; + } + + // We can't just take the size of the vector. There may be holes in the entry ID space. + // Since the entries are sorted by ID, the last one will be the biggest. + const size_t numEntries = sortedEntries->back()->id.value() + 1; + + specHeader->entryCount = util::hostToDevice32(numEntries); + + // Reserve space for the masks of each resource in this type. These + // show for which configuration axis the resource changes. + uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries); + + const size_t actualNumEntries = sortedEntries->size(); + for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) { + ResourceEntry* entry = sortedEntries->at(entryIndex); + + // Populate the config masks for this entry. + + if (entry->publicStatus.isPublic) { + configMasks[entry->id.value()] |= + util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); + } + + const size_t configCount = entry->values.size(); + for (size_t i = 0; i < configCount; i++) { + const ConfigDescription& config = entry->values[i].config; + for (size_t j = i + 1; j < configCount; j++) { + configMasks[entry->id.value()] |= util::hostToDevice32( + config.diff(entry->values[j].config)); + } + } + } + typeSpecWriter.finish(); + return true; + } + + bool flattenPublic(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries, + BigBuffer* buffer) { + ChunkWriter publicWriter(buffer); + Public_header* publicHeader = publicWriter.startChunk<Public_header>(RES_TABLE_PUBLIC_TYPE); + publicHeader->typeId = type->id.value(); + + for (ResourceEntry* entry : *sortedEntries) { + if (entry->publicStatus.isPublic) { + // Write the public status of this entry. + Public_entry* publicEntry = publicWriter.nextBlock<Public_entry>(); + publicEntry->entryId = util::hostToDevice32(entry->id.value()); + publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef( + entry->name).getIndex()); + publicEntry->source.index = util::hostToDevice32(mSourcePool.makeRef( + util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex()); + if (entry->publicStatus.source.line) { + publicEntry->sourceLine = util::hostToDevice32( + entry->publicStatus.source.line.value()); + } + + // Don't hostToDevice until the last step. + publicHeader->count += 1; + } + } + + publicHeader->count = util::hostToDevice32(publicHeader->count); + publicWriter.finish(); + return true; + } + + bool flattenTypes(BigBuffer* buffer) { + // Sort the types by their IDs. They will be inserted into the StringPool in this order. + std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes(); + + size_t expectedTypeId = 1; + for (ResourceTableType* type : sortedTypes) { + // If there is a gap in the type IDs, fill in the StringPool + // with empty values until we reach the ID we expect. + while (type->id.value() > expectedTypeId) { + std::u16string typeName(u"?"); + typeName += expectedTypeId; + mTypePool.makeRef(typeName); + expectedTypeId++; + } + expectedTypeId++; + mTypePool.makeRef(toString(type->type)); + + std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type); + + if (!flattenTypeSpec(type, &sortedEntries, buffer)) { + return false; + } + + if (mOptions.useExtendedChunks) { + if (!flattenPublic(type, &sortedEntries, buffer)) { + return false; + } + } + + // The binary resource table lists resource entries for each configuration. + // We store them inverted, where a resource entry lists the values for each + // configuration available. Here we reverse this to match the binary table. + std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap; + for (ResourceEntry* entry : sortedEntries) { + const size_t keyIndex = mKeyPool.makeRef(entry->name).getIndex(); + + // Group values by configuration. + for (auto& configValue : entry->values) { + configToEntryListMap[configValue.config].push_back(FlatEntry{ + entry, configValue.value.get(), (uint32_t) keyIndex, + (uint32_t)(mSourcePool.makeRef(util::utf8ToUtf16( + configValue.source.path)).getIndex()), + (uint32_t)(configValue.source.line + ? configValue.source.line.value() : 0) + }); + } + } + + // Flatten a configuration value. + for (auto& entry : configToEntryListMap) { + if (!flattenConfig(type, entry.first, &entry.second, buffer)) { + return false; + } + } + } + return true; + } + + bool flattenPackage(BigBuffer* buffer) { + // We must do this before writing the resources, since the string pool IDs may change. + mTable->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { + int diff = a.context.priority - b.context.priority; + if (diff < 0) return true; + if (diff > 0) return false; + diff = a.context.config.compare(b.context.config); + if (diff < 0) return true; + if (diff > 0) return false; + return a.value < b.value; + }); + mTable->stringPool.prune(); + + const size_t beginningIndex = buffer->size(); + + BigBuffer typeBuffer(1024); + if (!flattenTypes(&typeBuffer)) { + return false; + } + + ChunkWriter tableWriter(buffer); + ResTable_header* tableHeader = tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE); + tableHeader->packageCount = util::hostToDevice32(1); + + SymbolTable_entry* symbolEntryData = nullptr; + if (mOptions.useExtendedChunks && !mSymbols.symbols.empty()) { + // Sort the offsets so we can scan them linearly. + std::sort(mSymbols.symbols.begin(), mSymbols.symbols.end(), + [](const SymbolWriter::Entry& a, const SymbolWriter::Entry& b) -> bool { + return a.offset < b.offset; + }); + + ChunkWriter symbolWriter(tableWriter.getBuffer()); + SymbolTable_header* symbolHeader = symbolWriter.startChunk<SymbolTable_header>( + RES_TABLE_SYMBOL_TABLE_TYPE); + symbolHeader->count = util::hostToDevice32(mSymbols.symbols.size()); + + symbolEntryData = symbolWriter.nextBlock<SymbolTable_entry>(mSymbols.symbols.size()); + StringPool::flattenUtf8(symbolWriter.getBuffer(), mSymbols.pool); + symbolWriter.finish(); + } + + if (mOptions.useExtendedChunks && mSourcePool.size() > 0) { + // Write out source pool. + ChunkWriter srcWriter(tableWriter.getBuffer()); + srcWriter.startChunk<ResChunk_header>(RES_TABLE_SOURCE_POOL_TYPE); + StringPool::flattenUtf8(srcWriter.getBuffer(), mSourcePool); + srcWriter.finish(); + } + + StringPool::flattenUtf8(tableWriter.getBuffer(), mTable->stringPool); + + ChunkWriter pkgWriter(tableWriter.getBuffer()); + ResTable_package* pkgHeader = pkgWriter.startChunk<ResTable_package>( + RES_TABLE_PACKAGE_TYPE); + pkgHeader->id = util::hostToDevice32(mPackage->id.value()); + + if (mPackage->name.size() >= NELEM(pkgHeader->name)) { + mDiag->error(DiagMessage() << + "package name '" << mPackage->name << "' is too long"); + return false; + } + + strcpy16_htod(pkgHeader->name, NELEM(pkgHeader->name), mPackage->name); + + pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size()); + StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool); + + pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size()); + StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool); + + // Actually write out the symbol entries if we have symbols. + if (symbolEntryData) { + for (auto& entry : mSymbols.symbols) { + symbolEntryData->stringIndex = util::hostToDevice32(entry.name.getIndex()); + + // The symbols were all calculated with the typeBuffer offset. We need to + // add the beginning of the output buffer. + symbolEntryData->offset = util::hostToDevice32( + (pkgWriter.getBuffer()->size() - beginningIndex) + entry.offset); + + symbolEntryData++; + } + } + + // Write out the types and entries. + pkgWriter.getBuffer()->appendBuffer(std::move(typeBuffer)); + + pkgWriter.finish(); + tableWriter.finish(); + return true; + } +}; + +} // namespace + +bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) { + for (auto& package : table->packages) { + // Only support flattening one package. Since the StringPool is shared between packages + // in ResourceTable, we must fail if other packages are present, since their strings + // will be included in the final ResourceTable. + if (context->getCompilationPackage() != package->name) { + context->getDiagnostics()->error(DiagMessage() + << "resources for package '" << package->name + << "' can't be flattened when compiling package '" + << context->getCompilationPackage() << "'"); + return false; + } + + if (!package->id || package->id.value() != context->getPackageId()) { + context->getDiagnostics()->error(DiagMessage() + << "package '" << package->name << "' must have " + << "package id " + << std::hex << context->getPackageId() << std::dec); + return false; + } + + PackageFlattener flattener = { + context->getDiagnostics(), + mOptions, + table, + package.get() + }; + + if (!flattener.flattenPackage(mBuffer)) { + return false; + } + return true; + } + + context->getDiagnostics()->error(DiagMessage() + << "compilation package '" << context->getCompilationPackage() + << "' not found"); + return false; +} + +} // namespace aapt diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h new file mode 100644 index 000000000000..901b129725ea --- /dev/null +++ b/tools/aapt2/flatten/TableFlattener.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_FLATTEN_TABLEFLATTENER_H +#define AAPT_FLATTEN_TABLEFLATTENER_H + +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +class BigBuffer; +class ResourceTable; + +struct TableFlattenerOptions { + /** + * Specifies whether to output extended chunks, like + * source information and missing symbol entries. Default + * is false. + * + * Set this to true when emitting intermediate resource table. + */ + bool useExtendedChunks = false; +}; + +class TableFlattener : public IResourceTableConsumer { +public: + TableFlattener(BigBuffer* buffer, TableFlattenerOptions options) : + mBuffer(buffer), mOptions(options) { + } + + bool consume(IAaptContext* context, ResourceTable* table) override; + +private: + BigBuffer* mBuffer; + TableFlattenerOptions mOptions; +}; + +} // namespace aapt + +#endif /* AAPT_FLATTEN_TABLEFLATTENER_H */ diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp new file mode 100644 index 000000000000..68a1f478c34d --- /dev/null +++ b/tools/aapt2/flatten/TableFlattener_test.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "flatten/TableFlattener.h" +#include "unflatten/BinaryResourceParser.h" +#include "util/Util.h" + +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.h> + +using namespace android; + +namespace aapt { + +class TableFlattenerTest : public ::testing::Test { +public: + void SetUp() override { + mContext = test::ContextBuilder() + .setCompilationPackage(u"com.app.test") + .setPackageId(0x7f) + .build(); + } + + ::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) { + BigBuffer buffer(1024); + TableFlattenerOptions options = {}; + options.useExtendedChunks = true; + TableFlattener flattener(&buffer, options); + if (!flattener.consume(mContext.get(), table)) { + return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; + } + + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + if (outTable->add(data.get(), buffer.size(), -1, true) != NO_ERROR) { + return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; + } + return ::testing::AssertionSuccess(); + } + + ::testing::AssertionResult flatten(ResourceTable* table, ResourceTable* outTable) { + BigBuffer buffer(1024); + TableFlattenerOptions options = {}; + options.useExtendedChunks = true; + TableFlattener flattener(&buffer, options); + if (!flattener.consume(mContext.get(), table)) { + return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; + } + + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + BinaryResourceParser parser(mContext.get(), outTable, {}, data.get(), buffer.size()); + if (!parser.parse()) { + return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; + } + return ::testing::AssertionSuccess(); + } + + ::testing::AssertionResult exists(ResTable* table, + const StringPiece16& expectedName, + const ResourceId expectedId, + const ConfigDescription& expectedConfig, + const uint8_t expectedDataType, const uint32_t expectedData, + const uint32_t expectedSpecFlags) { + const ResourceName expectedResName = test::parseNameOrDie(expectedName); + + table->setParameters(&expectedConfig); + + ResTable_config config; + Res_value val; + uint32_t specFlags; + if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) < 0) { + return ::testing::AssertionFailure() << "could not find resource with"; + } + + if (expectedDataType != val.dataType) { + return ::testing::AssertionFailure() + << "expected data type " + << std::hex << (int) expectedDataType << " but got data type " + << (int) val.dataType << std::dec << " instead"; + } + + if (expectedData != val.data) { + return ::testing::AssertionFailure() + << "expected data " + << std::hex << expectedData << " but got data " + << val.data << std::dec << " instead"; + } + + if (expectedSpecFlags != specFlags) { + return ::testing::AssertionFailure() + << "expected specFlags " + << std::hex << expectedSpecFlags << " but got specFlags " + << specFlags << std::dec << " instead"; + } + + ResTable::resource_name actualName; + if (!table->getResourceName(expectedId.id, false, &actualName)) { + return ::testing::AssertionFailure() << "failed to find resource name"; + } + + StringPiece16 package16(actualName.package, actualName.packageLen); + if (package16 != expectedResName.package) { + return ::testing::AssertionFailure() + << "expected package '" << expectedResName.package << "' but got '" + << package16 << "'"; + } + + StringPiece16 type16(actualName.type, actualName.typeLen); + if (type16 != toString(expectedResName.type)) { + return ::testing::AssertionFailure() + << "expected type '" << expectedResName.type + << "' but got '" << type16 << "'"; + } + + StringPiece16 name16(actualName.name, actualName.nameLen); + if (name16 != expectedResName.entry) { + return ::testing::AssertionFailure() + << "expected name '" << expectedResName.entry + << "' but got '" << name16 << "'"; + } + + if (expectedConfig != config) { + return ::testing::AssertionFailure() + << "expected config '" << expectedConfig << "' but got '" + << ConfigDescription(config) << "'"; + } + return ::testing::AssertionSuccess(); + } + +private: + std::unique_ptr<IAaptContext> mContext; +}; + +TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"com.app.test", 0x7f) + .addSimple(u"@com.app.test:id/one", ResourceId(0x7f020000)) + .addSimple(u"@com.app.test:id/two", ResourceId(0x7f020001)) + .addValue(u"@com.app.test:id/three", ResourceId(0x7f020002), + test::buildReference(u"@com.app.test:id/one", ResourceId(0x7f020000))) + .addValue(u"@com.app.test:integer/one", ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) + .addValue(u"@com.app.test:integer/one", ResourceId(0x7f030000), + test::parseConfigOrDie("v1"), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) + .addString(u"@com.app.test:string/test", ResourceId(0x7f040000), u"foo") + .addString(u"@com.app.test:layout/bar", ResourceId(0x7f050000), u"res/layout/bar.xml") + .build(); + + ResTable resTable; + ASSERT_TRUE(flatten(table.get(), &resTable)); + + EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/one", ResourceId(0x7f020000), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/two", ResourceId(0x7f020001), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/three", ResourceId(0x7f020002), {}, + Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); + + EXPECT_TRUE(exists(&resTable, u"@com.app.test:integer/one", ResourceId(0x7f030000), + {}, Res_value::TYPE_INT_DEC, 1u, + ResTable_config::CONFIG_VERSION)); + + EXPECT_TRUE(exists(&resTable, u"@com.app.test:integer/one", ResourceId(0x7f030000), + test::parseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u, + ResTable_config::CONFIG_VERSION)); + + StringPiece16 fooStr = u"foo"; + ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(), fooStr.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(exists(&resTable, u"@com.app.test:string/test", ResourceId(0x7f040000), + {}, Res_value::TYPE_STRING, (uint32_t) idx, 0u)); + + StringPiece16 barPath = u"res/layout/bar.xml"; + idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(), barPath.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(exists(&resTable, u"@com.app.test:layout/bar", ResourceId(0x7f050000), {}, + Res_value::TYPE_STRING, (uint32_t) idx, 0u)); +} + +TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"com.app.test", 0x7f) + .addSimple(u"@com.app.test:id/one", ResourceId(0x7f020001)) + .addSimple(u"@com.app.test:id/three", ResourceId(0x7f020003)) + .build(); + + ResTable resTable; + ASSERT_TRUE(flatten(table.get(), &resTable)); + + EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/one", ResourceId(0x7f020001), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/three", ResourceId(0x7f020003), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); +} + +TEST_F(TableFlattenerTest, FlattenUnlinkedTable) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"com.app.test", 0x7f) + .addValue(u"@com.app.test:integer/one", ResourceId(0x7f020000), + test::buildReference(u"@android:integer/foo")) + .addValue(u"@com.app.test:style/Theme", ResourceId(0x7f030000), test::StyleBuilder() + .setParent(u"@android:style/Theme.Material") + .addItem(u"@android:attr/background", {}) + .addItem(u"@android:attr/colorAccent", + test::buildReference(u"@com.app.test:color/green")) + .build()) + .build(); + + { + // Need access to stringPool to make RawString. + Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme"); + style->entries[0].value = util::make_unique<RawString>(table->stringPool.makeRef(u"foo")); + } + + ResourceTable finalTable; + ASSERT_TRUE(flatten(table.get(), &finalTable)); + + Reference* ref = test::getValue<Reference>(&finalTable, u"@com.app.test:integer/one"); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->name); + EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@android:integer/foo")); + + Style* style = test::getValue<Style>(&finalTable, u"@com.app.test:style/Theme"); + ASSERT_NE(style, nullptr); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(style->parent.value().name.value(), + test::parseNameOrDie(u"@android:style/Theme.Material")); + + ASSERT_EQ(2u, style->entries.size()); + + AAPT_ASSERT_TRUE(style->entries[0].key.name); + EXPECT_EQ(style->entries[0].key.name.value(), + test::parseNameOrDie(u"@android:attr/background")); + RawString* raw = valueCast<RawString>(style->entries[0].value.get()); + ASSERT_NE(raw, nullptr); + EXPECT_EQ(*raw->value, u"foo"); + + AAPT_ASSERT_TRUE(style->entries[1].key.name); + EXPECT_EQ(style->entries[1].key.name.value(), + test::parseNameOrDie(u"@android:attr/colorAccent")); + ref = valueCast<Reference>(style->entries[1].value.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->name); + EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@com.app.test:color/green")); +} + +} // namespace aapt diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp new file mode 100644 index 000000000000..4efb08bfe58d --- /dev/null +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SdkConstants.h" +#include "XmlDom.h" + +#include "flatten/ChunkWriter.h" +#include "flatten/ResourceTypeExtensions.h" +#include "flatten/XmlFlattener.h" + +#include <androidfw/ResourceTypes.h> +#include <vector> +#include <utils/misc.h> + +using namespace android; + +namespace aapt { + +namespace { + +constexpr uint32_t kLowPriority = 0xffffffffu; + +struct XmlFlattenerVisitor : public xml::Visitor { + using xml::Visitor::visit; + + BigBuffer* mBuffer; + XmlFlattenerOptions mOptions; + StringPool mPool; + std::map<uint8_t, StringPool> mPackagePools; + + struct StringFlattenDest { + StringPool::Ref ref; + ResStringPool_ref* dest; + }; + std::vector<StringFlattenDest> mStringRefs; + + // Scratch vector to filter attributes. We avoid allocations + // making this a member. + std::vector<xml::Attribute*> mFilteredAttrs; + + + XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) : + mBuffer(buffer), mOptions(options) { + } + + void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) { + if (!str.empty()) { + mStringRefs.push_back(StringFlattenDest{ + mPool.makeRef(str, StringPool::Context{ priority }), + dest }); + } else { + // The device doesn't think a string of size 0 is the same as null. + dest->index = util::deviceToHost32(-1); + } + } + + void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) { + mStringRefs.push_back(StringFlattenDest{ ref, dest }); + } + + void writeNamespace(xml::Namespace* node, uint16_t type) { + ChunkWriter writer(mBuffer); + + ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type); + flatNode->lineNumber = util::hostToDevice32(node->lineNumber); + flatNode->comment.index = util::hostToDevice32(-1); + + ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>(); + addString(node->namespacePrefix, kLowPriority, &flatNs->prefix); + addString(node->namespaceUri, kLowPriority, &flatNs->uri); + + writer.finish(); + } + + void visit(xml::Namespace* node) override { + writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE); + xml::Visitor::visit(node); + writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE); + } + + void visit(xml::Text* node) override { + if (util::trimWhitespace(node->text).empty()) { + // Skip whitespace only text nodes. + return; + } + + ChunkWriter writer(mBuffer); + ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE); + flatNode->lineNumber = util::hostToDevice32(node->lineNumber); + flatNode->comment.index = util::hostToDevice32(-1); + + ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>(); + addString(node->text, kLowPriority, &flatText->data); + + writer.finish(); + } + + void visit(xml::Element* node) override { + { + ChunkWriter startWriter(mBuffer); + ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>( + RES_XML_START_ELEMENT_TYPE); + flatNode->lineNumber = util::hostToDevice32(node->lineNumber); + flatNode->comment.index = util::hostToDevice32(-1); + + ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>(); + addString(node->namespaceUri, kLowPriority, &flatElem->ns); + addString(node->name, kLowPriority, &flatElem->name); + flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem)); + flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute)); + + writeAttributes(node, flatElem, &startWriter); + + startWriter.finish(); + } + + xml::Visitor::visit(node); + + { + ChunkWriter endWriter(mBuffer); + ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>( + RES_XML_END_ELEMENT_TYPE); + flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber); + flatEndNode->comment.index = util::hostToDevice32(-1); + + ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>(); + addString(node->namespaceUri, kLowPriority, &flatEndElem->ns); + addString(node->name, kLowPriority, &flatEndElem->name); + + endWriter.finish(); + } + } + + static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) { + if (a->compiledAttribute) { + if (b->compiledAttribute) { + return a->compiledAttribute.value().id < b->compiledAttribute.value().id; + } + return true; + } else if (!b->compiledAttribute) { + int diff = a->namespaceUri.compare(b->namespaceUri); + if (diff < 0) { + return true; + } else if (diff > 0) { + return false; + } + return a->name < b->name; + } + return false; + } + + void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) { + mFilteredAttrs.clear(); + mFilteredAttrs.reserve(node->attributes.size()); + + // Filter the attributes. + for (xml::Attribute& attr : node->attributes) { + if (mOptions.maxSdkLevel && attr.compiledAttribute) { + size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id); + if (sdkLevel > mOptions.maxSdkLevel.value()) { + continue; + } + } + mFilteredAttrs.push_back(&attr); + } + + if (mFilteredAttrs.empty()) { + return; + } + + const ResourceId kIdAttr(0x010100d0); + + std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById); + + flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size()); + + ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>( + mFilteredAttrs.size()); + uint16_t attributeIndex = 1; + for (const xml::Attribute* xmlAttr : mFilteredAttrs) { + // Assign the indices for specific attributes. + if (xmlAttr->compiledAttribute && + xmlAttr->compiledAttribute.value().id == kIdAttr) { + flatElem->idIndex = util::hostToDevice16(attributeIndex); + } else if (xmlAttr->namespaceUri.empty()) { + if (xmlAttr->name == u"class") { + flatElem->classIndex = util::hostToDevice16(attributeIndex); + } else if (xmlAttr->name == u"style") { + flatElem->styleIndex = util::hostToDevice16(attributeIndex); + } + } + attributeIndex++; + + // Add the namespaceUri to the list of StringRefs to encode. + addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns); + + flatAttr->rawValue.index = util::hostToDevice32(-1); + + if (!xmlAttr->compiledAttribute) { + // The attribute has no associated ResourceID, so the string order doesn't matter. + addString(xmlAttr->name, kLowPriority, &flatAttr->name); + } else { + // Attribute names are stored without packages, but we use + // their StringPool index to lookup their resource IDs. + // This will cause collisions, so we can't dedupe + // attribute names from different packages. We use separate + // pools that we later combine. + // + // Lookup the StringPool for this package and make the reference there. + const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value(); + + StringPool::Ref nameRef = mPackagePools[aaptAttr.id.packageId()].makeRef( + xmlAttr->name, StringPool::Context{ aaptAttr.id.id }); + + // Add it to the list of strings to flatten. + addString(nameRef, &flatAttr->name); + + if (mOptions.keepRawValues) { + // Keep raw values (this is for static libraries). + // TODO(with a smarter inflater for binary XML, we can do without this). + addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue); + } + } + + if (xmlAttr->compiledValue) { + bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue); + assert(result); + } else { + // Flatten as a regular string type. + flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING; + addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue); + addString(xmlAttr->value, kLowPriority, + (ResStringPool_ref*) &flatAttr->typedValue.data); + } + + flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue)); + flatAttr++; + } + } +}; + +} // namespace + +bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) { + BigBuffer nodeBuffer(1024); + XmlFlattenerVisitor visitor(&nodeBuffer, mOptions); + node->accept(&visitor); + + // Merge the package pools into the main pool. + for (auto& packagePoolEntry : visitor.mPackagePools) { + visitor.mPool.merge(std::move(packagePoolEntry.second)); + } + + // Sort the string pool so that attribute resource IDs show up first. + visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { + return a.context.priority < b.context.priority; + }); + + // Now we flatten the string pool references into the correct places. + for (const auto& refEntry : visitor.mStringRefs) { + refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex()); + } + + // Write the XML header. + ChunkWriter xmlHeaderWriter(mBuffer); + xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE); + + // Flatten the StringPool. + StringPool::flattenUtf16(mBuffer, visitor.mPool); + + { + // Write the array of resource IDs, indexed by StringPool order. + ChunkWriter resIdMapWriter(mBuffer); + resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE); + for (const auto& str : visitor.mPool) { + ResourceId id = { str->context.priority }; + if (id.id == kLowPriority || !id.isValid()) { + // When we see the first non-resource ID, + // we're done. + break; + } + + *resIdMapWriter.nextBlock<uint32_t>() = id.id; + } + resIdMapWriter.finish(); + } + + // Move the nodeBuffer and append it to the out buffer. + mBuffer->appendBuffer(std::move(nodeBuffer)); + + // Finish the xml header. + xmlHeaderWriter.finish(); + return true; +} + +bool XmlFlattener::consume(IAaptContext* context, XmlResource* resource) { + if (!resource->root) { + return false; + } + return flatten(context, resource->root.get()); +} + +} // namespace aapt diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h new file mode 100644 index 000000000000..b1fb3a7cef27 --- /dev/null +++ b/tools/aapt2/flatten/XmlFlattener.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_FLATTEN_XMLFLATTENER_H +#define AAPT_FLATTEN_XMLFLATTENER_H + +#include "util/BigBuffer.h" + +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +namespace xml { +struct Node; +} + +struct XmlFlattenerOptions { + /** + * Keep attribute raw string values along with typed values. + */ + bool keepRawValues = false; + + /** + * If set, the max SDK level of attribute to flatten. All others are ignored. + */ + Maybe<size_t> maxSdkLevel; +}; + +class XmlFlattener : public IXmlResourceConsumer { +public: + XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) : + mBuffer(buffer), mOptions(options) { + } + + bool consume(IAaptContext* context, XmlResource* resource) override; + +private: + BigBuffer* mBuffer; + XmlFlattenerOptions mOptions; + + bool flatten(IAaptContext* context, xml::Node* node); +}; + +} // namespace aapt + +#endif /* AAPT_FLATTEN_XMLFLATTENER_H */ diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp new file mode 100644 index 000000000000..318bcddf44ec --- /dev/null +++ b/tools/aapt2/flatten/XmlFlattener_test.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "flatten/XmlFlattener.h" +#include "link/Linkers.h" +#include "util/BigBuffer.h" +#include "util/Util.h" + +#include "test/Builders.h" +#include "test/Context.h" + +#include <androidfw/ResourceTypes.h> +#include <gtest/gtest.h> + +namespace aapt { + +class XmlFlattenerTest : public ::testing::Test { +public: + void SetUp() override { + mContext = test::ContextBuilder() + .setCompilationPackage(u"com.app.test") + .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" }) + .setSymbolTable(test::StaticSymbolTableBuilder() + .addSymbol(u"@android:attr/id", ResourceId(0x010100d0), + test::AttributeBuilder().build()) + .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f020000)) + .addSymbol(u"@android:attr/paddingStart", ResourceId(0x010103b3), + test::AttributeBuilder().build()) + .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435), + test::AttributeBuilder().build()) + .build()) + .build(); + } + + ::testing::AssertionResult flatten(XmlResource* doc, android::ResXMLTree* outTree, + XmlFlattenerOptions options = {}) { + BigBuffer buffer(1024); + XmlFlattener flattener(&buffer, options); + if (!flattener.consume(mContext.get(), doc)) { + return ::testing::AssertionFailure() << "failed to flatten XML Tree"; + } + + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + if (outTree->setTo(data.get(), buffer.size(), true) != android::NO_ERROR) { + return ::testing::AssertionFailure() << "flattened XML is corrupt"; + } + return ::testing::AssertionSuccess(); + } + +protected: + std::unique_ptr<IAaptContext> mContext; +}; + +TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) { + std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + <View xmlns:test="http://com.test" + attr="hey"> + <Layout test:hello="hi" /> + <Layout>Some text</Layout> + </View>)EOF"); + + + android::ResXMLTree tree; + ASSERT_TRUE(flatten(doc.get(), &tree)); + + ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); + + size_t len; + const char16_t* namespacePrefix = tree.getNamespacePrefix(&len); + EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test"); + + const char16_t* namespaceUri = tree.getNamespaceUri(&len); + ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test"); + + ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + const char16_t* tagName = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tagName, len), u"View"); + + ASSERT_EQ(1u, tree.getAttributeCount()); + ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr); + const char16_t* attrName = tree.getAttributeName(0, &len); + EXPECT_EQ(StringPiece16(attrName, len), u"attr"); + + EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size())); + + ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + tagName = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tagName, len), u"Layout"); + + ASSERT_EQ(1u, tree.getAttributeCount()); + const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len); + EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test"); + + attrName = tree.getAttributeName(0, &len); + EXPECT_EQ(StringPiece16(attrName, len), u"hello"); + + ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); + ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + tagName = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tagName, len), u"Layout"); + ASSERT_EQ(0u, tree.getAttributeCount()); + + ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT); + const char16_t* text = tree.getText(&len); + EXPECT_EQ(StringPiece16(text, len), u"Some text"); + + ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + tagName = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tagName, len), u"Layout"); + + ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + tagName = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tagName, len), u"View"); + + ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE); + namespacePrefix = tree.getNamespacePrefix(&len); + EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test"); + + namespaceUri = tree.getNamespaceUri(&len); + ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test"); + + ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT); +} + +TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) { + std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingStart="1dp" + android:colorAccent="#ffffff"/>)EOF"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + ASSERT_TRUE(linker.getSdkLevels().count(17) == 1); + ASSERT_TRUE(linker.getSdkLevels().count(21) == 1); + + android::ResXMLTree tree; + XmlFlattenerOptions options; + options.maxSdkLevel = 17; + ASSERT_TRUE(flatten(doc.get(), &tree, options)); + + while (tree.next() != android::ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + } + + ASSERT_EQ(1u, tree.getAttributeCount()); + EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0)); +} + +TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) { + std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/id" + class="str" + style="@id/id"/>)EOF"); + + android::ResXMLTree tree; + ASSERT_TRUE(flatten(doc.get(), &tree)); + + while (tree.next() != android::ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + } + + EXPECT_EQ(tree.indexOfClass(), 0); + EXPECT_EQ(tree.indexOfStyle(), 1); +} + +/* + * The device ResXMLParser in libandroidfw differentiates between empty namespace and null + * namespace. + */ +TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { + std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>"); + + android::ResXMLTree tree; + ASSERT_TRUE(flatten(doc.get(), &tree)); + + while (tree.next() != android::ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + } + + const StringPiece16 kPackage = u"package"; + EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); +} + +} // namespace aapt diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp new file mode 100644 index 000000000000..0ccafc2107d0 --- /dev/null +++ b/tools/aapt2/link/AutoVersioner.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ConfigDescription.h" +#include "ResourceTable.h" +#include "SdkConstants.h" +#include "ValueVisitor.h" + +#include "link/Linkers.h" + +#include <algorithm> +#include <cassert> + +namespace aapt { + +static bool cmpConfigValue(const ResourceConfigValue& lhs, const ConfigDescription& config) { + return lhs.config < config; +} + +bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config, + const int sdkVersionToGenerate) { + assert(sdkVersionToGenerate > config.sdkVersion); + const auto endIter = entry->values.end(); + auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmpConfigValue); + + // The source config came from this list, so it should be here. + assert(iter != entry->values.end()); + ++iter; + + // The next configuration either only varies in sdkVersion, or it is completely different + // and therefore incompatible. If it is incompatible, we must generate the versioned resource. + + // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other + // qualifiers, so we need to iterate through the entire list to be sure there + // are no higher sdk level versions of this resource. + ConfigDescription tempConfig(config); + for (; iter != endIter; ++iter) { + tempConfig.sdkVersion = iter->config.sdkVersion; + if (tempConfig == iter->config) { + // The two configs are the same, check the sdk version. + return sdkVersionToGenerate < iter->config.sdkVersion; + } + } + + // No match was found, so we should generate the versioned resource. + return true; +} + +bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) { + for (auto& package : table->packages) { + for (auto& type : package->types) { + if (type->type != ResourceType::kStyle) { + continue; + } + + for (auto& entry : type->entries) { + for (size_t i = 0; i < entry->values.size(); i++) { + ResourceConfigValue& configValue = entry->values[i]; + if (configValue.config.sdkVersion >= SDK_LOLLIPOP_MR1) { + // If this configuration is only used on L-MR1 then we don't need + // to do anything since we use private attributes since that version. + continue; + } + + if (Style* style = valueCast<Style>(configValue.value.get())) { + Maybe<size_t> minSdkStripped; + std::vector<Style::Entry> stripped; + + auto iter = style->entries.begin(); + while (iter != style->entries.end()) { + assert(iter->key.id && "IDs must be assigned and linked"); + + // Find the SDK level that is higher than the configuration allows. + const size_t sdkLevel = findAttributeSdkLevel(iter->key.id.value()); + if (sdkLevel > std::max<size_t>(configValue.config.sdkVersion, 1)) { + // Record that we are about to strip this. + stripped.emplace_back(std::move(*iter)); + + // We use the smallest SDK level to generate the new style. + if (minSdkStripped) { + minSdkStripped = std::min(minSdkStripped.value(), sdkLevel); + } else { + minSdkStripped = sdkLevel; + } + + // Erase this from this style. + iter = style->entries.erase(iter); + continue; + } + ++iter; + } + + if (minSdkStripped && !stripped.empty()) { + // We found attributes from a higher SDK level. Check that + // there is no other defined resource for the version we want to + // generate. + if (shouldGenerateVersionedResource(entry.get(), configValue.config, + minSdkStripped.value())) { + // Let's create a new Style for this versioned resource. + ConfigDescription newConfig(configValue.config); + newConfig.sdkVersion = minSdkStripped.value(); + + ResourceConfigValue newValue = { + newConfig, + configValue.source, + configValue.comment, + std::unique_ptr<Value>(configValue.value->clone( + &table->stringPool)) + }; + + Style* newStyle = static_cast<Style*>(newValue.value.get()); + + // Move the previously stripped attributes into this style. + newStyle->entries.insert(newStyle->entries.end(), + std::make_move_iterator(stripped.begin()), + std::make_move_iterator(stripped.end())); + + // Insert the new Resource into the correct place. + auto iter = std::lower_bound(entry->values.begin(), + entry->values.end(), newConfig, + cmpConfigValue); + entry->values.insert(iter, std::move(newValue)); + } + } + } + } + } + } + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp new file mode 100644 index 000000000000..29bcc93518cf --- /dev/null +++ b/tools/aapt2/link/AutoVersioner_test.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ConfigDescription.h" + +#include "link/Linkers.h" + +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(AutoVersionerTest, GenerateVersionedResources) { + const ConfigDescription defaultConfig = {}; + const ConfigDescription landConfig = test::parseConfigOrDie("land"); + const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land"); + + ResourceEntry entry(u"foo"); + entry.values.push_back(ResourceConfigValue{ defaultConfig }); + entry.values.push_back(ResourceConfigValue{ landConfig }); + entry.values.push_back(ResourceConfigValue{ sw600dpLandConfig }); + + EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17)); + EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17)); +} + +TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) { + const ConfigDescription defaultConfig = {}; + const ConfigDescription sw600dpV13Config = test::parseConfigOrDie("sw600dp-v13"); + const ConfigDescription v21Config = test::parseConfigOrDie("v21"); + + ResourceEntry entry(u"foo"); + entry.values.push_back(ResourceConfigValue{ defaultConfig }); + entry.values.push_back(ResourceConfigValue{ sw600dpV13Config }); + entry.values.push_back(ResourceConfigValue{ v21Config }); + + EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17)); + EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22)); +} + +TEST(AutoVersionerTest, VersionStylesForTable) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"app", 0x7f) + .addValue(u"@app:style/Foo", ResourceId(0x7f020000), test::parseConfigOrDie("v4"), + test::StyleBuilder() + .addItem(u"@android:attr/onClick", ResourceId(0x0101026f), + util::make_unique<Id>()) + .addItem(u"@android:attr/paddingStart", ResourceId(0x010103b3), + util::make_unique<Id>()) + .addItem(u"@android:attr/requiresSmallestWidthDp", + ResourceId(0x01010364), util::make_unique<Id>()) + .addItem(u"@android:attr/colorAccent", ResourceId(0x01010435), + util::make_unique<Id>()) + .build()) + .addValue(u"@app:style/Foo", ResourceId(0x7f020000), test::parseConfigOrDie("v21"), + test::StyleBuilder() + .addItem(u"@android:attr/paddingEnd", ResourceId(0x010103b4), + util::make_unique<Id>()) + .build()) + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder() + .setCompilationPackage(u"app") + .setPackageId(0x7f) + .build(); + + AutoVersioner versioner; + ASSERT_TRUE(versioner.consume(context.get(), table.get())); + + Style* style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo", + test::parseConfigOrDie("v4")); + ASSERT_NE(style, nullptr); + ASSERT_EQ(style->entries.size(), 1u); + AAPT_ASSERT_TRUE(style->entries.front().key.name); + EXPECT_EQ(style->entries.front().key.name.value(), + test::parseNameOrDie(u"@android:attr/onClick")); + + style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo", + test::parseConfigOrDie("v13")); + ASSERT_NE(style, nullptr); + ASSERT_EQ(style->entries.size(), 2u); + AAPT_ASSERT_TRUE(style->entries[0].key.name); + EXPECT_EQ(style->entries[0].key.name.value(), + test::parseNameOrDie(u"@android:attr/onClick")); + AAPT_ASSERT_TRUE(style->entries[1].key.name); + EXPECT_EQ(style->entries[1].key.name.value(), + test::parseNameOrDie(u"@android:attr/requiresSmallestWidthDp")); + + style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo", + test::parseConfigOrDie("v17")); + ASSERT_NE(style, nullptr); + ASSERT_EQ(style->entries.size(), 3u); + AAPT_ASSERT_TRUE(style->entries[0].key.name); + EXPECT_EQ(style->entries[0].key.name.value(), + test::parseNameOrDie(u"@android:attr/onClick")); + AAPT_ASSERT_TRUE(style->entries[1].key.name); + EXPECT_EQ(style->entries[1].key.name.value(), + test::parseNameOrDie(u"@android:attr/requiresSmallestWidthDp")); + AAPT_ASSERT_TRUE(style->entries[2].key.name); + EXPECT_EQ(style->entries[2].key.name.value(), + test::parseNameOrDie(u"@android:attr/paddingStart")); + + style = test::getValueForConfig<Style>(table.get(), u"@app:style/Foo", + test::parseConfigOrDie("v21")); + ASSERT_NE(style, nullptr); + ASSERT_EQ(style->entries.size(), 1u); + AAPT_ASSERT_TRUE(style->entries.front().key.name); + EXPECT_EQ(style->entries.front().key.name.value(), + test::parseNameOrDie(u"@android:attr/paddingEnd")); +} + +} // namespace aapt diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp new file mode 100644 index 000000000000..2b4c4d20b791 --- /dev/null +++ b/tools/aapt2/link/Link.cpp @@ -0,0 +1,702 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AppInfo.h" +#include "Debug.h" +#include "Flags.h" +#include "JavaClassGenerator.h" +#include "NameMangler.h" +#include "ProguardRules.h" +#include "XmlDom.h" + +#include "compile/IdAssigner.h" +#include "flatten/Archive.h" +#include "flatten/TableFlattener.h" +#include "flatten/XmlFlattener.h" +#include "link/Linkers.h" +#include "link/TableMerger.h" +#include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" +#include "unflatten/BinaryResourceParser.h" +#include "unflatten/FileExportHeaderReader.h" +#include "util/Files.h" +#include "util/StringPiece.h" + +#include <fstream> +#include <sys/stat.h> +#include <utils/FileMap.h> +#include <vector> + +namespace aapt { + +struct LinkOptions { + std::string outputPath; + std::string manifestPath; + std::vector<std::string> includePaths; + Maybe<std::string> generateJavaClassPath; + Maybe<std::string> generateProguardRulesPath; + bool noAutoVersion = false; + bool staticLib = false; + bool verbose = false; + bool outputToDirectory = false; +}; + +struct LinkContext : public IAaptContext { + StdErrDiagnostics mDiagnostics; + std::unique_ptr<NameMangler> mNameMangler; + std::u16string mCompilationPackage; + uint8_t mPackageId; + std::unique_ptr<ISymbolTable> mSymbols; + + IDiagnostics* getDiagnostics() override { + return &mDiagnostics; + } + + NameMangler* getNameMangler() override { + return mNameMangler.get(); + } + + StringPiece16 getCompilationPackage() override { + return mCompilationPackage; + } + + uint8_t getPackageId() override { + return mPackageId; + } + + ISymbolTable* getExternalSymbols() override { + return mSymbols.get(); + } +}; + +struct LinkCommand { + LinkOptions mOptions; + LinkContext mContext; + + std::string buildResourceFileName(const ResourceFile& resFile) { + std::stringstream out; + out << "res/" << resFile.name.type; + if (resFile.config != ConfigDescription{}) { + out << "-" << resFile.config; + } + out << "/"; + + if (mContext.getNameMangler()->shouldMangle(resFile.name.package)) { + out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry); + } else { + out << resFile.name.entry; + } + out << file::getExtension(resFile.source.path); + return out.str(); + } + + /** + * Creates a SymbolTable that loads symbols from the various APKs and caches the + * results for faster lookup. + */ + std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() { + AssetManagerSymbolTableBuilder builder; + for (const std::string& path : mOptions.includePaths) { + if (mOptions.verbose) { + mContext.getDiagnostics()->note( + DiagMessage(Source{ path }) << "loading include path"); + } + + std::unique_ptr<android::AssetManager> assetManager = + util::make_unique<android::AssetManager>(); + int32_t cookie = 0; + if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) { + mContext.getDiagnostics()->error( + DiagMessage(Source{ path }) << "failed to load include path"); + return {}; + } + builder.add(std::move(assetManager)); + } + return builder.build(); + } + + /** + * Loads the resource table (not inside an apk) at the given path. + */ + std::unique_ptr<ResourceTable> loadTable(const std::string& input) { + std::string errorStr; + Maybe<android::FileMap> map = file::mmapPath(input, &errorStr); + if (!map) { + mContext.getDiagnostics()->error(DiagMessage(Source{ input }) << errorStr); + return {}; + } + + std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); + BinaryResourceParser parser(&mContext, table.get(), Source{ input }, + map.value().getDataPtr(), map.value().getDataLength()); + if (!parser.parse()) { + return {}; + } + return table; + } + + /** + * Inflates an XML file from the source path. + */ + std::unique_ptr<XmlResource> loadXml(const std::string& path) { + std::ifstream fin(path, std::ifstream::binary); + if (!fin) { + mContext.getDiagnostics()->error(DiagMessage(Source{ path }) << strerror(errno)); + return {}; + } + + return xml::inflate(&fin, mContext.getDiagnostics(), Source{ path }); + } + + /** + * Inflates a binary XML file from the source path. + */ + std::unique_ptr<XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) { + // Read header for symbol info and export info. + std::string errorStr; + Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr); + if (!maybeF) { + mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); + return {}; + } + + ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(), + maybeF.value().getDataLength(), &errorStr); + if (offset < 0) { + mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); + return {}; + } + + std::unique_ptr<XmlResource> xmlRes = xml::inflate( + (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset, + maybeF.value().getDataLength() - offset, + mContext.getDiagnostics(), Source(path)); + if (!xmlRes) { + return {}; + } + return xmlRes; + } + + Maybe<ResourceFile> loadFileExportHeader(const std::string& path) { + // Read header for symbol info and export info. + std::string errorStr; + Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr); + if (!maybeF) { + mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); + return {}; + } + + ResourceFile resFile; + ssize_t offset = unwrapFileExportHeader(maybeF.value().getDataPtr(), + maybeF.value().getDataLength(), + &resFile, &errorStr); + if (offset < 0) { + mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); + return {}; + } + return std::move(resFile); + } + + bool copyFileToArchive(const std::string& path, const std::string& outPath, uint32_t flags, + IArchiveWriter* writer) { + std::string errorStr; + Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr); + if (!maybeF) { + mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); + return false; + } + + ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(), + maybeF.value().getDataLength(), + &errorStr); + if (offset < 0) { + mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); + return false; + } + + ArchiveEntry* entry = writer->writeEntry(outPath, flags, &maybeF.value(), + offset, maybeF.value().getDataLength() - offset); + if (!entry) { + mContext.getDiagnostics()->error( + DiagMessage(mOptions.outputPath) << "failed to write file " << outPath); + return false; + } + return true; + } + + Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) { + xml::Node* node = xmlRes->root.get(); + + // Find the first xml::Element. + while (node && !xml::nodeCast<xml::Element>(node)) { + node = !node->children.empty() ? node->children.front().get() : nullptr; + } + + // Make sure the first element is <manifest> with package attribute. + if (xml::Element* manifestEl = xml::nodeCast<xml::Element>(node)) { + if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") { + if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) { + return AppInfo{ packageAttr->value }; + } + } + } + return {}; + } + + bool verifyNoExternalPackages(ResourceTable* table) { + bool error = false; + for (const auto& package : table->packages) { + if (mContext.getCompilationPackage() != package->name || + !package->id || package->id.value() != mContext.getPackageId()) { + // We have a package that is not related to the one we're building! + for (const auto& type : package->types) { + for (const auto& entry : type->entries) { + for (const auto& configValue : entry->values) { + mContext.getDiagnostics()->error(DiagMessage(configValue.source) + << "defined resource '" + << ResourceNameRef(package->name, + type->type, + entry->name) + << "' for external package '" + << package->name << "'"); + error = true; + } + } + } + } + } + return !error; + } + + std::unique_ptr<IArchiveWriter> makeArchiveWriter() { + if (mOptions.outputToDirectory) { + return createDirectoryArchiveWriter(mOptions.outputPath); + } else { + return createZipFileArchiveWriter(mOptions.outputPath); + } + } + + bool flattenTable(ResourceTable* table, IArchiveWriter* writer) { + BigBuffer buffer(1024); + TableFlattenerOptions options = {}; + options.useExtendedChunks = mOptions.staticLib; + TableFlattener flattener(&buffer, options); + if (!flattener.consume(&mContext, table)) { + return false; + } + + ArchiveEntry* entry = writer->writeEntry("resources.arsc", ArchiveEntry::kAlign, buffer); + if (!entry) { + mContext.getDiagnostics()->error( + DiagMessage() << "failed to write resources.arsc to archive"); + return false; + } + return true; + } + + bool flattenXml(XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel, + IArchiveWriter* writer) { + BigBuffer buffer(1024); + XmlFlattenerOptions options = {}; + options.keepRawValues = mOptions.staticLib; + options.maxSdkLevel = maxSdkLevel; + XmlFlattener flattener(&buffer, options); + if (!flattener.consume(&mContext, xmlRes)) { + return false; + } + + ArchiveEntry* entry = writer->writeEntry(path, ArchiveEntry::kCompress, buffer); + if (!entry) { + mContext.getDiagnostics()->error( + DiagMessage() << "failed to write " << path << " to archive"); + return false; + } + return true; + } + + bool writeJavaFile(ResourceTable* table, const StringPiece16& package) { + if (!mOptions.generateJavaClassPath) { + return true; + } + + std::string outPath = mOptions.generateJavaClassPath.value(); + file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(package))); + file::mkdirs(outPath); + file::appendPath(&outPath, "R.java"); + + std::ofstream fout(outPath, std::ofstream::binary); + if (!fout) { + mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + return false; + } + + JavaClassGeneratorOptions javaOptions; + if (mOptions.staticLib) { + javaOptions.useFinal = false; + } + + JavaClassGenerator generator(table, javaOptions); + if (!generator.generate(mContext.getCompilationPackage(), &fout)) { + mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); + return false; + } + return true; + } + + bool writeProguardFile(const proguard::KeepSet& keepSet) { + if (!mOptions.generateProguardRulesPath) { + return true; + } + + std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary); + if (!fout) { + mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + return false; + } + + proguard::writeKeepSet(&fout, keepSet); + if (!fout) { + mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + return false; + } + return true; + } + + int run(const std::vector<std::string>& inputFiles) { + // Load the AndroidManifest.xml + std::unique_ptr<XmlResource> manifestXml = loadXml(mOptions.manifestPath); + if (!manifestXml) { + return 1; + } + + if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) { + mContext.mCompilationPackage = maybeAppInfo.value().package; + } else { + mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath) + << "no package specified in <manifest> tag"); + return 1; + } + + if (!util::isJavaPackageName(mContext.mCompilationPackage)) { + mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath) + << "invalid package name '" + << mContext.mCompilationPackage + << "'"); + return 1; + } + + mContext.mNameMangler = util::make_unique<NameMangler>( + NameManglerPolicy{ mContext.mCompilationPackage }); + mContext.mPackageId = 0x7f; + mContext.mSymbols = createSymbolTableFromIncludePaths(); + if (!mContext.mSymbols) { + return 1; + } + + if (mOptions.verbose) { + mContext.getDiagnostics()->note( + DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' " + << "with package ID " << std::hex << (int) mContext.mPackageId); + } + + ResourceTable mergedTable; + TableMerger tableMerger(&mContext, &mergedTable); + + struct FilesToProcess { + Source source; + ResourceFile file; + }; + + bool error = false; + std::queue<FilesToProcess> filesToProcess; + for (const std::string& input : inputFiles) { + if (util::stringEndsWith<char>(input, ".apk")) { + // TODO(adamlesinski): Load resources from a static library APK + // Merge the table into TableMerger. + + } else if (util::stringEndsWith<char>(input, ".arsc.flat")) { + if (mOptions.verbose) { + mContext.getDiagnostics()->note(DiagMessage() << "linking " << input); + } + + std::unique_ptr<ResourceTable> table = loadTable(input); + if (!table) { + return 1; + } + + if (!tableMerger.merge(Source(input), table.get())) { + return 1; + } + + } else { + // Extract the exported IDs here so we can build the resource table. + if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) { + ResourceFile& f = maybeF.value(); + + if (f.name.package.empty()) { + f.name.package = mContext.getCompilationPackage().toString(); + } + + Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(f.name); + + // Add this file to the table. + if (!mergedTable.addFileReference(mangledName ? mangledName.value() : f.name, + f.config, f.source, + util::utf8ToUtf16(buildResourceFileName(f)), + mContext.getDiagnostics())) { + error = true; + } + + // Add the exports of this file to the table. + for (SourcedResourceName& exportedSymbol : f.exportedSymbols) { + if (exportedSymbol.name.package.empty()) { + exportedSymbol.name.package = mContext.getCompilationPackage() + .toString(); + } + + Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName( + exportedSymbol.name); + if (!mergedTable.addResource( + mangledName ? mangledName.value() : exportedSymbol.name, + {}, {}, f.source.withLine(exportedSymbol.line), + util::make_unique<Id>(), mContext.getDiagnostics())) { + error = true; + } + } + + filesToProcess.push(FilesToProcess{ Source(input), std::move(f) }); + } else { + return 1; + } + } + } + + if (error) { + mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input"); + return 1; + } + + if (!verifyNoExternalPackages(&mergedTable)) { + return 1; + } + + if (!mOptions.staticLib) { + PrivateAttributeMover mover; + if (!mover.consume(&mContext, &mergedTable)) { + mContext.getDiagnostics()->error( + DiagMessage() << "failed moving private attributes"); + return 1; + } + } + + { + IdAssigner idAssigner; + if (!idAssigner.consume(&mContext, &mergedTable)) { + mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); + return 1; + } + } + + mContext.mNameMangler = util::make_unique<NameMangler>( + NameManglerPolicy{ mContext.mCompilationPackage, tableMerger.getMergedPackages() }); + mContext.mSymbols = JoinedSymbolTableBuilder() + .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mergedTable)) + .addSymbolTable(std::move(mContext.mSymbols)) + .build(); + + { + ReferenceLinker linker; + if (!linker.consume(&mContext, &mergedTable)) { + mContext.getDiagnostics()->error(DiagMessage() << "failed linking references"); + return 1; + } + } + + proguard::KeepSet proguardKeepSet; + + std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(); + if (!archiveWriter) { + mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive"); + return 1; + } + + { + XmlReferenceLinker manifestLinker; + if (manifestLinker.consume(&mContext, manifestXml.get())) { + + if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), + manifestXml.get(), + &proguardKeepSet)) { + error = true; + } + + if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {}, + archiveWriter.get())) { + error = true; + } + } else { + error = true; + } + } + + for (; !filesToProcess.empty(); filesToProcess.pop()) { + FilesToProcess& f = filesToProcess.front(); + if (f.file.name.type != ResourceType::kRaw && + util::stringEndsWith<char>(f.source.path, ".xml.flat")) { + if (mOptions.verbose) { + mContext.getDiagnostics()->note(DiagMessage() << "linking " << f.source.path); + } + + std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(f.source.path); + if (!xmlRes) { + return 1; + } + + xmlRes->file = std::move(f.file); + + XmlReferenceLinker xmlLinker; + if (xmlLinker.consume(&mContext, xmlRes.get())) { + if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(), + &proguardKeepSet)) { + error = true; + } + + Maybe<size_t> maxSdkLevel; + if (!mOptions.noAutoVersion) { + maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u); + } + + if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file), maxSdkLevel, + archiveWriter.get())) { + error = true; + } + + if (!mOptions.noAutoVersion) { + Maybe<ResourceTable::SearchResult> result = mergedTable.findResource( + xmlRes->file.name); + for (int sdkLevel : xmlLinker.getSdkLevels()) { + if (sdkLevel > xmlRes->file.config.sdkVersion && + shouldGenerateVersionedResource(result.value().entry, + xmlRes->file.config, + sdkLevel)) { + xmlRes->file.config.sdkVersion = sdkLevel; + if (!mergedTable.addFileReference(xmlRes->file.name, + xmlRes->file.config, + xmlRes->file.source, + util::utf8ToUtf16( + buildResourceFileName(xmlRes->file)), + mContext.getDiagnostics())) { + error = true; + continue; + } + + if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file), + sdkLevel, archiveWriter.get())) { + error = true; + } + } + } + } + + } else { + error = true; + } + } else { + if (mOptions.verbose) { + mContext.getDiagnostics()->note(DiagMessage() << "copying " << f.source.path); + } + + if (!copyFileToArchive(f.source.path, buildResourceFileName(f.file), 0, + archiveWriter.get())) { + error = true; + } + } + } + + if (error) { + mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources"); + return 1; + } + + if (!mOptions.noAutoVersion) { + AutoVersioner versioner; + if (!versioner.consume(&mContext, &mergedTable)) { + mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles"); + return 1; + } + } + + if (!flattenTable(&mergedTable, archiveWriter.get())) { + mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc"); + return 1; + } + + if (mOptions.generateJavaClassPath) { + if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage())) { + return 1; + } + } + + if (mOptions.generateProguardRulesPath) { + if (!writeProguardFile(proguardKeepSet)) { + return 1; + } + } + + if (mOptions.verbose) { + Debug::printTable(&mergedTable); + for (; !tableMerger.getFileMergeQueue()->empty(); + tableMerger.getFileMergeQueue()->pop()) { + const FileToMerge& f = tableMerger.getFileMergeQueue()->front(); + mContext.getDiagnostics()->note( + DiagMessage() << f.srcPath << " -> " << f.dstPath << " from (0x" + << std::hex << (uintptr_t) f.srcTable << std::dec); + } + } + + return 0; + } +}; + +int link(const std::vector<StringPiece>& args) { + LinkOptions options; + Flags flags = Flags() + .requiredFlag("-o", "Output path", &options.outputPath) + .requiredFlag("--manifest", "Path to the Android manifest to build", + &options.manifestPath) + .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths) + .optionalFlag("--java", "Directory in which to generate R.java", + &options.generateJavaClassPath) + .optionalFlag("--proguard", "Output file for generated Proguard rules", + &options.generateProguardRulesPath) + .optionalSwitch("--no-auto-version", + "Disables automatic style and layout SDK versioning", + &options.noAutoVersion) + .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified " + "by -o", + &options.outputToDirectory) + .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib) + .optionalSwitch("-v", "Enables verbose logging", &options.verbose); + + if (!flags.parse("aapt2 link", args, &std::cerr)) { + return 1; + } + + LinkCommand cmd = { options }; + return cmd.run(flags.getArgs()); +} + +} // namespace aapt diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h new file mode 100644 index 000000000000..2cc8d9ff5096 --- /dev/null +++ b/tools/aapt2/link/Linkers.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_LINKER_LINKERS_H +#define AAPT_LINKER_LINKERS_H + +#include "process/IResourceTableConsumer.h" + +#include <set> + +namespace aapt { + +class ResourceTable; +struct ResourceEntry; +struct ConfigDescription; + +/** + * Determines whether a versioned resource should be created. If a versioned resource already + * exists, it takes precedence. + */ +bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config, + const int sdkVersionToGenerate); + +struct AutoVersioner : public IResourceTableConsumer { + bool consume(IAaptContext* context, ResourceTable* table) override; +}; + +struct PrivateAttributeMover : public IResourceTableConsumer { + bool consume(IAaptContext* context, ResourceTable* table) override; +}; + +struct XmlAutoVersioner : public IXmlResourceConsumer { + bool consume(IAaptContext* context, XmlResource* resource) override; +}; + +struct ReferenceLinker : public IResourceTableConsumer { + bool consume(IAaptContext* context, ResourceTable* table) override; +}; + +class XmlReferenceLinker : IXmlResourceConsumer { +private: + std::set<int> mSdkLevelsFound; + +public: + bool consume(IAaptContext* context, XmlResource* resource) override; + + const std::set<int>& getSdkLevels() const { + return mSdkLevelsFound; + } +}; + +} // namespace aapt + +#endif /* AAPT_LINKER_LINKERS_H */ diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp new file mode 100644 index 000000000000..db20bcb09425 --- /dev/null +++ b/tools/aapt2/link/PrivateAttributeMover.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ResourceTable.h" + +#include "link/Linkers.h" + +#include <algorithm> +#include <iterator> + +namespace aapt { + +template <typename InputContainer, typename OutputIterator, typename Predicate> +OutputIterator moveIf(InputContainer& inputContainer, OutputIterator result, + Predicate pred) { + const auto last = inputContainer.end(); + auto newEnd = std::find_if(inputContainer.begin(), inputContainer.end(), pred); + if (newEnd == last) { + return result; + } + + *result = std::move(*newEnd); + + auto first = newEnd; + ++first; + + for (; first != last; ++first) { + if (bool(pred(*first))) { + // We want to move this guy + *result = std::move(*first); + ++result; + } else { + // We want to keep this guy, but we will need to move it up the list to replace + // missing items. + *newEnd = std::move(*first); + ++newEnd; + } + } + + inputContainer.erase(newEnd, last); + return result; +} + +bool PrivateAttributeMover::consume(IAaptContext* context, ResourceTable* table) { + for (auto& package : table->packages) { + ResourceTableType* type = package->findType(ResourceType::kAttr); + if (!type) { + continue; + } + + if (!type->publicStatus.isPublic) { + // No public attributes, so we can safely leave these private attributes where they are. + return true; + } + + ResourceTableType* privAttrType = package->findOrCreateType(ResourceType::kAttrPrivate); + assert(privAttrType->entries.empty()); + + moveIf(type->entries, std::back_inserter(privAttrType->entries), + [](const std::unique_ptr<ResourceEntry>& entry) -> bool { + return !entry->publicStatus.isPublic; + }); + break; + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp new file mode 100644 index 000000000000..8173c30b25fd --- /dev/null +++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "link/Linkers.h" +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(PrivateAttributeMoverTest, MovePrivateAttributes) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addSimple(u"@android:attr/publicA") + .addSimple(u"@android:attr/privateA") + .addSimple(u"@android:attr/publicB") + .addSimple(u"@android:attr/privateB") + .build(); + ASSERT_TRUE(table->markPublic(test::parseNameOrDie(u"@android:attr/publicA"), + ResourceId(0x01010000), {}, context->getDiagnostics())); + ASSERT_TRUE(table->markPublic(test::parseNameOrDie(u"@android:attr/publicB"), + ResourceId(0x01010002), {}, context->getDiagnostics())); + + PrivateAttributeMover mover; + ASSERT_TRUE(mover.consume(context.get(), table.get())); + + ResourceTablePackage* package = table->findPackage(u"android"); + ASSERT_NE(package, nullptr); + + ResourceTableType* type = package->findType(ResourceType::kAttr); + ASSERT_NE(type, nullptr); + ASSERT_EQ(type->entries.size(), 2u); + EXPECT_NE(type->findEntry(u"publicA"), nullptr); + EXPECT_NE(type->findEntry(u"publicB"), nullptr); + + type = package->findType(ResourceType::kAttrPrivate); + ASSERT_NE(type, nullptr); + ASSERT_EQ(type->entries.size(), 2u); + EXPECT_NE(type->findEntry(u"privateA"), nullptr); + EXPECT_NE(type->findEntry(u"privateB"), nullptr); +} + +TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefined) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addSimple(u"@android:attr/privateA") + .addSimple(u"@android:attr/privateB") + .build(); + + PrivateAttributeMover mover; + ASSERT_TRUE(mover.consume(context.get(), table.get())); + + ResourceTablePackage* package = table->findPackage(u"android"); + ASSERT_NE(package, nullptr); + + ResourceTableType* type = package->findType(ResourceType::kAttr); + ASSERT_NE(type, nullptr); + ASSERT_EQ(type->entries.size(), 2u); + + type = package->findType(ResourceType::kAttrPrivate); + ASSERT_EQ(type, nullptr); +} + +} // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp new file mode 100644 index 000000000000..c0356e50da3a --- /dev/null +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Diagnostics.h" +#include "ResourceTable.h" +#include "ResourceUtils.h" +#include "ResourceValues.h" +#include "util/Util.h" +#include "ValueVisitor.h" + +#include "link/Linkers.h" +#include "link/ReferenceLinkerVisitor.h" +#include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" + +#include <androidfw/ResourceTypes.h> +#include <cassert> + +namespace aapt { + +namespace { + +/** + * The ReferenceLinkerVisitor will follow all references and make sure they point + * to resources that actually exist, either in the local resource table, or as external + * symbols. Once the target resource has been found, the ID of the resource will be assigned + * to the reference object. + * + * NOTE: All of the entries in the ResourceTable must be assigned IDs. + */ +class StyleAndReferenceLinkerVisitor : public ValueVisitor { +private: + ReferenceLinkerVisitor mReferenceVisitor; + IAaptContext* mContext; + ISymbolTable* mSymbols; + IPackageDeclStack* mPackageDecls; + StringPool* mStringPool; + bool mError = false; + + const ISymbolTable::Symbol* findAttributeSymbol(Reference* reference) { + assert(reference); + assert(reference->name || reference->id); + + if (reference->name) { + // Transform the package name if it is an alias. + Maybe<ResourceName> realName = mPackageDecls->transformPackage( + reference->name.value(), mContext->getCompilationPackage()); + + // Mangle the reference name if it should be mangled. + Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName( + realName ? realName.value() : reference->name.value()); + + const ISymbolTable::Symbol* s = nullptr; + if (mangledName) { + s = mSymbols->findByName(mangledName.value()); + } else if (realName) { + s = mSymbols->findByName(realName.value()); + } else { + s = mSymbols->findByName(reference->name.value()); + } + + if (s && s->attribute) { + return s; + } + } + + if (reference->id) { + if (const ISymbolTable::Symbol* s = mSymbols->findById(reference->id.value())) { + if (s->attribute) { + return s; + } + } + } + return nullptr; + } + + /** + * Transform a RawString value into a more specific, appropriate value, based on the + * Attribute. If a non RawString value is passed in, this is an identity transform. + */ + std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value, + const Attribute* attr) { + if (RawString* rawString = valueCast<RawString>(value.get())) { + std::unique_ptr<Item> transformed = ResourceUtils::parseItemForAttribute( + *rawString->value, attr); + + // If we could not parse as any specific type, try a basic STRING. + if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) { + util::StringBuilder stringBuilder; + stringBuilder.append(*rawString->value); + if (stringBuilder) { + transformed = util::make_unique<String>( + mStringPool->makeRef(stringBuilder.str())); + } + } + + if (transformed) { + return transformed; + } + }; + return value; + } + + void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr, + const Item* value) { + *msg << "expected"; + if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) { + *msg << " boolean"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_COLOR) { + *msg << " color"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) { + *msg << " dimension"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_ENUM) { + *msg << " enum"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) { + *msg << " flags"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) { + *msg << " float"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) { + *msg << " fraction"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) { + *msg << " integer"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) { + *msg << " reference"; + } + + if (attr->typeMask & android::ResTable_map::TYPE_STRING) { + *msg << " string"; + } + + *msg << " but got " << *value; + } + +public: + using ValueVisitor::visit; + + StyleAndReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, + StringPool* stringPool, IPackageDeclStack* decl) : + mReferenceVisitor(context, symbols, decl), mContext(context), mSymbols(symbols), + mPackageDecls(decl), mStringPool(stringPool) { + } + + void visit(Reference* reference) override { + mReferenceVisitor.visit(reference); + } + + /** + * We visit the Style specially because during this phase, values of attributes are + * all RawString values. Now that we are expected to resolve all symbols, we can + * lookup the attributes to find out which types are allowed for the attributes' values. + */ + void visit(Style* style) override { + if (style->parent) { + visit(&style->parent.value()); + } + + for (Style::Entry& entry : style->entries) { + if (const ISymbolTable::Symbol* s = findAttributeSymbol(&entry.key)) { + // Assign our style key the correct ID. + entry.key.id = s->id; + + // Try to convert the value to a more specific, typed value based on the + // attribute it is set to. + entry.value = parseValueWithAttribute(std::move(entry.value), s->attribute.get()); + + // Link/resolve the final value (mostly if it's a reference). + entry.value->accept(this); + + // Now verify that the type of this item is compatible with the attribute it + // is defined for. + android::Res_value val = {}; + entry.value->flatten(&val); + + // Always allow references. + const uint32_t typeMask = s->attribute->typeMask | + android::ResTable_map::TYPE_REFERENCE; + + if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) { + // The actual type of this item is incompatible with the attribute. + DiagMessage msg; + buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get()); + mContext->getDiagnostics()->error(msg); + mError = true; + } + } else { + DiagMessage msg; + msg << "style attribute '"; + if (entry.key.name) { + msg << entry.key.name.value().package << ":" << entry.key.name.value().entry; + } else { + msg << entry.key.id.value(); + } + msg << "' not found"; + mContext->getDiagnostics()->error(msg); + mError = true; + } + } + } + + inline bool hasError() { + return mError || mReferenceVisitor.hasError(); + } +}; + +struct EmptyDeclStack : public IPackageDeclStack { + Maybe<ResourceName> transformPackage(const ResourceName& name, + const StringPiece16& localPackage) const override { + if (name.package.empty()) { + return ResourceName{ localPackage.toString(), name.type, name.entry }; + } + return {}; + } +}; + +} // namespace + +bool ReferenceLinker::consume(IAaptContext* context, ResourceTable* table) { + EmptyDeclStack declStack; + bool error = false; + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + // A public entry with no values will not be encoded properly. + if (entry->publicStatus.isPublic && entry->values.empty()) { + context->getDiagnostics()->error(DiagMessage(entry->publicStatus.source) + << "No value for public resource"); + error = true; + } + + for (auto& configValue : entry->values) { + StyleAndReferenceLinkerVisitor visitor(context, + context->getExternalSymbols(), + &table->stringPool, &declStack); + configValue.value->accept(&visitor); + if (visitor.hasError()) { + error = true; + } + } + } + } + } + return !error; +} + +} // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinkerVisitor.h b/tools/aapt2/link/ReferenceLinkerVisitor.h new file mode 100644 index 000000000000..c70531ba8296 --- /dev/null +++ b/tools/aapt2/link/ReferenceLinkerVisitor.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_LINKER_REFERENCELINKERVISITOR_H +#define AAPT_LINKER_REFERENCELINKERVISITOR_H + +#include "Resource.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" + +#include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" + +#include <cassert> + +namespace aapt { + +/** + * The ReferenceLinkerVisitor will follow all references and make sure they point + * to resources that actually exist in the given ISymbolTable. + * Once the target resource has been found, the ID of the resource will be assigned + * to the reference object. + */ +class ReferenceLinkerVisitor : public ValueVisitor { + using ValueVisitor::visit; +private: + IAaptContext* mContext; + ISymbolTable* mSymbols; + IPackageDeclStack* mPackageDecls; + bool mError = false; + +public: + ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, IPackageDeclStack* decls) : + mContext(context), mSymbols(symbols), mPackageDecls(decls) { + } + + /** + * Lookup a reference and ensure it exists, either in our local table, or as an external + * symbol. Once found, assign the ID of the target resource to this reference object. + */ + void visit(Reference* reference) override { + assert(reference); + assert(reference->name || reference->id); + + // We prefer to lookup by name if the name is set. Otherwise it could be + // an out-of-date ID. + if (reference->name) { + // Transform the package name if it is an alias. + Maybe<ResourceName> realName = mPackageDecls->transformPackage( + reference->name.value(), mContext->getCompilationPackage()); + + // Mangle the reference name if it should be mangled. + Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName( + realName ? realName.value() : reference->name.value()); + + const ISymbolTable::Symbol* s = nullptr; + if (mangledName) { + s = mSymbols->findByName(mangledName.value()); + } else if (realName) { + s = mSymbols->findByName(realName.value()); + } else { + s = mSymbols->findByName(reference->name.value()); + } + + if (s) { + reference->id = s->id; + return; + } + + DiagMessage errorMsg; + errorMsg << "reference to " << reference->name.value(); + if (realName) { + errorMsg << " (aka " << realName.value() << ")"; + } + errorMsg << " was not found"; + mContext->getDiagnostics()->error(errorMsg); + mError = true; + return; + } + + if (!mSymbols->findById(reference->id.value())) { + mContext->getDiagnostics()->error(DiagMessage() + << "reference to " << reference->id.value() + << " was not found"); + mError = true; + } + } + + inline bool hasError() { + return mError; + } +}; + +} // namespace aapt + +#endif /* AAPT_LINKER_REFERENCELINKERVISITOR_H */ diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp new file mode 100644 index 000000000000..5e7641a4ec4f --- /dev/null +++ b/tools/aapt2/link/ReferenceLinker_test.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "link/Linkers.h" +#include "process/SymbolTable.h" + +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(ReferenceLinkerTest, LinkSimpleReferences) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"com.app.test", 0x7f) + .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000), + u"@com.app.test:string/bar") + + // Test use of local reference (w/o package name). + .addReference(u"@com.app.test:string/bar", ResourceId(0x7f020001), u"@string/baz") + + .addReference(u"@com.app.test:string/baz", ResourceId(0x7f020002), + u"@android:string/ok") + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder() + .setCompilationPackage(u"com.app.test") + .setPackageId(0x7f) + .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" }) + .setSymbolTable(JoinedSymbolTableBuilder() + .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get())) + .addSymbolTable(test::StaticSymbolTableBuilder() + .addSymbol(u"@android:string/ok", ResourceId(0x01040034)) + .build()) + .build()) + .build(); + + ReferenceLinker linker; + ASSERT_TRUE(linker.consume(context.get(), table.get())); + + Reference* ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/foo"); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001)); + + ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/bar"); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f020002)); + + ref = test::getValue<Reference>(table.get(), u"@com.app.test:string/baz"); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x01040034)); +} + +TEST(ReferenceLinkerTest, LinkStyleAttributes) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"com.app.test", 0x7f) + .addValue(u"@com.app.test:style/Theme", test::StyleBuilder() + .setParent(u"@android:style/Theme.Material") + .addItem(u"@android:attr/foo", ResourceUtils::tryParseColor(u"#ff00ff")) + .addItem(u"@android:attr/bar", {} /* placeholder */) + .build()) + .build(); + + { + // We need to fill in the value for the attribute android:attr/bar after we build the + // table, because we need access to the string pool. + Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme"); + ASSERT_NE(style, nullptr); + style->entries.back().value = util::make_unique<RawString>( + table->stringPool.makeRef(u"one|two")); + } + + std::unique_ptr<IAaptContext> context = test::ContextBuilder() + .setCompilationPackage(u"com.app.test") + .setPackageId(0x7f) + .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" }) + .setSymbolTable(test::StaticSymbolTableBuilder() + .addSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000)) + .addSymbol(u"@android:attr/foo", ResourceId(0x01010001), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_COLOR) + .build()) + .addSymbol(u"@android:attr/bar", ResourceId(0x01010002), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_FLAGS) + .addItem(u"one", 0x01) + .addItem(u"two", 0x02) + .build()) + .build()) + .build(); + + ReferenceLinker linker; + ASSERT_TRUE(linker.consume(context.get(), table.get())); + + Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme"); + ASSERT_NE(style, nullptr); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().id); + EXPECT_EQ(style->parent.value().id.value(), ResourceId(0x01060000)); + + ASSERT_EQ(2u, style->entries.size()); + + AAPT_ASSERT_TRUE(style->entries[0].key.id); + EXPECT_EQ(style->entries[0].key.id.value(), ResourceId(0x01010001)); + ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[0].value.get()), nullptr); + + AAPT_ASSERT_TRUE(style->entries[1].key.id); + EXPECT_EQ(style->entries[1].key.id.value(), ResourceId(0x01010002)); + ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[1].value.get()), nullptr); +} + +TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder() + .setCompilationPackage(u"com.app.test") + .setPackageId(0x7f) + .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.android.support" } }) + .setSymbolTable(test::StaticSymbolTableBuilder() + .addSymbol(u"@com.app.test:attr/com.android.support$foo", + ResourceId(0x7f010000), test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) + .build()) + .build(); + + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .setPackageId(u"com.app.test", 0x7f) + .addValue(u"@com.app.test:style/Theme", ResourceId(0x7f020000), + test::StyleBuilder().addItem(u"@com.android.support:attr/foo", + ResourceUtils::tryParseColor(u"#ff0000")) + .build()) + .build(); + + ReferenceLinker linker; + ASSERT_TRUE(linker.consume(context.get(), table.get())); + + Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme"); + ASSERT_NE(style, nullptr); + ASSERT_EQ(1u, style->entries.size()); + AAPT_ASSERT_TRUE(style->entries.front().key.id); + EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000)); +} + +} // namespace aapt diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp new file mode 100644 index 000000000000..d5fd1fc49c85 --- /dev/null +++ b/tools/aapt2/link/TableMerger.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" + +#include "link/TableMerger.h" +#include "util/Util.h" + +#include <cassert> + +namespace aapt { + +TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable) : + mContext(context), mMasterTable(outTable) { + // Create the desired package that all tables will be merged into. + mMasterPackage = mMasterTable->createPackage( + mContext->getCompilationPackage(), mContext->getPackageId()); + assert(mMasterPackage && "package name or ID already taken"); +} + +bool TableMerger::merge(const Source& src, ResourceTable* table) { + const uint8_t desiredPackageId = mContext->getPackageId(); + + bool error = false; + for (auto& package : table->packages) { + // Warn of packages with an unrelated ID. + if (package->id && package->id.value() != desiredPackageId) { + mContext->getDiagnostics()->warn(DiagMessage(src) + << "ignoring package " << package->name); + continue; + } + + bool manglePackage = false; + if (!package->name.empty() && mContext->getCompilationPackage() != package->name) { + manglePackage = true; + mMergedPackages.insert(package->name); + } + + // Merge here. Once the entries are merged and mangled, any references to + // them are still valid. This is because un-mangled references are + // mangled, then looked up at resolution time. + // Also, when linking, we convert references with no package name to use + // the compilation package name. + if (!doMerge(src, table, package.get(), manglePackage)) { + error = true; + } + } + return !error; +} + +bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable, + ResourceTablePackage* srcPackage, const bool manglePackage) { + bool error = false; + + for (auto& srcType : srcPackage->types) { + ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type); + if (srcType->publicStatus.isPublic) { + if (dstType->publicStatus.isPublic && dstType->id && srcType->id + && dstType->id.value() == srcType->id.value()) { + // Both types are public and have different IDs. + mContext->getDiagnostics()->error(DiagMessage(src) + << "can not merge type '" + << srcType->type + << "': conflicting public IDs"); + error = true; + continue; + } + + dstType->publicStatus = std::move(srcType->publicStatus); + dstType->id = srcType->id; + } + + for (auto& srcEntry : srcType->entries) { + ResourceEntry* dstEntry; + if (manglePackage) { + dstEntry = dstType->findOrCreateEntry(NameMangler::mangleEntry( + srcPackage->name, srcEntry->name)); + } else { + dstEntry = dstType->findOrCreateEntry(srcEntry->name); + } + + if (srcEntry->publicStatus.isPublic) { + if (dstEntry->publicStatus.isPublic && dstEntry->id && srcEntry->id + && dstEntry->id.value() != srcEntry->id.value()) { + // Both entries are public and have different IDs. + mContext->getDiagnostics()->error(DiagMessage(src) + << "can not merge entry '" + << srcEntry->name + << "': conflicting public IDs"); + error = true; + continue; + } + + dstEntry->publicStatus = std::move(srcEntry->publicStatus); + dstEntry->id = srcEntry->id; + } + + for (ResourceConfigValue& srcValue : srcEntry->values) { + auto cmp = [](const ResourceConfigValue& a, + const ConfigDescription& b) -> bool { + return a.config < b; + }; + + auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(), + srcValue.config, cmp); + + if (iter != dstEntry->values.end() && iter->config == srcValue.config) { + const int collisionResult = ResourceTable::resolveValueCollision( + iter->value.get(), srcValue.value.get()); + if (collisionResult == 0) { + // Error! + ResourceNameRef resourceName = + { srcPackage->name, srcType->type, srcEntry->name }; + mContext->getDiagnostics()->error(DiagMessage(srcValue.source) + << "resource '" << resourceName + << "' has a conflicting value for " + << "configuration (" + << srcValue.config << ")"); + mContext->getDiagnostics()->note(DiagMessage(iter->source) + << "originally defined here"); + error = true; + continue; + } else if (collisionResult < 0) { + // Keep our existing value. + continue; + } + + } else { + // Insert a new value. + iter = dstEntry->values.insert(iter, + ResourceConfigValue{ srcValue.config }); + } + + iter->source = std::move(srcValue.source); + iter->comment = std::move(srcValue.comment); + if (manglePackage) { + iter->value = cloneAndMangle(srcTable, srcPackage->name, + srcValue.value.get()); + } else { + iter->value = clone(srcValue.value.get()); + } + } + } + } + return !error; +} + +std::unique_ptr<Value> TableMerger::cloneAndMangle(ResourceTable* table, + const std::u16string& package, + Value* value) { + if (FileReference* f = valueCast<FileReference>(value)) { + // Mangle the path. + StringPiece16 prefix, entry, suffix; + if (util::extractResFilePathParts(*f->path, &prefix, &entry, &suffix)) { + std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString()); + std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString(); + mFilesToMerge.push(FileToMerge{ table, *f->path, newPath }); + return util::make_unique<FileReference>(mMasterTable->stringPool.makeRef(newPath)); + } + } + return clone(value); +} + +std::unique_ptr<Value> TableMerger::clone(Value* value) { + return std::unique_ptr<Value>(value->clone(&mMasterTable->stringPool)); +} + +} // namespace aapt diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h new file mode 100644 index 000000000000..157c16ee7c98 --- /dev/null +++ b/tools/aapt2/link/TableMerger.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_TABLEMERGER_H +#define AAPT_TABLEMERGER_H + +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "util/Util.h" + +#include "process/IResourceTableConsumer.h" + +#include <queue> +#include <set> + +namespace aapt { + +struct FileToMerge { + ResourceTable* srcTable; + std::u16string srcPath; + std::u16string dstPath; +}; + +/** + * TableMerger takes resource tables and merges all packages within the tables that have the same + * package ID. + * + * If a package has a different name, all the entries in that table have their names mangled + * to include the package name. This way there are no collisions. In order to do this correctly, + * the TableMerger needs to also mangle any FileReference paths. Once these are mangled, + * the original source path of the file, along with the new destination path is recorded in the + * queue returned from getFileMergeQueue(). + * + * Once the merging is complete, a separate process can go collect the files from the various + * source APKs and either copy or process their XML and put them in the correct location in + * the final APK. + */ +class TableMerger { +public: + TableMerger(IAaptContext* context, ResourceTable* outTable); + + inline std::queue<FileToMerge>* getFileMergeQueue() { + return &mFilesToMerge; + } + + inline const std::set<std::u16string>& getMergedPackages() const { + return mMergedPackages; + } + + bool merge(const Source& src, ResourceTable* table); + +private: + IAaptContext* mContext; + ResourceTable* mMasterTable; + ResourceTablePackage* mMasterPackage; + + std::set<std::u16string> mMergedPackages; + std::queue<FileToMerge> mFilesToMerge; + + bool doMerge(const Source& src, ResourceTable* srcTable, ResourceTablePackage* srcPackage, + const bool manglePackage); + + std::unique_ptr<Value> cloneAndMangle(ResourceTable* table, const std::u16string& package, + Value* value); + std::unique_ptr<Value> clone(Value* value); +}; + +} // namespace aapt + +#endif /* AAPT_TABLEMERGER_H */ diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp new file mode 100644 index 000000000000..fa7ce8627cf5 --- /dev/null +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "link/TableMerger.h" + +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.h> + +namespace aapt { + +struct TableMergerTest : public ::testing::Test { + std::unique_ptr<IAaptContext> mContext; + + void SetUp() override { + mContext = test::ContextBuilder() + // We are compiling this package. + .setCompilationPackage(u"com.app.a") + + // Merge all packages that have this package ID. + .setPackageId(0x7f) + + // Mangle all packages that do not have this package name. + .setNameManglerPolicy(NameManglerPolicy{ u"com.app.a", { u"com.app.b" } }) + + .build(); + } +}; + +TEST_F(TableMergerTest, SimpleMerge) { + std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() + .setPackageId(u"com.app.a", 0x7f) + .addReference(u"@com.app.a:id/foo", u"@com.app.a:id/bar") + .addReference(u"@com.app.a:id/bar", u"@com.app.b:id/foo") + .addValue(u"@com.app.a:styleable/view", test::StyleableBuilder() + .addItem(u"@com.app.b:id/foo") + .build()) + .build(); + + std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() + .setPackageId(u"com.app.b", 0x7f) + .addSimple(u"@com.app.b:id/foo") + .build(); + + ResourceTable finalTable; + TableMerger merger(mContext.get(), &finalTable); + + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_TRUE(merger.merge({}, tableB.get())); + + EXPECT_TRUE(merger.getMergedPackages().count(u"com.app.b") != 0); + + // Entries from com.app.a should not be mangled. + AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/foo"))); + AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/bar"))); + AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:styleable/view"))); + + // The unmangled name should not be present. + AAPT_EXPECT_FALSE(finalTable.findResource(test::parseNameOrDie(u"@com.app.b:id/foo"))); + + // Look for the mangled name. + AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/com.app.b$foo"))); +} + +TEST_F(TableMergerTest, MergeFileReferences) { + std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() + .setPackageId(u"com.app.a", 0x7f) + .addFileReference(u"@com.app.a:xml/file", u"res/xml/file.xml") + .build(); + std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() + .setPackageId(u"com.app.b", 0x7f) + .addFileReference(u"@com.app.b:xml/file", u"res/xml/file.xml") + .build(); + + ResourceTable finalTable; + TableMerger merger(mContext.get(), &finalTable); + + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_TRUE(merger.merge({}, tableB.get())); + + FileReference* f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/file"); + ASSERT_NE(f, nullptr); + EXPECT_EQ(std::u16string(u"res/xml/file.xml"), *f->path); + + f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/com.app.b$file"); + ASSERT_NE(f, nullptr); + EXPECT_EQ(std::u16string(u"res/xml/com.app.b$file.xml"), *f->path); + + std::queue<FileToMerge>* filesToMerge = merger.getFileMergeQueue(); + ASSERT_FALSE(filesToMerge->empty()); + + FileToMerge& fileToMerge = filesToMerge->front(); + EXPECT_EQ(fileToMerge.srcTable, tableB.get()); + EXPECT_EQ(fileToMerge.srcPath, u"res/xml/file.xml"); + EXPECT_EQ(fileToMerge.dstPath, u"res/xml/com.app.b$file.xml"); +} + +} // namespace aapt diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp new file mode 100644 index 000000000000..147b9bf16248 --- /dev/null +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Diagnostics.h" +#include "ResourceUtils.h" +#include "SdkConstants.h" +#include "XmlDom.h" + +#include "link/Linkers.h" +#include "link/ReferenceLinkerVisitor.h" +#include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" +#include "util/Util.h" + +namespace aapt { + +namespace { + +class XmlReferenceLinkerVisitor : public xml::PackageAwareVisitor { +private: + IAaptContext* mContext; + ISymbolTable* mSymbols; + std::set<int>* mSdkLevelsFound; + ReferenceLinkerVisitor mReferenceLinkerVisitor; + bool mError = false; + +public: + using xml::PackageAwareVisitor::visit; + + XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, + std::set<int>* sdkLevelsFound) : + mContext(context), mSymbols(symbols), mSdkLevelsFound(sdkLevelsFound), + mReferenceLinkerVisitor(context, symbols, this) { + } + + void visit(xml::Element* el) override { + for (xml::Attribute& attr : el->attributes) { + Maybe<std::u16string> maybePackage = + util::extractPackageFromNamespace(attr.namespaceUri); + if (maybePackage) { + // There is a valid package name for this attribute. We will look this up. + StringPiece16 package = maybePackage.value(); + if (package.empty()) { + // Empty package means the 'current' or 'local' package. + package = mContext->getCompilationPackage(); + } + + attr.compiledAttribute = compileAttribute( + ResourceName{ package.toString(), ResourceType::kAttr, attr.name }); + + // Convert the string value into a compiled Value if this is a valid attribute. + if (attr.compiledAttribute) { + // Record all SDK levels from which the attributes were defined. + const int sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id); + if (sdkLevel > 1) { + mSdkLevelsFound->insert(sdkLevel); + } + + const Attribute* attribute = &attr.compiledAttribute.value().attribute; + attr.compiledValue = ResourceUtils::parseItemForAttribute(attr.value, + attribute); + if (!attr.compiledValue && + !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) { + // We won't be able to encode this as a string. + mContext->getDiagnostics()->error( + DiagMessage() << "'" << attr.value << "' " + << "is incompatible with attribute " + << package << ":" << attr.name << " " << *attribute); + mError = true; + } + } else { + mContext->getDiagnostics()->error( + DiagMessage() << "attribute '" << package << ":" << attr.name + << "' was not found"); + mError = true; + + } + } else { + // We still encode references. + attr.compiledValue = ResourceUtils::tryParseReference(attr.value); + } + + if (attr.compiledValue) { + // With a compiledValue, we must resolve the reference and assign it an ID. + attr.compiledValue->accept(&mReferenceLinkerVisitor); + } + } + + // Call the super implementation. + xml::PackageAwareVisitor::visit(el); + } + + Maybe<xml::AaptAttribute> compileAttribute(const ResourceName& name) { + Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(name); + if (const ISymbolTable::Symbol* symbol = mSymbols->findByName( + mangledName ? mangledName.value() : name)) { + if (symbol->attribute) { + return xml::AaptAttribute{ symbol->id, *symbol->attribute }; + } + } + return {}; + } + + inline bool hasError() { + return mError || mReferenceLinkerVisitor.hasError(); + } +}; + +} // namespace + +bool XmlReferenceLinker::consume(IAaptContext* context, XmlResource* resource) { + mSdkLevelsFound.clear(); + XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), &mSdkLevelsFound); + if (resource->root) { + resource->root->accept(&visitor); + return !visitor.hasError(); + } + return false; +} + +} // namespace aapt diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp new file mode 100644 index 000000000000..7f91ec348c9d --- /dev/null +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "link/Linkers.h" + +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.h> + +namespace aapt { + +class XmlReferenceLinkerTest : public ::testing::Test { +public: + void SetUp() override { + mContext = test::ContextBuilder() + .setCompilationPackage(u"com.app.test") + .setNameManglerPolicy( + NameManglerPolicy{ u"com.app.test", { u"com.android.support" } }) + .setSymbolTable(test::StaticSymbolTableBuilder() + .addSymbol(u"@android:attr/layout_width", ResourceId(0x01010000), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_ENUM | + android::ResTable_map::TYPE_DIMENSION) + .addItem(u"match_parent", 0xffffffff) + .build()) + .addSymbol(u"@android:attr/background", ResourceId(0x01010001), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) + .addSymbol(u"@android:attr/attr", ResourceId(0x01010002), + test::AttributeBuilder().build()) + .addSymbol(u"@android:attr/text", ResourceId(0x01010003), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_STRING) + .build()) + + // Add one real symbol that was introduces in v21 + .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435), + test::AttributeBuilder().build()) + + .addSymbol(u"@android:id/id", ResourceId(0x01030000)) + .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f030000)) + .addSymbol(u"@com.app.test:color/green", ResourceId(0x7f020000)) + .addSymbol(u"@com.app.test:color/red", ResourceId(0x7f020001)) + .addSymbol(u"@com.app.test:attr/colorAccent", ResourceId(0x7f010000), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) + .addSymbol(u"@com.app.test:attr/com.android.support$colorAccent", + ResourceId(0x7f010001), test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) + .addSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002), + test::AttributeBuilder().build()) + .build()) + .build(); + } + +protected: + std::unique_ptr<IAaptContext> mContext; +}; + +static xml::Element* getRootElement(XmlResource* doc) { + xml::Node* node = doc->root.get(); + while (xml::nodeCast<xml::Namespace>(node)) { + if (node->children.empty()) { + return nullptr; + } + node = node->children.front().get(); + } + + if (xml::Element* el = xml::nodeCast<xml::Element>(node)) { + return el; + } + return nullptr; +} + +TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { + std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:background="@color/green" + android:text="hello" + class="hello" />)EOF"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + + xml::Element* viewEl = getRootElement(doc.get()); + ASSERT_NE(viewEl, nullptr); + + xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", + u"layout_width"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x01010000)); + ASSERT_NE(xmlAttr->compiledValue, nullptr); + ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr); + + xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", u"background"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x01010001)); + ASSERT_NE(xmlAttr->compiledValue, nullptr); + Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->name); + EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@color/green")); // Make sure the name + // didn't change. + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000)); + + xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", u"text"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + ASSERT_FALSE(xmlAttr->compiledValue); // Strings don't get compiled for memory sake. + + xmlAttr = viewEl->findAttribute(u"", u"class"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_FALSE(xmlAttr->compiledAttribute); + ASSERT_EQ(xmlAttr->compiledValue, nullptr); +} + +TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) { + std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:colorAccent="#ffffff" />)EOF"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + EXPECT_TRUE(linker.getSdkLevels().count(21) == 1); +} + +TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { + std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + <View xmlns:support="http://schemas.android.com/apk/res/com.android.support" + support:colorAccent="#ff0000" />)EOF"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + + xml::Element* viewEl = getRootElement(doc.get()); + ASSERT_NE(viewEl, nullptr); + + xml::Attribute* xmlAttr = viewEl->findAttribute( + u"http://schemas.android.com/apk/res/com.android.support", u"colorAccent"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010001)); + ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr); +} + +TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { + std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + <View xmlns:app="http://schemas.android.com/apk/res-auto" + app:colorAccent="@app:color/red" />)EOF"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + + xml::Element* viewEl = getRootElement(doc.get()); + ASSERT_NE(viewEl, nullptr); + + xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res-auto", + u"colorAccent"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010000)); + Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->name); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001)); +} + +TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { + std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + <View xmlns:app="http://schemas.android.com/apk/res/android" + app:attr="@app:id/id"> + <View xmlns:app="http://schemas.android.com/apk/res/com.app.test" + app:attr="@app:id/id"/> + </View>)EOF"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + + xml::Element* viewEl = getRootElement(doc.get()); + ASSERT_NE(viewEl, nullptr); + + // All attributes and references in this element should be referring to "android" (0x01). + xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android", + u"attr"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x01010002)); + Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x01030000)); + + ASSERT_FALSE(viewEl->getChildElements().empty()); + viewEl = viewEl->getChildElements().front(); + ASSERT_NE(viewEl, nullptr); + + // All attributes and references in this element should be referring to "com.app.test" (0x7f). + xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/com.app.test", u"attr"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010002)); + ref = valueCast<Reference>(xmlAttr->compiledValue.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); +} + +TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { + std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/com.app.test" + android:attr="@id/id"/>)EOF"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + + xml::Element* viewEl = getRootElement(doc.get()); + ASSERT_NE(viewEl, nullptr); + + // All attributes and references in this element should be referring to "com.app.test" (0x7f). + xml::Attribute* xmlAttr = viewEl->findAttribute( + u"http://schemas.android.com/apk/res/com.app.test", u"attr"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id, ResourceId(0x7f010002)); + Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); +} + +} // namespace aapt diff --git a/tools/aapt2/process.dot b/tools/aapt2/process.dot deleted file mode 100644 index 4741952d2332..000000000000 --- a/tools/aapt2/process.dot +++ /dev/null @@ -1,108 +0,0 @@ -digraph aapt { - out_package [label="out/default/package.apk"]; - out_fr_package [label="out/fr/package.apk"]; - out_table_aligned [label="out/default/resources-aligned.arsc"]; - out_table_fr_aligned [label="out/fr/resources-aligned.arsc"]; - out_res_layout_main_xml [label="out/res/layout/main.xml"]; - out_res_layout_v21_main_xml [color=red,label="out/res/layout-v21/main.xml"]; - out_res_layout_fr_main_xml [label="out/res/layout-fr/main.xml"]; - out_res_layout_fr_v21_main_xml [color=red,label="out/res/layout-fr-v21/main.xml"]; - out_table [label="out/default/resources.arsc"]; - out_fr_table [label="out/fr/resources.arsc"]; - out_values_table [label="out/values/resources.arsc"]; - out_layout_table [label="out/layout/resources.arsc"]; - out_values_fr_table [label="out/values-fr/resources.arsc"]; - out_layout_fr_table [label="out/layout-fr/resources.arsc"]; - res_values_strings_xml [label="res/values/strings.xml"]; - res_values_attrs_xml [label="res/values/attrs.xml"]; - res_layout_main_xml [label="res/layout/main.xml"]; - res_layout_fr_main_xml [label="res/layout-fr/main.xml"]; - res_values_fr_strings_xml [label="res/values-fr/strings.xml"]; - - lib_apk_resources_arsc [label="lib.apk:resources.arsc",color=green]; - lib_apk_res_layout_main_xml [label="lib.apk:res/layout/main.xml",color=green]; - lib_apk_res_drawable_icon_png [label="lib.apk:res/drawable/icon.png",color=green]; - lib_apk_fr_res_layout_main_xml [label="lib.apk:res/layout-fr/main.xml",color=green]; - lib_apk_fr_res_drawable_icon_png [label="lib.apk:res/drawable-fr/icon.png",color=green]; - out_res_layout_lib_main_xml [label="out/res/layout/lib-main.xml"]; - - out_package -> package_default; - out_fr_package -> package_fr; - - package_default [shape=box,label="Assemble",color=blue]; - package_default -> out_table_aligned; - package_default -> out_res_layout_main_xml; - package_default -> out_res_layout_v21_main_xml [color=red]; - package_default -> out_res_layout_lib_main_xml; - - package_fr [shape=box,label="Assemble",color=blue]; - package_fr -> out_table_fr_aligned; - package_fr -> out_res_layout_fr_main_xml; - package_fr -> out_res_layout_fr_v21_main_xml [color=red]; - - out_table_aligned -> align_tables; - out_table_fr_aligned -> align_tables; - - align_tables [shape=box,label="Align",color=blue]; - align_tables -> out_table; - align_tables -> out_fr_table; - - out_table -> link_tables; - - link_tables [shape=box,label="Link",color=blue]; - link_tables -> out_values_table; - link_tables -> out_layout_table; - link_tables -> lib_apk_resources_arsc; - - out_values_table -> compile_values; - - compile_values [shape=box,label="Collect",color=blue]; - compile_values -> res_values_strings_xml; - compile_values -> res_values_attrs_xml; - - out_layout_table -> collect_xml; - - collect_xml [shape=box,label="Collect",color=blue]; - collect_xml -> res_layout_main_xml; - - out_fr_table -> link_fr_tables; - - link_fr_tables [shape=box,label="Link",color=blue]; - link_fr_tables -> out_values_fr_table; - link_fr_tables -> out_layout_fr_table; - link_fr_tables -> lib_apk_resources_arsc; - - out_values_fr_table -> compile_values_fr; - - compile_values_fr [shape=box,label="Collect",color=blue]; - compile_values_fr -> res_values_fr_strings_xml; - - out_layout_fr_table -> collect_xml_fr; - - collect_xml_fr [shape=box,label="Collect",color=blue]; - collect_xml_fr -> res_layout_fr_main_xml; - - compile_res_layout_main_xml [shape=box,label="Compile",color=blue]; - - out_res_layout_main_xml -> compile_res_layout_main_xml; - - out_res_layout_v21_main_xml -> compile_res_layout_main_xml [color=red]; - - compile_res_layout_main_xml -> res_layout_main_xml; - compile_res_layout_main_xml -> out_table_aligned; - - compile_res_layout_fr_main_xml [shape=box,label="Compile",color=blue]; - - out_res_layout_fr_main_xml -> compile_res_layout_fr_main_xml; - - out_res_layout_fr_v21_main_xml -> compile_res_layout_fr_main_xml [color=red]; - - compile_res_layout_fr_main_xml -> res_layout_fr_main_xml; - compile_res_layout_fr_main_xml -> out_table_fr_aligned; - - out_res_layout_lib_main_xml -> compile_res_layout_lib_main_xml; - - compile_res_layout_lib_main_xml [shape=box,label="Compile",color=blue]; - compile_res_layout_lib_main_xml -> out_table_aligned; - compile_res_layout_lib_main_xml -> lib_apk_res_layout_main_xml; -} diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h new file mode 100644 index 000000000000..24ad05d14cec --- /dev/null +++ b/tools/aapt2/process/IResourceTableConsumer.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_PROCESS_IRESOURCETABLECONSUMER_H +#define AAPT_PROCESS_IRESOURCETABLECONSUMER_H + +#include "Diagnostics.h" +#include "NameMangler.h" +#include "Resource.h" +#include "ResourceValues.h" +#include "Source.h" + +#include <iostream> +#include <list> +#include <sstream> + +namespace aapt { + +class ResourceTable; +struct ISymbolTable; + +struct IAaptContext { + virtual ~IAaptContext() = default; + + virtual ISymbolTable* getExternalSymbols() = 0; + virtual IDiagnostics* getDiagnostics() = 0; + virtual StringPiece16 getCompilationPackage() = 0; + virtual uint8_t getPackageId() = 0; + virtual NameMangler* getNameMangler() = 0; +}; + +struct IResourceTableConsumer { + virtual ~IResourceTableConsumer() = default; + + virtual bool consume(IAaptContext* context, ResourceTable* table) = 0; +}; + +namespace xml { +struct Node; +} + +struct XmlResource { + ResourceFile file; + std::unique_ptr<xml::Node> root; +}; + +struct IXmlResourceConsumer { + virtual ~IXmlResourceConsumer() = default; + + virtual bool consume(IAaptContext* context, XmlResource* resource) = 0; +}; + +struct IPackageDeclStack { + virtual ~IPackageDeclStack() = default; + + virtual Maybe<ResourceName> transformPackage(const ResourceName& name, + const StringPiece16& localPackage) const = 0; +}; + +} // namespace aapt + +#endif /* AAPT_PROCESS_IRESOURCETABLECONSUMER_H */ diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp new file mode 100644 index 000000000000..c96b080f586b --- /dev/null +++ b/tools/aapt2/process/SymbolTable.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ConfigDescription.h" +#include "Resource.h" +#include "util/Util.h" + +#include "process/SymbolTable.h" + +#include <androidfw/AssetManager.h> +#include <androidfw/ResourceTypes.h> + +namespace aapt { + +const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& name) { + if (const std::shared_ptr<Symbol>& s = mCache.get(name)) { + return s.get(); + } + + Maybe<ResourceTable::SearchResult> result = mTable->findResource(name); + if (!result) { + if (name.type == ResourceType::kAttr) { + // Recurse and try looking up a private attribute. + return findByName(ResourceName{ name.package, ResourceType::kAttrPrivate, name.entry }); + } + return {}; + } + + ResourceTable::SearchResult sr = result.value(); + + // If no ID exists, we treat the symbol as missing. SymbolTables are used to + // find symbols to link. + if (!sr.package->id || !sr.type->id || !sr.entry->id) { + return {}; + } + + std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>(); + symbol->id = ResourceId{ + sr.package->id.value(), sr.type->id.value(), sr.entry->id.value() }; + + if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) { + auto lt = [](ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool { + return lhs.config < rhs; + }; + + const ConfigDescription kDefaultConfig; + auto iter = std::lower_bound(sr.entry->values.begin(), sr.entry->values.end(), + kDefaultConfig, lt); + + if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) { + // This resource has an Attribute. + symbol->attribute = util::make_unique<Attribute>( + *static_cast<Attribute*>(iter->value.get())); + } + } + + if (name.type == ResourceType::kAttrPrivate) { + // Masquerade this entry as kAttr. + mCache.put(ResourceName{ name.package, ResourceType::kAttr, name.entry }, symbol); + } else { + mCache.put(name, symbol); + } + return symbol.get(); +} + + +static std::shared_ptr<ISymbolTable::Symbol> lookupIdInTable(const android::ResTable& table, + ResourceId id) { + android::Res_value val = {}; + ssize_t block = table.getResource(id.id, &val, true); + if (block >= 0) { + std::shared_ptr<ISymbolTable::Symbol> s = std::make_shared<ISymbolTable::Symbol>(); + s->id = id; + return s; + } + + // Try as a bag. + const android::ResTable::bag_entry* entry; + ssize_t count = table.lockBag(id.id, &entry); + if (count < 0) { + table.unlockBag(entry); + return nullptr; + } + + // We found a resource. + std::shared_ptr<ISymbolTable::Symbol> s = std::make_shared<ISymbolTable::Symbol>(); + s->id = id; + + // Check to see if it is an attribute. + for (size_t i = 0; i < (size_t) count; i++) { + if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) { + s->attribute = util::make_unique<Attribute>(false); + s->attribute->typeMask = entry[i].map.value.data; + break; + } + } + + if (s->attribute) { + for (size_t i = 0; i < (size_t) count; i++) { + if (!Res_INTERNALID(entry[i].map.name.ident)) { + android::ResTable::resource_name entryName; + if (!table.getResourceName(entry[i].map.name.ident, false, &entryName)) { + table.unlockBag(entry); + return nullptr; + } + + const ResourceType* parsedType = parseResourceType( + StringPiece16(entryName.type, entryName.typeLen)); + if (!parsedType) { + table.unlockBag(entry); + return nullptr; + } + + Attribute::Symbol symbol; + symbol.symbol.name = ResourceNameRef( + StringPiece16(entryName.package, entryName.packageLen), + *parsedType, + StringPiece16(entryName.name, entryName.nameLen)).toResourceName(); + symbol.symbol.id = ResourceId(entry[i].map.name.ident); + symbol.value = entry[i].map.value.data; + s->attribute->symbols.push_back(std::move(symbol)); + } + } + } + table.unlockBag(entry); + return s; +} + +const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTable::findByName( + const ResourceName& name) { + if (const std::shared_ptr<Symbol>& s = mCache.get(name)) { + return s.get(); + } + + for (const auto& asset : mAssets) { + const android::ResTable& table = asset->getResources(false); + StringPiece16 typeStr = toString(name.type); + ResourceId resId = table.identifierForName(name.entry.data(), name.entry.size(), + typeStr.data(), typeStr.size(), + name.package.data(), name.package.size()); + if (!resId.isValid()) { + continue; + } + + std::shared_ptr<Symbol> s = lookupIdInTable(table, resId); + if (s) { + mCache.put(name, s); + return s.get(); + } + } + return nullptr; +} + +const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTable::findById( + ResourceId id) { + if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) { + return s.get(); + } + + for (const auto& asset : mAssets) { + const android::ResTable& table = asset->getResources(false); + + std::shared_ptr<Symbol> s = lookupIdInTable(table, id); + if (s) { + mIdCache.put(id, s); + return s.get(); + } + } + return nullptr; +} + +const ISymbolTable::Symbol* JoinedSymbolTableBuilder::JoinedSymbolTable::findByName( + const ResourceName& name) { + for (auto& symbolTable : mSymbolTables) { + if (const Symbol* s = symbolTable->findByName(name)) { + return s; + } + } + return {}; +} + +const ISymbolTable::Symbol* JoinedSymbolTableBuilder::JoinedSymbolTable::findById(ResourceId id) { + for (auto& symbolTable : mSymbolTables) { + if (const Symbol* s = symbolTable->findById(id)) { + return s; + } + } + return {}; +} + +} // namespace aapt diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h new file mode 100644 index 000000000000..22096ed82f4e --- /dev/null +++ b/tools/aapt2/process/SymbolTable.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_PROCESS_SYMBOLTABLE_H +#define AAPT_PROCESS_SYMBOLTABLE_H + +#include "Resource.h" +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "util/Util.h" + +#include <utils/JenkinsHash.h> +#include <utils/LruCache.h> + +#include <androidfw/AssetManager.h> +#include <algorithm> +#include <map> +#include <memory> +#include <vector> + +namespace aapt { + +struct ISymbolTable { + virtual ~ISymbolTable() = default; + + struct Symbol { + ResourceId id; + std::unique_ptr<Attribute> attribute; + bool isPublic; + }; + + /** + * Never hold on to the result between calls to findByName or findById. The results + * are typically stored in a cache which may evict entries. + */ + virtual const Symbol* findByName(const ResourceName& name) = 0; + virtual const Symbol* findById(ResourceId id) = 0; +}; + +inline android::hash_t hash_type(const ResourceName& name) { + std::hash<std::u16string> strHash; + android::hash_t hash = 0; + hash = android::JenkinsHashMix(hash, strHash(name.package)); + hash = android::JenkinsHashMix(hash, (uint32_t) name.type); + hash = android::JenkinsHashMix(hash, strHash(name.entry)); + return hash; +} + +inline android::hash_t hash_type(const ResourceId& id) { + return android::hash_type(id.id); +} + +/** + * Presents a ResourceTable as an ISymbolTable, caching results. + * Instances of this class must outlive the encompassed ResourceTable. + * Since symbols are cached, the ResourceTable should not change during the + * lifetime of this SymbolTableWrapper. + * + * If a resource in the ResourceTable does not have a ResourceID assigned to it, + * it is ignored. + * + * Lookups by ID are ignored. + */ +class SymbolTableWrapper : public ISymbolTable { +private: + ResourceTable* mTable; + + // We use shared_ptr because unique_ptr is not supported and + // we need automatic deletion. + android::LruCache<ResourceName, std::shared_ptr<Symbol>> mCache; + +public: + SymbolTableWrapper(ResourceTable* table) : mTable(table), mCache(200) { + } + + const Symbol* findByName(const ResourceName& name) override; + + // Unsupported, all queries to ResourceTable should be done by name. + const Symbol* findById(ResourceId id) override { + return {}; + } +}; + +class AssetManagerSymbolTableBuilder { +private: + struct AssetManagerSymbolTable : public ISymbolTable { + std::vector<std::unique_ptr<android::AssetManager>> mAssets; + + // We use shared_ptr because unique_ptr is not supported and + // we need automatic deletion. + android::LruCache<ResourceName, std::shared_ptr<Symbol>> mCache; + android::LruCache<ResourceId, std::shared_ptr<Symbol>> mIdCache; + + AssetManagerSymbolTable() : mCache(200), mIdCache(200) { + } + + const Symbol* findByName(const ResourceName& name) override; + const Symbol* findById(ResourceId id) override; + }; + + std::unique_ptr<AssetManagerSymbolTable> mSymbolTable = + util::make_unique<AssetManagerSymbolTable>(); + +public: + AssetManagerSymbolTableBuilder& add(std::unique_ptr<android::AssetManager> assetManager) { + mSymbolTable->mAssets.push_back(std::move(assetManager)); + return *this; + } + + std::unique_ptr<ISymbolTable> build() { + return std::move(mSymbolTable); + } +}; + +class JoinedSymbolTableBuilder { +private: + struct JoinedSymbolTable : public ISymbolTable { + std::vector<std::unique_ptr<ISymbolTable>> mSymbolTables; + + const Symbol* findByName(const ResourceName& name) override; + const Symbol* findById(ResourceId id) override; + }; + + std::unique_ptr<JoinedSymbolTable> mSymbolTable = util::make_unique<JoinedSymbolTable>(); + +public: + JoinedSymbolTableBuilder& addSymbolTable(std::unique_ptr<ISymbolTable> table) { + mSymbolTable->mSymbolTables.push_back(std::move(table)); + return *this; + } + + std::unique_ptr<ISymbolTable> build() { + return std::move(mSymbolTable); + } +}; + +} // namespace aapt + +#endif /* AAPT_PROCESS_SYMBOLTABLE_H */ diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp new file mode 100644 index 000000000000..1dc3b4fe4e4a --- /dev/null +++ b/tools/aapt2/process/SymbolTable_test.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "process/SymbolTable.h" +#include "test/Builders.h" +#include "test/Context.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(SymbolTableWrapperTest, FindSymbolsWithIds) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addSimple(u"@android:id/foo", ResourceId(0x01020000)) + .addSimple(u"@android:id/bar") + .addValue(u"@android:attr/foo", ResourceId(0x01010000), + test::AttributeBuilder().build()) + .build(); + + SymbolTableWrapper symbolTable(table.get()); + EXPECT_NE(symbolTable.findByName(test::parseNameOrDie(u"@android:id/foo")), nullptr); + EXPECT_EQ(symbolTable.findByName(test::parseNameOrDie(u"@android:id/bar")), nullptr); + + const ISymbolTable::Symbol* s = symbolTable.findByName( + test::parseNameOrDie(u"@android:attr/foo")); + ASSERT_NE(s, nullptr); + EXPECT_NE(s->attribute, nullptr); +} + +TEST(SymbolTableWrapperTest, FindPrivateAttrSymbol) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addValue(u"@android:^attr-private/foo", ResourceId(0x01010000), + test::AttributeBuilder().build()) + .build(); + + SymbolTableWrapper symbolTable(table.get()); + const ISymbolTable::Symbol* s = symbolTable.findByName( + test::parseNameOrDie(u"@android:attr/foo")); + ASSERT_NE(s, nullptr); + EXPECT_NE(s->attribute, nullptr); +} + +} // namespace aapt diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h new file mode 100644 index 000000000000..0d8d8b5d49aa --- /dev/null +++ b/tools/aapt2/test/Builders.h @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_TEST_BUILDERS_H +#define AAPT_TEST_BUILDERS_H + +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "util/Util.h" +#include "XmlDom.h" + +#include "test/Common.h" + +#include <memory> + +namespace aapt { +namespace test { + +class ResourceTableBuilder { +private: + DummyDiagnosticsImpl mDiagnostics; + std::unique_ptr<ResourceTable> mTable = util::make_unique<ResourceTable>(); + +public: + ResourceTableBuilder() = default; + + ResourceTableBuilder& setPackageId(const StringPiece16& packageName, uint8_t id) { + ResourceTablePackage* package = mTable->createPackage(packageName, id); + assert(package); + return *this; + } + + ResourceTableBuilder& addSimple(const StringPiece16& name, ResourceId id = {}) { + return addValue(name, id, util::make_unique<Id>()); + } + + ResourceTableBuilder& addReference(const StringPiece16& name, const StringPiece16& ref) { + return addReference(name, {}, ref); + } + + ResourceTableBuilder& addReference(const StringPiece16& name, ResourceId id, + const StringPiece16& ref) { + return addValue(name, id, util::make_unique<Reference>(parseNameOrDie(ref))); + } + + ResourceTableBuilder& addString(const StringPiece16& name, const StringPiece16& str) { + return addString(name, {}, str); + } + + ResourceTableBuilder& addString(const StringPiece16& name, ResourceId id, + const StringPiece16& str) { + return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str))); + } + + ResourceTableBuilder& addFileReference(const StringPiece16& name, const StringPiece16& path) { + return addFileReference(name, {}, path); + } + + ResourceTableBuilder& addFileReference(const StringPiece16& name, ResourceId id, + const StringPiece16& path) { + return addValue(name, id, + util::make_unique<FileReference>(mTable->stringPool.makeRef(path))); + } + + + ResourceTableBuilder& addValue(const StringPiece16& name, std::unique_ptr<Value> value) { + return addValue(name, {}, std::move(value)); + } + + ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id, + std::unique_ptr<Value> value) { + return addValue(name, id, {}, std::move(value)); + } + + ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id, + const ConfigDescription& config, std::unique_ptr<Value> value) { + bool result = mTable->addResource(parseNameOrDie(name), id, config, {}, std::move(value), + &mDiagnostics); + assert(result); + return *this; + } + + std::unique_ptr<ResourceTable> build() { + return std::move(mTable); + } +}; + +inline std::unique_ptr<Reference> buildReference(const StringPiece16& ref, + Maybe<ResourceId> id = {}) { + std::unique_ptr<Reference> reference = util::make_unique<Reference>(parseNameOrDie(ref)); + reference->id = id; + return reference; +} + +class AttributeBuilder { +private: + std::unique_ptr<Attribute> mAttr; + +public: + AttributeBuilder(bool weak = false) : mAttr(util::make_unique<Attribute>(weak)) { + mAttr->typeMask = android::ResTable_map::TYPE_ANY; + } + + AttributeBuilder& setTypeMask(uint32_t typeMask) { + mAttr->typeMask = typeMask; + return *this; + } + + AttributeBuilder& addItem(const StringPiece16& name, uint32_t value) { + mAttr->symbols.push_back(Attribute::Symbol{ + Reference(ResourceName{ {}, ResourceType::kId, name.toString()}), + value}); + return *this; + } + + std::unique_ptr<Attribute> build() { + return std::move(mAttr); + } +}; + +class StyleBuilder { +private: + std::unique_ptr<Style> mStyle = util::make_unique<Style>(); + +public: + StyleBuilder& setParent(const StringPiece16& str) { + mStyle->parent = Reference(parseNameOrDie(str)); + return *this; + } + + StyleBuilder& addItem(const StringPiece16& str, std::unique_ptr<Item> value) { + mStyle->entries.push_back(Style::Entry{ Reference(parseNameOrDie(str)), std::move(value) }); + return *this; + } + + StyleBuilder& addItem(const StringPiece16& str, ResourceId id, std::unique_ptr<Item> value) { + addItem(str, std::move(value)); + mStyle->entries.back().key.id = id; + return *this; + } + + std::unique_ptr<Style> build() { + return std::move(mStyle); + } +}; + +class StyleableBuilder { +private: + std::unique_ptr<Styleable> mStyleable = util::make_unique<Styleable>(); + +public: + StyleableBuilder& addItem(const StringPiece16& str, Maybe<ResourceId> id = {}) { + mStyleable->entries.push_back(Reference(parseNameOrDie(str))); + mStyleable->entries.back().id = id; + return *this; + } + + std::unique_ptr<Styleable> build() { + return std::move(mStyleable); + } +}; + +inline std::unique_ptr<XmlResource> buildXmlDom(const StringPiece& str) { + std::stringstream in; + in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str; + StdErrDiagnostics diag; + std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, {}); + assert(doc); + return doc; +} + +} // namespace test +} // namespace aapt + +#endif /* AAPT_TEST_BUILDERS_H */ diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h new file mode 100644 index 000000000000..b41c5683c77a --- /dev/null +++ b/tools/aapt2/test/Common.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_TEST_COMMON_H +#define AAPT_TEST_COMMON_H + +#include "ConfigDescription.h" +#include "ResourceTable.h" +#include "ResourceUtils.h" +#include "ValueVisitor.h" + +#include "process/IResourceTableConsumer.h" +#include "util/StringPiece.h" + +#include <gtest/gtest.h> +#include <iostream> + +// +// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to fail to compile. +// +#define AAPT_ASSERT_TRUE(v) ASSERT_TRUE(bool(v)) +#define AAPT_ASSERT_FALSE(v) ASSERT_FALSE(bool(v)) +#define AAPT_EXPECT_TRUE(v) EXPECT_TRUE(bool(v)) +#define AAPT_EXPECT_FALSE(v) EXPECT_FALSE(bool(v)) + +namespace aapt { +namespace test { + +struct DummyDiagnosticsImpl : public IDiagnostics { + void error(const DiagMessage& message) override { + DiagMessageActual actual = message.build(); + std::cerr << actual.source << ": error: " << actual.message << "." << std::endl; + } + void warn(const DiagMessage& message) override { + DiagMessageActual actual = message.build(); + std::cerr << actual.source << ": warn: " << actual.message << "." << std::endl; + } + void note(const DiagMessage& message) override {} +}; + +inline ResourceName parseNameOrDie(const StringPiece16& str) { + ResourceNameRef ref; + bool result = ResourceUtils::tryParseReference(str, &ref); + assert(result && "invalid resource name"); + return ref.toResourceName(); +} + +inline ConfigDescription parseConfigOrDie(const StringPiece& str) { + ConfigDescription config; + bool result = ConfigDescription::parse(str, &config); + assert(result && "invalid configuration"); + return config; +} + +template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece16& resName, + const ConfigDescription& config) { + Maybe<ResourceTable::SearchResult> result = table->findResource(parseNameOrDie(resName)); + if (result) { + ResourceEntry* entry = result.value().entry; + auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config, + [](const ResourceConfigValue& a, const ConfigDescription& b) + -> bool { + return a.config < b; + }); + if (iter != entry->values.end() && iter->config == config) { + return valueCast<T>(iter->value.get()); + } + } + return nullptr; +} + +template <typename T> T* getValue(ResourceTable* table, const StringPiece16& resName) { + return getValueForConfig<T>(table, resName, {}); +} + +} // namespace test +} // namespace aapt + +#endif /* AAPT_TEST_COMMON_H */ diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h new file mode 100644 index 000000000000..4fa49186a09f --- /dev/null +++ b/tools/aapt2/test/Context.h @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_TEST_CONTEXT_H +#define AAPT_TEST_CONTEXT_H + +#include "NameMangler.h" +#include "util/Util.h" + +#include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" +#include "test/Common.h" + +#include <cassert> +#include <list> + +namespace aapt { +namespace test { + +class Context : public IAaptContext { +private: + friend class ContextBuilder; + + Context() = default; + + Maybe<std::u16string> mCompilationPackage; + Maybe<uint8_t> mPackageId; + std::unique_ptr<IDiagnostics> mDiagnostics = util::make_unique<StdErrDiagnostics>(); + std::unique_ptr<ISymbolTable> mSymbols; + std::unique_ptr<NameMangler> mNameMangler; + +public: + ISymbolTable* getExternalSymbols() override { + assert(mSymbols && "test symbols not set"); + return mSymbols.get(); + } + + void setSymbolTable(std::unique_ptr<ISymbolTable> symbols) { + mSymbols = std::move(symbols); + } + + IDiagnostics* getDiagnostics() override { + assert(mDiagnostics && "test diagnostics not set"); + return mDiagnostics.get(); + } + + StringPiece16 getCompilationPackage() override { + assert(mCompilationPackage && "package name not set"); + return mCompilationPackage.value(); + } + + uint8_t getPackageId() override { + assert(mPackageId && "package ID not set"); + return mPackageId.value(); + } + + NameMangler* getNameMangler() override { + assert(mNameMangler && "test name mangler not set"); + return mNameMangler.get(); + } +}; + +class ContextBuilder { +private: + std::unique_ptr<Context> mContext = std::unique_ptr<Context>(new Context()); + +public: + ContextBuilder& setCompilationPackage(const StringPiece16& package) { + mContext->mCompilationPackage = package.toString(); + return *this; + } + + ContextBuilder& setPackageId(uint8_t id) { + mContext->mPackageId = id; + return *this; + } + + ContextBuilder& setSymbolTable(std::unique_ptr<ISymbolTable> symbols) { + mContext->mSymbols = std::move(symbols); + return *this; + } + + ContextBuilder& setDiagnostics(std::unique_ptr<IDiagnostics> diag) { + mContext->mDiagnostics = std::move(diag); + return *this; + } + + ContextBuilder& setNameManglerPolicy(NameManglerPolicy policy) { + mContext->mNameMangler = util::make_unique<NameMangler>(policy); + return *this; + } + + std::unique_ptr<Context> build() { + return std::move(mContext); + } +}; + +class StaticSymbolTableBuilder { +private: + struct SymbolTable : public ISymbolTable { + std::list<std::unique_ptr<Symbol>> mSymbols; + std::map<ResourceName, Symbol*> mNameMap; + std::map<ResourceId, Symbol*> mIdMap; + + const Symbol* findByName(const ResourceName& name) override { + auto iter = mNameMap.find(name); + if (iter != mNameMap.end()) { + return iter->second; + } + return nullptr; + } + + const Symbol* findById(ResourceId id) override { + auto iter = mIdMap.find(id); + if (iter != mIdMap.end()) { + return iter->second; + } + return nullptr; + } + }; + + std::unique_ptr<SymbolTable> mSymbolTable = util::make_unique<SymbolTable>(); + +public: + StaticSymbolTableBuilder& addSymbol(const StringPiece16& name, ResourceId id, + std::unique_ptr<Attribute> attr = {}) { + std::unique_ptr<ISymbolTable::Symbol> symbol = util::make_unique<ISymbolTable::Symbol>( + id, std::move(attr)); + mSymbolTable->mNameMap[parseNameOrDie(name)] = symbol.get(); + mSymbolTable->mIdMap[id] = symbol.get(); + mSymbolTable->mSymbols.push_back(std::move(symbol)); + return *this; + } + + std::unique_ptr<ISymbolTable> build() { + return std::move(mSymbolTable); + } +}; + +} // namespace test +} // namespace aapt + +#endif /* AAPT_TEST_CONTEXT_H */ diff --git a/tools/aapt2/todo.txt b/tools/aapt2/todo.txt deleted file mode 100644 index acc8bfbcc9e5..000000000000 --- a/tools/aapt2/todo.txt +++ /dev/null @@ -1,29 +0,0 @@ -XML Files -X Collect declared IDs -X Build StringPool -X Flatten - -Resource Table Operations -X Build Resource Table (with StringPool) from XML. -X Modify Resource Table. -X - Copy and transform resources. -X - Pre-17/21 attr correction. -X Perform analysis of types. -X Flatten. -X Assign resource IDs. -X Assign public resource IDs. -X Merge resource tables -- Assign private attributes to different typespace. -- Align resource tables - -Splits -- Collect all resources (ids from layouts). -- Generate resource table from base resources. -- Generate resource table from individual resources of the required type. -- Align resource tables (same type/name = same ID). - -Fat Apk -X Collect all resources (ids from layouts). -X Generate resource tables for all configurations. -- Align individual resource tables. -- Merge resource tables. diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index 4f1947ab8364..992a45cb34f4 100644 --- a/tools/aapt2/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -14,19 +14,21 @@ * limitations under the License. */ -#include "BinaryResourceParser.h" -#include "Logger.h" -#include "ResChunkPullParser.h" -#include "Resolver.h" -#include "ResourceParser.h" #include "ResourceTable.h" -#include "ResourceTypeExtensions.h" +#include "ResourceUtils.h" #include "ResourceValues.h" #include "Source.h" -#include "Util.h" +#include "ValueVisitor.h" + +#include "flatten/ResourceTypeExtensions.h" +#include "unflatten/BinaryResourceParser.h" +#include "unflatten/ResChunkPullParser.h" +#include "util/Util.h" #include <androidfw/ResourceTypes.h> #include <androidfw/TypeWrappers.h> +#include <utils/misc.h> + #include <map> #include <string> @@ -38,89 +40,35 @@ using namespace android; * Visitor that converts a reference's resource ID to a resource name, * given a mapping from resource ID to resource name. */ -struct ReferenceIdToNameVisitor : ValueVisitor { - ReferenceIdToNameVisitor(const std::shared_ptr<IResolver>& resolver, - std::map<ResourceId, ResourceName>* cache) : - mResolver(resolver), mCache(cache) { - } - - void visit(Reference& reference, ValueVisitorArgs&) override { - idToName(reference); - } - - void visit(Attribute& attr, ValueVisitorArgs&) override { - for (auto& entry : attr.symbols) { - idToName(entry.symbol); - } - } - - void visit(Style& style, ValueVisitorArgs&) override { - if (style.parent.id.isValid()) { - idToName(style.parent); - } - - for (auto& entry : style.entries) { - idToName(entry.key); - entry.value->accept(*this, {}); - } - } - - void visit(Styleable& styleable, ValueVisitorArgs&) override { - for (auto& attr : styleable.entries) { - idToName(attr); - } - } +class ReferenceIdToNameVisitor : public ValueVisitor { +private: + const std::map<ResourceId, ResourceName>* mMapping; - void visit(Array& array, ValueVisitorArgs&) override { - for (auto& item : array.items) { - item->accept(*this, {}); - } - } +public: + using ValueVisitor::visit; - void visit(Plural& plural, ValueVisitorArgs&) override { - for (auto& item : plural.values) { - if (item) { - item->accept(*this, {}); - } - } + ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) : + mMapping(mapping) { + assert(mMapping); } -private: - void idToName(Reference& reference) { - if (!reference.id.isValid()) { + void visit(Reference* reference) override { + if (!reference->id || !reference->id.value().isValid()) { return; } - auto cacheIter = mCache->find(reference.id); - if (cacheIter != mCache->end()) { - reference.name = cacheIter->second; - reference.id = 0; - } else { - Maybe<ResourceName> result = mResolver->findName(reference.id); - if (result) { - reference.name = result.value(); - - // Add to cache. - mCache->insert({reference.id, reference.name}); - - reference.id = 0; - } + ResourceId id = reference->id.value(); + auto cacheIter = mMapping->find(id); + if (cacheIter != mMapping->end()) { + reference->name = cacheIter->second; + reference->id = {}; } } - - std::shared_ptr<IResolver> mResolver; - std::map<ResourceId, ResourceName>* mCache; }; - -BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver, - const Source& source, - const std::u16string& defaultPackage, - const void* data, - size_t len) : - mTable(table), mResolver(resolver), mSource(source), mDefaultPackage(defaultPackage), - mData(data), mDataLen(len) { +BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table, + const Source& source, const void* data, size_t len) : + mContext(context), mTable(table), mSource(source), mData(data), mDataLen(len) { } bool BinaryResourceParser::parse() { @@ -129,23 +77,21 @@ bool BinaryResourceParser::parse() { bool error = false; while(ResChunkPullParser::isGoodEvent(parser.next())) { if (parser.getChunk()->type != android::RES_TABLE_TYPE) { - Logger::warn(mSource) - << "unknown chunk of type '" - << parser.getChunk()->type - << "'." - << std::endl; + mContext->getDiagnostics()->warn(DiagMessage(mSource) + << "unknown chunk of type '" + << (int) parser.getChunk()->type << "'"); continue; } - error |= !parseTable(parser.getChunk()); + if (!parseTable(parser.getChunk())) { + error = true; + } } if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { - Logger::error(mSource) - << "bad document: " - << parser.getLastError() - << "." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt resource table: " + << parser.getLastError()); return false; } return !error; @@ -156,32 +102,30 @@ bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbo return false; } - if (reinterpret_cast<uintptr_t>(data) < reinterpret_cast<uintptr_t>(mData)) { + if ((uintptr_t) data < (uintptr_t) mData) { return false; } // We only support 32 bit offsets right now. - const uintptr_t offset = reinterpret_cast<uintptr_t>(data) - - reinterpret_cast<uintptr_t>(mData); + const uintptr_t offset = (uintptr_t) data - (uintptr_t) mData; if (offset > std::numeric_limits<uint32_t>::max()) { return false; } for (size_t i = 0; i < mSymbolEntryCount; i++) { - if (mSymbolEntries[i].offset == offset) { + if (util::deviceToHost32(mSymbolEntries[i].offset) == offset) { // This offset is a symbol! - const StringPiece16 str = util::getString(mSymbolPool, - mSymbolEntries[i].stringIndex); + const StringPiece16 str = util::getString( + mSymbolPool, util::deviceToHost32(mSymbolEntries[i].stringIndex)); + StringPiece16 typeStr; - ResourceParser::extractResourceName(str, &outSymbol->package, &typeStr, - &outSymbol->entry); + ResourceUtils::extractResourceName(str, &outSymbol->package, &typeStr, + &outSymbol->entry); const ResourceType* type = parseResourceType(typeStr); if (!type) { return false; } - if (outSymbol->package.empty()) { - outSymbol->package = mTable->getPackage(); - } + outSymbol->type = *type; // Since we scan the symbol table in order, we can start looking for the @@ -194,88 +138,96 @@ bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbo return false; } +/** + * Parses the SymbolTable_header, which is present on non-final resource tables + * after the compile phase. + * + * | SymbolTable_header | + * |--------------------| + * |SymbolTable_entry 0 | + * |SymbolTable_entry 1 | + * | ... | + * |SymbolTable_entry n | + * |--------------------| + * + */ bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) { - const SymbolTable_header* symbolTableHeader = convertTo<SymbolTable_header>(chunk); - if (!symbolTableHeader) { - Logger::error(mSource) - << "could not parse chunk as SymbolTable_header." - << std::endl; + const SymbolTable_header* header = convertTo<SymbolTable_header>(chunk); + if (!header) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt SymbolTable_header"); return false; } - const size_t entrySizeBytes = symbolTableHeader->count * sizeof(SymbolTable_entry); - if (entrySizeBytes > getChunkDataLen(symbolTableHeader->header)) { - Logger::error(mSource) - << "entries extend beyond chunk." - << std::endl; + const uint32_t entrySizeBytes = + util::deviceToHost32(header->count) * sizeof(SymbolTable_entry); + if (entrySizeBytes > getChunkDataLen(&header->header)) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "SymbolTable_header data section too long"); return false; } - mSymbolEntries = reinterpret_cast<const SymbolTable_entry*>( - getChunkData(symbolTableHeader->header)); - mSymbolEntryCount = symbolTableHeader->count; + mSymbolEntries = (const SymbolTable_entry*) getChunkData(&header->header); + mSymbolEntryCount = util::deviceToHost32(header->count); - ResChunkPullParser parser(getChunkData(symbolTableHeader->header) + entrySizeBytes, - getChunkDataLen(symbolTableHeader->header) - entrySizeBytes); + // Skip over the symbol entries and parse the StringPool chunk that should be next. + ResChunkPullParser parser(getChunkData(&header->header) + entrySizeBytes, + getChunkDataLen(&header->header) - entrySizeBytes); if (!ResChunkPullParser::isGoodEvent(parser.next())) { - Logger::error(mSource) - << "failed to parse chunk: " - << parser.getLastError() - << "." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "failed to parse chunk in SymbolTable: " + << parser.getLastError()); return false; } - if (parser.getChunk()->type != android::RES_STRING_POOL_TYPE) { - Logger::error(mSource) - << "expected Symbol string pool." - << std::endl; + const ResChunk_header* nextChunk = parser.getChunk(); + if (util::deviceToHost16(nextChunk->type) != android::RES_STRING_POOL_TYPE) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "expected string pool in SymbolTable but got " + << "chunk of type " + << (int) util::deviceToHost16(nextChunk->type)); return false; } - if (mSymbolPool.setTo(parser.getChunk(), parser.getChunk()->size) != NO_ERROR) { - Logger::error(mSource) - << "failed to parse symbol string pool with code: " - << mSymbolPool.getError() - << "." - << std::endl; + if (mSymbolPool.setTo(nextChunk, util::deviceToHost32(nextChunk->size)) != NO_ERROR) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt string pool in SymbolTable: " + << mSymbolPool.getError()); return false; } return true; } +/** + * Parses the resource table, which contains all the packages, types, and entries. + */ bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) { const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk); if (!tableHeader) { - Logger::error(mSource) - << "could not parse chunk as ResTable_header." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) << "corrupt ResTable_header chunk"); return false; } - ResChunkPullParser parser(getChunkData(tableHeader->header), - getChunkDataLen(tableHeader->header)); + ResChunkPullParser parser(getChunkData(&tableHeader->header), + getChunkDataLen(&tableHeader->header)); while (ResChunkPullParser::isGoodEvent(parser.next())) { - switch (parser.getChunk()->type) { + switch (util::deviceToHost16(parser.getChunk()->type)) { case android::RES_STRING_POOL_TYPE: if (mValuePool.getError() == NO_INIT) { - if (mValuePool.setTo(parser.getChunk(), parser.getChunk()->size) != - NO_ERROR) { - Logger::error(mSource) - << "failed to parse value string pool with code: " - << mValuePool.getError() - << "." - << std::endl; + status_t err = mValuePool.setTo(parser.getChunk(), + util::deviceToHost32(parser.getChunk()->size)); + if (err != NO_ERROR) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt string pool in ResTable: " + << mValuePool.getError()); return false; } // Reserve some space for the strings we are going to add. - mTable->getValueStringPool().hintWillAdd( - mValuePool.size(), mValuePool.styleCount()); + mTable->stringPool.hintWillAdd(mValuePool.size(), mValuePool.styleCount()); } else { - Logger::warn(mSource) - << "unexpected string pool." - << std::endl; + mContext->getDiagnostics()->warn(DiagMessage(mSource) + << "unexpected string pool in ResTable"); } break; @@ -286,13 +238,12 @@ bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) { break; case RES_TABLE_SOURCE_POOL_TYPE: { - if (mSourcePool.setTo(getChunkData(*parser.getChunk()), - getChunkDataLen(*parser.getChunk())) != NO_ERROR) { - Logger::error(mSource) - << "failed to parse source pool with code: " - << mSourcePool.getError() - << "." - << std::endl; + status_t err = mSourcePool.setTo(getChunkData(parser.getChunk()), + getChunkDataLen(parser.getChunk())); + if (err != NO_ERROR) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt source string pool in ResTable: " + << mSourcePool.getError()); return false; } break; @@ -305,102 +256,81 @@ bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) { break; default: - Logger::warn(mSource) - << "unexpected chunk of type " - << parser.getChunk()->type - << "." - << std::endl; + mContext->getDiagnostics() + ->warn(DiagMessage(mSource) + << "unexpected chunk type " + << (int) util::deviceToHost16(parser.getChunk()->type)); break; } } if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { - Logger::error(mSource) - << "bad resource table: " << parser.getLastError() - << "." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt resource table: " << parser.getLastError()); return false; } return true; } -bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { - if (mValuePool.getError() != NO_ERROR) { - Logger::error(mSource) - << "no value string pool for ResTable." - << std::endl; - return false; - } +bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk); if (!packageHeader) { - Logger::error(mSource) - << "could not parse chunk as ResTable_header." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt ResTable_package chunk"); return false; } - if (mTable->getPackageId() == ResourceTable::kUnsetPackageId) { - // This is the first time the table has it's package ID set. - mTable->setPackageId(packageHeader->id); - } else if (mTable->getPackageId() != packageHeader->id) { - Logger::error(mSource) - << "ResTable_package has package ID " - << std::hex << packageHeader->id << std::dec - << " but ResourceTable has package ID " - << std::hex << mTable->getPackageId() << std::dec - << std::endl; + uint32_t packageId = util::deviceToHost32(packageHeader->id); + if (packageId > std::numeric_limits<uint8_t>::max()) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "package ID is too big (" << packageId << ")"); return false; } - size_t len = strnlen16(reinterpret_cast<const char16_t*>(packageHeader->name), - sizeof(packageHeader->name) / sizeof(packageHeader->name[0])); - if (mTable->getPackage().empty() && len == 0) { - mTable->setPackage(mDefaultPackage); - } else if (len > 0) { - StringPiece16 thisPackage(reinterpret_cast<const char16_t*>(packageHeader->name), len); - if (mTable->getPackage().empty()) { - mTable->setPackage(thisPackage); - } else if (thisPackage != mTable->getPackage()) { - Logger::error(mSource) - << "incompatible packages: " - << mTable->getPackage() - << " vs. " - << thisPackage - << std::endl; - return false; - } + // Extract the package name. + size_t len = strnlen16((const char16_t*) packageHeader->name, NELEM(packageHeader->name)); + std::u16string packageName; + packageName.resize(len); + for (size_t i = 0; i < len; i++) { + packageName[i] = util::deviceToHost16(packageHeader->name[i]); + } + + ResourceTablePackage* package = mTable->createPackage(packageName, (uint8_t) packageId); + if (!package) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "incompatible package '" << packageName + << "' with ID " << packageId); + return false; } - ResChunkPullParser parser(getChunkData(packageHeader->header), - getChunkDataLen(packageHeader->header)); + ResChunkPullParser parser(getChunkData(&packageHeader->header), + getChunkDataLen(&packageHeader->header)); while (ResChunkPullParser::isGoodEvent(parser.next())) { - switch (parser.getChunk()->type) { + switch (util::deviceToHost16(parser.getChunk()->type)) { case android::RES_STRING_POOL_TYPE: if (mTypePool.getError() == NO_INIT) { - if (mTypePool.setTo(parser.getChunk(), parser.getChunk()->size) != - NO_ERROR) { - Logger::error(mSource) - << "failed to parse type string pool with code " - << mTypePool.getError() - << "." - << std::endl; + status_t err = mTypePool.setTo(parser.getChunk(), + util::deviceToHost32(parser.getChunk()->size)); + if (err != NO_ERROR) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt type string pool in " + << "ResTable_package: " + << mTypePool.getError()); return false; } } else if (mKeyPool.getError() == NO_INIT) { - if (mKeyPool.setTo(parser.getChunk(), parser.getChunk()->size) != - NO_ERROR) { - Logger::error(mSource) - << "failed to parse key string pool with code " - << mKeyPool.getError() - << "." - << std::endl; + status_t err = mKeyPool.setTo(parser.getChunk(), + util::deviceToHost32(parser.getChunk()->size)); + if (err != NO_ERROR) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt key string pool in " + << "ResTable_package: " + << mKeyPool.getError()); return false; } } else { - Logger::warn(mSource) - << "unexpected string pool." - << std::endl; + mContext->getDiagnostics()->warn(DiagMessage(mSource) << "unexpected string pool"); } break; @@ -411,91 +341,97 @@ bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { break; case android::RES_TABLE_TYPE_TYPE: - if (!parseType(parser.getChunk())) { + if (!parseType(package, parser.getChunk())) { return false; } break; case RES_TABLE_PUBLIC_TYPE: - if (!parsePublic(parser.getChunk())) { + if (!parsePublic(package, parser.getChunk())) { return false; } break; default: - Logger::warn(mSource) - << "unexpected chunk of type " - << parser.getChunk()->type - << "." - << std::endl; + mContext->getDiagnostics() + ->warn(DiagMessage(mSource) + << "unexpected chunk type " + << (int) util::deviceToHost16(parser.getChunk()->type)); break; } } if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { - Logger::error(mSource) - << "bad package: " - << parser.getLastError() - << "." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt ResTable_package: " + << parser.getLastError()); return false; } - // Now go through the table and change resource ID references to + // Now go through the table and change local resource ID references to // symbolic references. - - ReferenceIdToNameVisitor visitor(mResolver, &mIdIndex); - for (auto& type : *mTable) { - for (auto& entry : type->entries) { - for (auto& configValue : entry->values) { - configValue.value->accept(visitor, {}); + ReferenceIdToNameVisitor visitor(&mIdIndex); + for (auto& package : mTable->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + for (auto& configValue : entry->values) { + configValue.value->accept(&visitor); + } } } } return true; } -bool BinaryResourceParser::parsePublic(const ResChunk_header* chunk) { +bool BinaryResourceParser::parsePublic(const ResourceTablePackage* package, + const ResChunk_header* chunk) { const Public_header* header = convertTo<Public_header>(chunk); + if (!header) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt Public_header chunk"); + return false; + } if (header->typeId == 0) { - Logger::error(mSource) - << "invalid type ID " << header->typeId << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "invalid type ID " + << (int) header->typeId); return false; } - const ResourceType* parsedType = parseResourceType(util::getString(mTypePool, - header->typeId - 1)); + StringPiece16 typeStr16 = util::getString(mTypePool, header->typeId - 1); + const ResourceType* parsedType = parseResourceType(typeStr16); if (!parsedType) { - Logger::error(mSource) - << "invalid type " << util::getString(mTypePool, header->typeId - 1) << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "invalid type '" << typeStr16 << "'"); return false; } - const uintptr_t chunkEnd = reinterpret_cast<uintptr_t>(chunk) + chunk->size; - const Public_entry* entry = reinterpret_cast<const Public_entry*>( - getChunkData(header->header)); - for (uint32_t i = 0; i < header->count; i++) { - if (reinterpret_cast<uintptr_t>(entry) + sizeof(*entry) > chunkEnd) { - Logger::error(mSource) - << "Public_entry extends beyond chunk." - << std::endl; + const uintptr_t chunkEnd = (uintptr_t) chunk + util::deviceToHost32(chunk->size); + const Public_entry* entry = (const Public_entry*) getChunkData(&header->header); + for (uint32_t i = 0; i < util::deviceToHost32(header->count); i++) { + if ((uintptr_t) entry + sizeof(*entry) > chunkEnd) { + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "Public_entry data section is too long"); return false; } - const ResourceId resId = { mTable->getPackageId(), header->typeId, entry->entryId }; + const ResourceId resId = { + package->id.value(), header->typeId, util::deviceToHost16(entry->entryId) }; + const ResourceName name = { - mTable->getPackage(), + package->name, *parsedType, util::getString(mKeyPool, entry->key.index).toString() }; - SourceLine source; + Source source; if (mSourcePool.getError() == NO_ERROR) { - source.path = util::utf16ToUtf8(util::getString(mSourcePool, entry->source.index)); - source.line = entry->sourceLine; + source.path = util::utf16ToUtf8(util::getString( + mSourcePool, util::deviceToHost32(entry->source.index))); + source.line = util::deviceToHost32(entry->sourceLine); } - if (!mTable->markPublicAllowMangled(name, resId, source)) { + if (!mTable->markPublicAllowMangled(name, resId, source, mContext->getDiagnostics())) { return false; } @@ -513,137 +449,127 @@ bool BinaryResourceParser::parsePublic(const ResChunk_header* chunk) { bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) { if (mTypePool.getError() != NO_ERROR) { - Logger::error(mSource) - << "no type string pool available for ResTable_typeSpec." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "missing type string pool"); return false; } const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk); if (!typeSpec) { - Logger::error(mSource) - << "could not parse chunk as ResTable_typeSpec." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt ResTable_typeSpec chunk"); return false; } if (typeSpec->id == 0) { - Logger::error(mSource) - << "ResTable_typeSpec has invalid id: " - << typeSpec->id - << "." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "ResTable_typeSpec has invalid id: " << typeSpec->id); return false; } return true; } -bool BinaryResourceParser::parseType(const ResChunk_header* chunk) { +bool BinaryResourceParser::parseType(const ResourceTablePackage* package, + const ResChunk_header* chunk) { if (mTypePool.getError() != NO_ERROR) { - Logger::error(mSource) - << "no type string pool available for ResTable_typeSpec." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "missing type string pool"); return false; } if (mKeyPool.getError() != NO_ERROR) { - Logger::error(mSource) - << "no key string pool available for ResTable_type." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "missing key string pool"); return false; } const ResTable_type* type = convertTo<ResTable_type>(chunk); if (!type) { - Logger::error(mSource) - << "could not parse chunk as ResTable_type." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "corrupt ResTable_type chunk"); return false; } if (type->id == 0) { - Logger::error(mSource) - << "ResTable_type has invalid id: " - << type->id - << "." - << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "ResTable_type has invalid id: " << (int) type->id); return false; } - const ConfigDescription config(type->config); - const StringPiece16 typeName = util::getString(mTypePool, type->id - 1); + ConfigDescription config; + config.copyFromDtoH(type->config); - const ResourceType* parsedType = parseResourceType(typeName); + StringPiece16 typeStr16 = util::getString(mTypePool, type->id - 1); + + const ResourceType* parsedType = parseResourceType(typeStr16); if (!parsedType) { - Logger::error(mSource) - << "invalid type name '" - << typeName - << "' for type with ID " - << uint32_t(type->id) - << "." << std::endl; + mContext->getDiagnostics()->error(DiagMessage(mSource) + << "invalid type name '" << typeStr16 + << "' for type with ID " << (int) type->id); return false; } - android::TypeVariant tv(type); + TypeVariant tv(type); for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) { - if (!*it) { + const ResTable_entry* entry = *it; + if (!entry) { continue; } - const ResTable_entry* entry = *it; const ResourceName name = { - mTable->getPackage(), + package->name, *parsedType, - util::getString(mKeyPool, entry->key.index).toString() - }; + util::getString(mKeyPool, util::deviceToHost32(entry->key.index)).toString() }; - const ResourceId resId = { mTable->getPackageId(), type->id, it.index() }; + const ResourceId resId = + { package->id.value(), type->id, static_cast<uint16_t>(it.index()) }; std::unique_ptr<Value> resourceValue; const ResTable_entry_source* sourceBlock = nullptr; + if (entry->flags & ResTable_entry::FLAG_COMPLEX) { const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry); - if (mapEntry->size - sizeof(*mapEntry) == sizeof(*sourceBlock)) { - const uint8_t* data = reinterpret_cast<const uint8_t*>(mapEntry); - data += mapEntry->size - sizeof(*sourceBlock); - sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data); + if (util::deviceToHost32(mapEntry->size) - sizeof(*mapEntry) == sizeof(*sourceBlock)) { + const uint8_t* data = (const uint8_t*) mapEntry; + data += util::deviceToHost32(mapEntry->size) - sizeof(*sourceBlock); + sourceBlock = (const ResTable_entry_source*) data; } // TODO(adamlesinski): Check that the entry count is valid. resourceValue = parseMapEntry(name, config, mapEntry); } else { - if (entry->size - sizeof(*entry) == sizeof(*sourceBlock)) { - const uint8_t* data = reinterpret_cast<const uint8_t*>(entry); - data += entry->size - sizeof(*sourceBlock); - sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data); + if (util::deviceToHost32(entry->size) - sizeof(*entry) == sizeof(*sourceBlock)) { + const uint8_t* data = (const uint8_t*) entry; + data += util::deviceToHost32(entry->size) - sizeof(*sourceBlock); + sourceBlock = (const ResTable_entry_source*) data; } - const Res_value* value = reinterpret_cast<const Res_value*>( - reinterpret_cast<const uint8_t*>(entry) + entry->size); + const Res_value* value = (const Res_value*)( + (const uint8_t*) entry + util::deviceToHost32(entry->size)); resourceValue = parseValue(name, config, value, entry->flags); } - if (!resourceValue) { - // TODO(adamlesinski): For now this is ok, but it really shouldn't be. - continue; - } + assert(resourceValue && "failed to interpret valid resource"); - SourceLine source = mSource.line(0); + Source source = mSource; if (sourceBlock) { size_t len; - const char* str = mSourcePool.string8At(sourceBlock->pathIndex, &len); + const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->pathIndex), + &len); if (str) { source.path.assign(str, len); } - source.line = sourceBlock->line; + source.line = util::deviceToHost32(sourceBlock->line); } - if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue))) { + if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue), + mContext->getDiagnostics())) { return false; } if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) { - if (!mTable->markPublicAllowMangled(name, resId, mSource.line(0))) { + if (!mTable->markPublicAllowMangled(name, resId, mSource.withLine(0), + mContext->getDiagnostics())) { return false; } } @@ -666,10 +592,12 @@ std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& na return util::make_unique<Id>(); } + const uint32_t data = util::deviceToHost32(value->data); + if (value->dataType == Res_value::TYPE_STRING) { - StringPiece16 str = util::getString(mValuePool, value->data); + StringPiece16 str = util::getString(mValuePool, data); - const ResStringPool_span* spans = mValuePool.styleAt(value->data); + const ResStringPool_span* spans = mValuePool.styleAt(data); if (spans != nullptr) { StyleString styleStr = { str.toString() }; while (spans->name.index != ResStringPool_span::END) { @@ -680,22 +608,20 @@ std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& na }); spans++; } - return util::make_unique<StyledString>( - mTable->getValueStringPool().makeRef( - styleStr, StringPool::Context{1, config})); + return util::make_unique<StyledString>(mTable->stringPool.makeRef( + styleStr, StringPool::Context{1, config})); } else { if (name.type != ResourceType::kString && util::stringStartsWith<char16_t>(str, u"res/")) { // This must be a FileReference. - return util::make_unique<FileReference>(mTable->getValueStringPool().makeRef( + return util::make_unique<FileReference>(mTable->stringPool.makeRef( str, StringPool::Context{ 0, config })); } // There are no styles associated with this string, so treat it as // a simple string. - return util::make_unique<String>( - mTable->getValueStringPool().makeRef( - str, StringPool::Context{1, config})); + return util::make_unique<String>(mTable->stringPool.makeRef( + str, StringPool::Context{1, config})); } } @@ -704,9 +630,9 @@ std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& na const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ? Reference::Type::kResource : Reference::Type::kAttribute; - if (value->data != 0) { + if (data != 0) { // This is a normal reference. - return util::make_unique<Reference>(value->data, type); + return util::make_unique<Reference>(data, type); } // This reference has an invalid ID. Check if it is an unresolved symbol. @@ -722,9 +648,8 @@ std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& na } if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) { - return util::make_unique<RawString>( - mTable->getValueStringPool().makeRef(util::getString(mValuePool, value->data), - StringPool::Context{ 1, config })); + return util::make_unique<RawString>(mTable->stringPool.makeRef( + util::getString(mValuePool, data), StringPool::Context{ 1, config })); } // Treat this as a raw binary primitive. @@ -755,23 +680,23 @@ std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& n const ConfigDescription& config, const ResTable_map_entry* map) { std::unique_ptr<Style> style = util::make_unique<Style>(); - if (map->parent.ident == 0) { + if (util::deviceToHost32(map->parent.ident) == 0) { // The parent is either not set or it is an unresolved symbol. // Check to see if it is a symbol. ResourceNameRef symbol; if (getSymbol(&map->parent.ident, &symbol)) { - style->parent.name = symbol.toResourceName(); + style->parent = Reference(symbol.toResourceName()); } } else { // The parent is a regular reference to a resource. - style->parent.id = map->parent.ident; + style->parent = Reference(util::deviceToHost32(map->parent.ident)); } for (const ResTable_map& mapEntry : map) { style->entries.emplace_back(); Style::Entry& styleEntry = style->entries.back(); - if (mapEntry.name.ident == 0) { + if (util::deviceToHost32(mapEntry.name.ident) == 0) { // The map entry's key (attribute) is not set. This must be // a symbol reference, so resolve it. ResourceNameRef symbol; @@ -780,7 +705,7 @@ std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& n styleEntry.key.name = symbol.toResourceName(); } else { // The map entry's key (attribute) is a regular reference. - styleEntry.key.id = mapEntry.name.ident; + styleEntry.key.id = ResourceId(util::deviceToHost32(mapEntry.name.ident)); } // Parse the attribute's value. @@ -793,27 +718,27 @@ std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& n std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { - const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0; + const bool isWeak = (util::deviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0; std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak); // First we must discover what type of attribute this is. Find the type mask. auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool { - return entry.name.ident == ResTable_map::ATTR_TYPE; + return util::deviceToHost32(entry.name.ident) == ResTable_map::ATTR_TYPE; }); if (typeMaskIter != end(map)) { - attr->typeMask = typeMaskIter->value.data; + attr->typeMask = util::deviceToHost32(typeMaskIter->value.data); } if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) { for (const ResTable_map& mapEntry : map) { - if (Res_INTERNALID(mapEntry.name.ident)) { + if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) { continue; } Attribute::Symbol symbol; - symbol.value = mapEntry.value.data; - if (mapEntry.name.ident == 0) { + symbol.value = util::deviceToHost32(mapEntry.value.data); + if (util::deviceToHost32(mapEntry.name.ident) == 0) { // The map entry's key (id) is not set. This must be // a symbol reference, so resolve it. ResourceNameRef symbolName; @@ -822,7 +747,7 @@ std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef symbol.symbol.name = symbolName.toResourceName(); } else { // The map entry's key (id) is a regular reference. - symbol.symbol.id = mapEntry.name.ident; + symbol.symbol.id = ResourceId(util::deviceToHost32(mapEntry.name.ident)); } attr->symbols.push_back(std::move(symbol)); @@ -848,7 +773,7 @@ std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNa const ResTable_map_entry* map) { std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); for (const ResTable_map& mapEntry : map) { - if (mapEntry.name.ident == 0) { + if (util::deviceToHost32(mapEntry.name.ident) == 0) { // The map entry's key (attribute) is not set. This must be // a symbol reference, so resolve it. ResourceNameRef symbol; @@ -857,7 +782,7 @@ std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNa styleable->entries.emplace_back(symbol); } else { // The map entry's key (attribute) is a regular reference. - styleable->entries.emplace_back(mapEntry.name.ident); + styleable->entries.emplace_back(util::deviceToHost32(mapEntry.name.ident)); } } return styleable; @@ -870,7 +795,7 @@ std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& for (const ResTable_map& mapEntry : map) { std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0); - switch (mapEntry.name.ident) { + switch (util::deviceToHost32(mapEntry.name.ident)) { case android::ResTable_map::ATTR_ZERO: plural->values[Plural::Zero] = std::move(item); break; diff --git a/tools/aapt2/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h index 3aab301ec199..4dbef5dcb815 100644 --- a/tools/aapt2/BinaryResourceParser.h +++ b/tools/aapt2/unflatten/BinaryResourceParser.h @@ -17,11 +17,13 @@ #ifndef AAPT_BINARY_RESOURCE_PARSER_H #define AAPT_BINARY_RESOURCE_PARSER_H -#include "Resolver.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "Source.h" +#include "process/IResourceTableConsumer.h" +#include "util/Util.h" + #include <androidfw/ResourceTypes.h> #include <string> @@ -42,11 +44,8 @@ public: * Creates a parser, which will read `len` bytes from `data`, and * add any resources parsed to `table`. `source` is for logging purposes. */ - BinaryResourceParser(const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver, - const Source& source, - const std::u16string& defaultPackage, - const void* data, size_t len); + BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source, + const void* data, size_t dataLen); BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy. @@ -62,14 +61,10 @@ private: bool parseTable(const android::ResChunk_header* chunk); bool parseSymbolTable(const android::ResChunk_header* chunk); - - // Looks up the resource ID in the reference and converts it to a name if available. - bool idToName(Reference* reference); - bool parsePackage(const android::ResChunk_header* chunk); - bool parsePublic(const android::ResChunk_header* chunk); + bool parsePublic(const ResourceTablePackage* package, const android::ResChunk_header* chunk); bool parseTypeSpec(const android::ResChunk_header* chunk); - bool parseType(const android::ResChunk_header* chunk); + bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk); std::unique_ptr<Item> parseValue(const ResourceNameRef& name, const ConfigDescription& config, const android::Res_value* value, uint16_t flags); @@ -92,15 +87,11 @@ private: std::unique_ptr<Styleable> parseStyleable(const ResourceNameRef& name, const ConfigDescription& config, const android::ResTable_map_entry* map); - std::shared_ptr<ResourceTable> mTable; - - std::shared_ptr<IResolver> mResolver; + IAaptContext* mContext; + ResourceTable* mTable; const Source mSource; - // The package name of the resource table. - std::u16string mDefaultPackage; - const void* mData; const size_t mDataLen; @@ -146,13 +137,11 @@ namespace android { */ inline const ResTable_map* begin(const ResTable_map_entry* map) { - return reinterpret_cast<const ResTable_map*>( - reinterpret_cast<const uint8_t*>(map) + map->size); + return (const ResTable_map*)((const uint8_t*) map + aapt::util::deviceToHost32(map->size)); } inline const ResTable_map* end(const ResTable_map_entry* map) { - return reinterpret_cast<const ResTable_map*>( - reinterpret_cast<const uint8_t*>(map) + map->size) + map->count; + return begin(map) + aapt::util::deviceToHost32(map->count); } } // namespace android diff --git a/tools/aapt2/unflatten/FileExportHeaderReader.h b/tools/aapt2/unflatten/FileExportHeaderReader.h new file mode 100644 index 000000000000..e552ea176417 --- /dev/null +++ b/tools/aapt2/unflatten/FileExportHeaderReader.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H +#define AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H + +#include "ResChunkPullParser.h" +#include "Resource.h" +#include "ResourceUtils.h" + +#include "flatten/ResourceTypeExtensions.h" +#include "util/StringPiece.h" +#include "util/Util.h" + +#include <androidfw/ResourceTypes.h> +#include <sstream> +#include <string> + +namespace aapt { + +static ssize_t parseFileExportHeaderImpl(const void* data, const size_t len, + const FileExport_header** outFileExport, + const ExportedSymbol** outExportedSymbolIndices, + android::ResStringPool* outStringPool, + std::string* outError) { + ResChunkPullParser parser(data, len); + if (!ResChunkPullParser::isGoodEvent(parser.next())) { + if (outError) *outError = parser.getLastError(); + return -1; + } + + if (util::deviceToHost16(parser.getChunk()->type) != RES_FILE_EXPORT_TYPE) { + if (outError) *outError = "no FileExport_header found"; + return -1; + } + + const FileExport_header* fileExport = convertTo<FileExport_header>(parser.getChunk()); + if (!fileExport) { + if (outError) *outError = "corrupt FileExport_header"; + return -1; + } + + if (memcmp(fileExport->magic, "AAPT", sizeof(fileExport->magic)) != 0) { + if (outError) *outError = "invalid magic value"; + return -1; + } + + const size_t exportedSymbolCount = util::deviceToHost32(fileExport->exportedSymbolCount); + + // Verify that we have enough space for all those symbols. + size_t dataLen = getChunkDataLen(&fileExport->header); + if (exportedSymbolCount > dataLen / sizeof(ExportedSymbol)) { + if (outError) *outError = "too many symbols"; + return -1; + } + + const size_t symbolIndicesSize = exportedSymbolCount * sizeof(ExportedSymbol); + + const void* strPoolData = getChunkData(&fileExport->header) + symbolIndicesSize; + const size_t strPoolDataLen = dataLen - symbolIndicesSize; + if (outStringPool->setTo(strPoolData, strPoolDataLen, false) != android::NO_ERROR) { + if (outError) *outError = "corrupt string pool"; + return -1; + } + + *outFileExport = fileExport; + *outExportedSymbolIndices = (const ExportedSymbol*) getChunkData( + &fileExport->header); + return util::deviceToHost16(fileExport->header.headerSize) + symbolIndicesSize + + outStringPool->bytes(); +} + +static ssize_t getWrappedDataOffset(const void* data, size_t len, std::string* outError) { + const FileExport_header* header = nullptr; + const ExportedSymbol* entries = nullptr; + android::ResStringPool pool; + return parseFileExportHeaderImpl(data, len, &header, &entries, &pool, outError); +} + +/** + * Reads the FileExport_header and populates outRes with the values in that header. + */ +static ssize_t unwrapFileExportHeader(const void* data, size_t len, ResourceFile* outRes, + std::string* outError) { + + const FileExport_header* fileExport = nullptr; + const ExportedSymbol* entries = nullptr; + android::ResStringPool symbolPool; + const ssize_t offset = parseFileExportHeaderImpl(data, len, &fileExport, &entries, &symbolPool, + outError); + if (offset < 0) { + return offset; + } + + const size_t exportedSymbolCount = util::deviceToHost32(fileExport->exportedSymbolCount); + outRes->exportedSymbols.clear(); + outRes->exportedSymbols.reserve(exportedSymbolCount); + + for (size_t i = 0; i < exportedSymbolCount; i++) { + const StringPiece16 str = util::getString(symbolPool, + util::deviceToHost32(entries[i].name.index)); + StringPiece16 packageStr, typeStr, entryStr; + ResourceUtils::extractResourceName(str, &packageStr, &typeStr, &entryStr); + const ResourceType* resType = parseResourceType(typeStr); + if (!resType || entryStr.empty()) { + if (outError) { + std::stringstream errorStr; + errorStr << "invalid exported symbol at index=" + << util::deviceToHost32(entries[i].name.index) + << " (" << str << ")"; + *outError = errorStr.str(); + } + return -1; + } + + outRes->exportedSymbols.push_back(SourcedResourceName{ + ResourceName{ packageStr.toString(), *resType, entryStr.toString() }, + util::deviceToHost32(entries[i].line) }); + } + + const StringPiece16 str = util::getString(symbolPool, + util::deviceToHost32(fileExport->name.index)); + StringPiece16 packageStr, typeStr, entryStr; + ResourceUtils::extractResourceName(str, &packageStr, &typeStr, &entryStr); + const ResourceType* resType = parseResourceType(typeStr); + if (!resType || entryStr.empty()) { + if (outError) { + std::stringstream errorStr; + errorStr << "invalid resource name at index=" + << util::deviceToHost32(fileExport->name.index) + << " (" << str << ")"; + *outError = errorStr.str(); + } + return -1; + } + + outRes->name = ResourceName{ packageStr.toString(), *resType, entryStr.toString() }; + outRes->source.path = util::utf16ToUtf8( + util::getString(symbolPool, util::deviceToHost32(fileExport->source.index))); + outRes->config.copyFromDtoH(fileExport->config); + return offset; +} + +} // namespace aapt + +#endif /* AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H */ diff --git a/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp b/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp new file mode 100644 index 000000000000..a76c83bdbd9a --- /dev/null +++ b/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Resource.h" + +#include "flatten/FileExportWriter.h" +#include "unflatten/FileExportHeaderReader.h" +#include "util/BigBuffer.h" +#include "util/Util.h" + +#include "test/Common.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(FileExportHeaderReaderTest, ReadHeaderWithNoSymbolExports) { + ResourceFile resFile = { + test::parseNameOrDie(u"@android:layout/main.xml"), + test::parseConfigOrDie("sw600dp-v4"), + Source{ "res/layout/main.xml" }, + }; + + BigBuffer buffer(1024); + ChunkWriter writer = wrapBufferWithFileExportHeader(&buffer, &resFile); + *writer.getBuffer()->nextBlock<uint32_t>() = 42u; + writer.finish(); + + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + + ResourceFile actualResFile; + + ssize_t offset = unwrapFileExportHeader(data.get(), buffer.size(), &actualResFile, nullptr); + ASSERT_GT(offset, 0); + + EXPECT_EQ(offset, getWrappedDataOffset(data.get(), buffer.size(), nullptr)); + + EXPECT_EQ(actualResFile.config, test::parseConfigOrDie("sw600dp-v4")); + EXPECT_EQ(actualResFile.name, test::parseNameOrDie(u"@android:layout/main.xml")); + EXPECT_EQ(actualResFile.source.path, "res/layout/main.xml"); + + EXPECT_EQ(*(uint32_t*)(data.get() + offset), 42u); +} + +} // namespace aapt diff --git a/tools/aapt2/ResChunkPullParser.cpp b/tools/aapt2/unflatten/ResChunkPullParser.cpp index 78ea60e795fc..6f8bb1b29b62 100644 --- a/tools/aapt2/ResChunkPullParser.cpp +++ b/tools/aapt2/unflatten/ResChunkPullParser.cpp @@ -14,7 +14,8 @@ * limitations under the License. */ -#include "ResChunkPullParser.h" +#include "unflatten/ResChunkPullParser.h" +#include "util/Util.h" #include <androidfw/ResourceTypes.h> #include <cstddef> @@ -31,12 +32,11 @@ ResChunkPullParser::Event ResChunkPullParser::next() { if (mEvent == Event::StartDocument) { mCurrentChunk = mData; } else { - mCurrentChunk = reinterpret_cast<const ResChunk_header*>( - reinterpret_cast<const char*>(mCurrentChunk) + mCurrentChunk->size); + mCurrentChunk = (const ResChunk_header*) + (((const char*) mCurrentChunk) + util::deviceToHost32(mCurrentChunk->size)); } - const std::ptrdiff_t diff = reinterpret_cast<const char*>(mCurrentChunk) - - reinterpret_cast<const char*>(mData); + const std::ptrdiff_t diff = (const char*) mCurrentChunk - (const char*) mData; assert(diff >= 0 && "diff is negative"); const size_t offset = static_cast<const size_t>(diff); @@ -49,15 +49,16 @@ ResChunkPullParser::Event ResChunkPullParser::next() { return (mEvent = Event::BadDocument); } - if (mCurrentChunk->headerSize < sizeof(ResChunk_header)) { + if (util::deviceToHost16(mCurrentChunk->headerSize) < sizeof(ResChunk_header)) { mLastError = "chunk has too small header"; mCurrentChunk = nullptr; return (mEvent = Event::BadDocument); - } else if (mCurrentChunk->size < mCurrentChunk->headerSize) { + } else if (util::deviceToHost32(mCurrentChunk->size) < + util::deviceToHost16(mCurrentChunk->headerSize)) { mLastError = "chunk's total size is smaller than header"; mCurrentChunk = nullptr; return (mEvent = Event::BadDocument); - } else if (offset + mCurrentChunk->size > mLen) { + } else if (offset + util::deviceToHost32(mCurrentChunk->size) > mLen) { mLastError = "chunk's data extends past the end of the document"; mCurrentChunk = nullptr; return (mEvent = Event::BadDocument); diff --git a/tools/aapt2/ResChunkPullParser.h b/tools/aapt2/unflatten/ResChunkPullParser.h index 1426ed23a5c7..a51d5bfdc9b3 100644 --- a/tools/aapt2/ResChunkPullParser.h +++ b/tools/aapt2/unflatten/ResChunkPullParser.h @@ -17,6 +17,8 @@ #ifndef AAPT_RES_CHUNK_PULL_PARSER_H #define AAPT_RES_CHUNK_PULL_PARSER_H +#include "util/Util.h" + #include <androidfw/ResourceTypes.h> #include <string> @@ -76,18 +78,18 @@ private: template <typename T> inline static const T* convertTo(const android::ResChunk_header* chunk) { - if (chunk->headerSize < sizeof(T)) { + if (util::deviceToHost16(chunk->headerSize) < sizeof(T)) { return nullptr; } return reinterpret_cast<const T*>(chunk); } -inline static const uint8_t* getChunkData(const android::ResChunk_header& chunk) { - return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize; +inline static const uint8_t* getChunkData(const android::ResChunk_header* chunk) { + return reinterpret_cast<const uint8_t*>(chunk) + util::deviceToHost16(chunk->headerSize); } -inline static size_t getChunkDataLen(const android::ResChunk_header& chunk) { - return chunk.size - chunk.headerSize; +inline static uint32_t getChunkDataLen(const android::ResChunk_header* chunk) { + return util::deviceToHost32(chunk->size) - util::deviceToHost16(chunk->headerSize); } // diff --git a/tools/aapt2/BigBuffer.cpp b/tools/aapt2/util/BigBuffer.cpp index 8f571728d729..c88e3c102415 100644 --- a/tools/aapt2/BigBuffer.cpp +++ b/tools/aapt2/util/BigBuffer.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "BigBuffer.h" +#include "util/BigBuffer.h" #include <algorithm> #include <memory> diff --git a/tools/aapt2/BigBuffer.h b/tools/aapt2/util/BigBuffer.h index 8b6569c6a8d6..cad2a2e63be1 100644 --- a/tools/aapt2/BigBuffer.h +++ b/tools/aapt2/util/BigBuffer.h @@ -20,6 +20,7 @@ #include <cassert> #include <cstring> #include <memory> +#include <type_traits> #include <vector> namespace aapt { @@ -124,6 +125,7 @@ inline size_t BigBuffer::size() const { template <typename T> inline T* BigBuffer::nextBlock(size_t count) { + static_assert(std::is_standard_layout<T>::value, "T must be standard_layout type"); assert(count != 0); return reinterpret_cast<T*>(nextBlockImpl(sizeof(T) * count)); } diff --git a/tools/aapt2/BigBuffer_test.cpp b/tools/aapt2/util/BigBuffer_test.cpp index 01ee8d7e9ad5..2a24f123e18e 100644 --- a/tools/aapt2/BigBuffer_test.cpp +++ b/tools/aapt2/util/BigBuffer_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "BigBuffer.h" +#include "util/BigBuffer.h" #include <gtest/gtest.h> diff --git a/tools/aapt2/Files.cpp b/tools/aapt2/util/Files.cpp index b24ff6bb6291..a81dc7b47926 100644 --- a/tools/aapt2/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -14,10 +14,11 @@ * limitations under the License. */ -#include "Files.h" -#include "Util.h" +#include "util/Files.h" +#include "util/Util.h" #include <cerrno> +#include <cstdio> #include <dirent.h> #include <string> #include <sys/stat.h> @@ -28,6 +29,7 @@ #endif namespace aapt { +namespace file { FileType getFileType(const StringPiece& path) { struct stat sb; @@ -61,15 +63,15 @@ FileType getFileType(const StringPiece& path) { } } -std::vector<std::string> listFiles(const StringPiece& root) { +std::vector<std::string> listFiles(const StringPiece& root, std::string* outError) { DIR* dir = opendir(root.data()); if (dir == nullptr) { - Logger::error(Source{ root.toString() }) - << "unable to open file: " - << strerror(errno) - << "." - << std::endl; - return {}; + if (outError) { + std::stringstream errorStr; + errorStr << "unable to open file: " << strerror(errno); + *outError = errorStr.str(); + return {}; + } } std::vector<std::string> files; @@ -105,17 +107,69 @@ bool mkdirs(const StringPiece& path) { return mkdirImpl(path) == 0 || errno == EEXIST; } -std::string getStem(const StringPiece& path) { +StringPiece getStem(const StringPiece& path) { const char* start = path.begin(); const char* end = path.end(); for (const char* current = end - 1; current != start - 1; --current) { if (*current == sDirSep) { - return std::string(start, current - start); + return StringPiece(start, current - start); } } return {}; } +StringPiece getFilename(const StringPiece& path) { + const char* end = path.end(); + const char* lastDirSep = path.begin(); + for (const char* c = path.begin(); c != end; ++c) { + if (*c == sDirSep) { + lastDirSep = c + 1; + } + } + return StringPiece(lastDirSep, end - lastDirSep); +} + +StringPiece getExtension(const StringPiece& path) { + StringPiece filename = getFilename(path); + const char* const end = filename.end(); + const char* c = std::find(filename.begin(), end, '.'); + if (c != end) { + return StringPiece(c, end - c); + } + return {}; +} + +std::string packageToPath(const StringPiece& package) { + std::string outPath; + for (StringPiece part : util::tokenize<char>(package, '.')) { + appendPath(&outPath, part); + } + return outPath; +} + +Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError) { + std::unique_ptr<FILE, decltype(fclose)*> f = { fopen(path.data(), "rb"), fclose }; + if (!f) { + if (outError) *outError = strerror(errno); + return {}; + } + + int fd = fileno(f.get()); + + struct stat fileStats = {}; + if (fstat(fd, &fileStats) != 0) { + if (outError) *outError = strerror(errno); + return {}; + } + + android::FileMap fileMap; + if (!fileMap.create(path.data(), fd, 0, fileStats.st_size, true)) { + if (outError) *outError = strerror(errno); + return {}; + } + return std::move(fileMap); +} + bool FileFilter::setPattern(const StringPiece& pattern) { mPatternTokens = util::splitAndLowercase(pattern, ':'); return true; @@ -169,14 +223,10 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const { if (ignore) { if (chatty) { - Logger::warn() - << "skipping " << - (type == FileType::kDirectory ? "dir '" : "file '") - << filename - << "' due to ignore pattern '" - << token - << "'." - << std::endl; + mDiag->warn(DiagMessage() << "skipping " + << (type == FileType::kDirectory ? "dir '" : "file '") + << filename << "' due to ignore pattern '" + << token << "'"); } return false; } @@ -184,5 +234,5 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const { return true; } - +} // namespace file } // namespace aapt diff --git a/tools/aapt2/Files.h b/tools/aapt2/util/Files.h index 844fd2b07189..c58ba5d6d1e3 100644 --- a/tools/aapt2/Files.h +++ b/tools/aapt2/util/Files.h @@ -17,15 +17,20 @@ #ifndef AAPT_FILES_H #define AAPT_FILES_H -#include "Logger.h" +#include "Diagnostics.h" +#include "Maybe.h" #include "Source.h" -#include "StringPiece.h" +#include "util/StringPiece.h" + +#include <utils/FileMap.h> #include <cassert> +#include <memory> #include <string> #include <vector> namespace aapt { +namespace file { #ifdef _WIN32 constexpr const char sDirSep = '\\'; @@ -74,7 +79,28 @@ bool mkdirs(const StringPiece& path); /** * Returns all but the last part of the path. */ -std::string getStem(const StringPiece& path); +StringPiece getStem(const StringPiece& path); + +/** + * Returns the last part of the path with extension. + */ +StringPiece getFilename(const StringPiece& path); + +/** + * Returns the extension of the path. This is the entire string after + * the first '.' of the last part of the path. + */ +StringPiece getExtension(const StringPiece& path); + +/** + * Converts a package name (com.android.app) to a path: com/android/app + */ +std::string packageToPath(const StringPiece& package); + +/** + * Creates a FileMap for the file at path. + */ +Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError); /* * Filter that determines which resource files/directories are @@ -84,6 +110,9 @@ std::string getStem(const StringPiece& path); */ class FileFilter { public: + FileFilter(IDiagnostics* diag) : mDiag(diag) { + } + /* * Patterns syntax: * - Delimiter is : @@ -106,6 +135,7 @@ public: bool operator()(const std::string& filename, FileType type) const; private: + IDiagnostics* mDiag; std::vector<std::string> mPatternTokens; }; @@ -123,6 +153,7 @@ void appendPath(std::string* base, const StringPiece& part, const Ts&... parts) appendPath(base, parts...); } +} // namespace file } // namespace aapt #endif // AAPT_FILES_H diff --git a/tools/aapt2/Maybe.h b/tools/aapt2/util/Maybe.h index ff6625f4bb5e..1f7d5ce901b4 100644 --- a/tools/aapt2/Maybe.h +++ b/tools/aapt2/util/Maybe.h @@ -72,7 +72,7 @@ public: * True if this holds a value, false if * it holds Nothing. */ - operator bool() const; + explicit operator bool() const; /** * Gets the value if one exists, or else diff --git a/tools/aapt2/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp index 71bbb940beda..d2c33cac7aa4 100644 --- a/tools/aapt2/Maybe_test.cpp +++ b/tools/aapt2/util/Maybe_test.cpp @@ -14,11 +14,12 @@ * limitations under the License. */ +#include "test/Common.h" +#include "util/Maybe.h" + #include <gtest/gtest.h> #include <string> -#include "Maybe.h" - namespace aapt { struct Dummy { @@ -85,22 +86,22 @@ struct Dummy { TEST(MaybeTest, MakeNothing) { Maybe<int> val = make_nothing<int>(); - EXPECT_FALSE(val); + AAPT_EXPECT_FALSE(val); Maybe<std::string> val2 = make_nothing<std::string>(); - EXPECT_FALSE(val2); + AAPT_EXPECT_FALSE(val2); val2 = make_nothing<std::string>(); - EXPECT_FALSE(val2); + AAPT_EXPECT_FALSE(val2); } TEST(MaybeTest, MakeSomething) { Maybe<int> val = make_value(23); - ASSERT_TRUE(val); + AAPT_ASSERT_TRUE(val); EXPECT_EQ(23, val.value()); Maybe<std::string> val2 = make_value(std::string("hey")); - ASSERT_TRUE(val2); + AAPT_ASSERT_TRUE(val2); EXPECT_EQ(std::string("hey"), val2.value()); } diff --git a/tools/aapt2/StringPiece.h b/tools/aapt2/util/StringPiece.h index 8cbdeae5e892..8cbdeae5e892 100644 --- a/tools/aapt2/StringPiece.h +++ b/tools/aapt2/util/StringPiece.h diff --git a/tools/aapt2/StringPiece_test.cpp b/tools/aapt2/util/StringPiece_test.cpp index 43f7a370d23c..d49b67ffa88e 100644 --- a/tools/aapt2/StringPiece_test.cpp +++ b/tools/aapt2/util/StringPiece_test.cpp @@ -19,7 +19,7 @@ #include <string> #include <vector> -#include "StringPiece.h" +#include "util/StringPiece.h" namespace aapt { diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/util/Util.cpp index ca352e0e9b61..f219b65378ff 100644 --- a/tools/aapt2/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ -#include "BigBuffer.h" -#include "Maybe.h" -#include "StringPiece.h" -#include "Util.h" +#include "util/BigBuffer.h" +#include "util/Maybe.h" +#include "util/StringPiece.h" +#include "util/Util.h" #include <algorithm> #include <ostream> @@ -122,6 +122,29 @@ bool isJavaClassName(const StringPiece16& str) { return pieces >= 2; } +bool isJavaPackageName(const StringPiece16& str) { + if (str.empty()) { + return false; + } + + size_t pieces = 0; + for (const StringPiece16& piece : tokenize(str, u'.')) { + pieces++; + if (piece.empty()) { + return false; + } + + if (piece.data()[0] == u'_' || piece.data()[piece.size() - 1] == u'_') { + return false; + } + + if (findNonAlphaNumericAndNotInSet(piece, u"_") != piece.end()) { + return false; + } + } + return pieces >= 1; +} + Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package, const StringPiece16& className) { if (className.empty()) { @@ -338,5 +361,29 @@ Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespac return {}; } +bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix, + StringPiece16* outEntry, StringPiece16* outSuffix) { + if (!stringStartsWith<char16_t>(path, u"res/")) { + return false; + } + + StringPiece16::const_iterator lastOccurence = path.end(); + for (auto iter = path.begin() + StringPiece16(u"res/").size(); iter != path.end(); ++iter) { + if (*iter == u'/') { + lastOccurence = iter; + } + } + + if (lastOccurence == path.end()) { + return false; + } + + auto iter = std::find(lastOccurence, path.end(), u'.'); + *outSuffix = StringPiece16(iter, path.end() - iter); + *outEntry = StringPiece16(lastOccurence + 1, iter - lastOccurence - 1); + *outPrefix = StringPiece16(path.begin(), lastOccurence - path.begin() + 1); + return true; +} + } // namespace util } // namespace aapt diff --git a/tools/aapt2/Util.h b/tools/aapt2/util/Util.h index 7ec6b030fd85..402147de32b2 100644 --- a/tools/aapt2/Util.h +++ b/tools/aapt2/util/Util.h @@ -17,9 +17,9 @@ #ifndef AAPT_UTIL_H #define AAPT_UTIL_H -#include "BigBuffer.h" -#include "Maybe.h" -#include "StringPiece.h" +#include "util/BigBuffer.h" +#include "util/Maybe.h" +#include "util/StringPiece.h" #include <androidfw/ResourceTypes.h> #include <functional> @@ -83,6 +83,11 @@ StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16 bool isJavaClassName(const StringPiece16& str); /** + * Tests that the string is a valid Java package name. + */ +bool isJavaPackageName(const StringPiece16& str); + +/** * Converts the class name to a fully qualified class name from the given `package`. Ex: * * asdf --> package.asdf @@ -296,6 +301,22 @@ inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) : mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) { } +inline uint16_t hostToDevice16(uint16_t value) { + return htods(value); +} + +inline uint32_t hostToDevice32(uint32_t value) { + return htodl(value); +} + +inline uint16_t deviceToHost16(uint16_t value) { + return dtohs(value); +} + +inline uint32_t deviceToHost32(uint32_t value) { + return dtohl(value); +} + /** * Returns a package name if the namespace URI is of the form: * http://schemas.android.com/apk/res/<package> @@ -305,6 +326,18 @@ inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) : */ Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri); +/** + * Given a path like: res/xml-sw600dp/foo.xml + * + * Extracts "res/xml-sw600dp/" into outPrefix. + * Extracts "foo" into outEntry. + * Extracts ".xml" into outSuffix. + * + * Returns true if successful. + */ +bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix, + StringPiece16* outEntry, StringPiece16* outSuffix); + } // namespace util /** diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index 92f2a1c0f1d1..cdba960b8670 100644 --- a/tools/aapt2/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -14,12 +14,13 @@ * limitations under the License. */ +#include "test/Common.h" +#include "util/StringPiece.h" +#include "util/Util.h" + #include <gtest/gtest.h> #include <string> -#include "StringPiece.h" -#include "Util.h" - namespace aapt { TEST(UtilTest, TrimOnlyWhitespace) { @@ -40,37 +41,37 @@ TEST(UtilTest, StringStartsWith) { TEST(UtilTest, StringBuilderSplitEscapeSequence) { EXPECT_EQ(StringPiece16(u"this is a new\nline."), - util::StringBuilder().append(u"this is a new\\") - .append(u"nline.") - .str()); + util::StringBuilder().append(u"this is a new\\") + .append(u"nline.") + .str()); } TEST(UtilTest, StringBuilderWhitespaceRemoval) { EXPECT_EQ(StringPiece16(u"hey guys this is so cool"), - util::StringBuilder().append(u" hey guys ") - .append(u" this is so cool ") - .str()); + util::StringBuilder().append(u" hey guys ") + .append(u" this is so cool ") + .str()); EXPECT_EQ(StringPiece16(u" wow, so many \t spaces. what?"), - util::StringBuilder().append(u" \" wow, so many \t ") - .append(u"spaces. \"what? ") - .str()); + util::StringBuilder().append(u" \" wow, so many \t ") + .append(u"spaces. \"what? ") + .str()); EXPECT_EQ(StringPiece16(u"where is the pie?"), - util::StringBuilder().append(u" where \t ") - .append(u" \nis the "" pie?") - .str()); + util::StringBuilder().append(u" where \t ") + .append(u" \nis the "" pie?") + .str()); } TEST(UtilTest, StringBuilderEscaping) { EXPECT_EQ(StringPiece16(u"hey guys\n this \t is so\\ cool"), - util::StringBuilder().append(u" hey guys\\n ") - .append(u" this \\t is so\\\\ cool ") - .str()); + util::StringBuilder().append(u" hey guys\\n ") + .append(u" this \\t is so\\\\ cool ") + .str()); EXPECT_EQ(StringPiece16(u"@?#\\\'"), - util::StringBuilder().append(u"\\@\\?\\#\\\\\\'") - .str()); + util::StringBuilder().append(u"\\@\\?\\#\\\\\\'") + .str()); } TEST(UtilTest, StringBuilderMisplacedQuote) { @@ -80,8 +81,8 @@ TEST(UtilTest, StringBuilderMisplacedQuote) { TEST(UtilTest, StringBuilderUnicodeCodes) { EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"), - util::StringBuilder().append(u"\\u00AF\\u0AF0 woah") - .str()); + util::StringBuilder().append(u"\\u00AF\\u0AF0 woah") + .str()); EXPECT_FALSE(util::StringBuilder().append(u"\\u00 yo")); } @@ -100,6 +101,15 @@ TEST(UtilTest, TokenizeInput) { ASSERT_EQ(tokenizer.end(), iter); } +TEST(UtilTest, TokenizeAtEnd) { + auto tokenizer = util::tokenize(StringPiece16(u"one."), u'.'); + auto iter = tokenizer.begin(); + ASSERT_EQ(*iter, StringPiece16(u"one")); + ++iter; + ASSERT_NE(iter, tokenizer.end()); + ASSERT_EQ(*iter, StringPiece16()); +} + TEST(UtilTest, IsJavaClassName) { EXPECT_TRUE(util::isJavaClassName(u"android.test.Class")); EXPECT_TRUE(util::isJavaClassName(u"android.test.Class$Inner")); @@ -111,33 +121,68 @@ TEST(UtilTest, IsJavaClassName) { EXPECT_FALSE(util::isJavaClassName(u"android")); } +TEST(UtilTest, IsJavaPackageName) { + EXPECT_TRUE(util::isJavaPackageName(u"android")); + EXPECT_TRUE(util::isJavaPackageName(u"android.test")); + EXPECT_TRUE(util::isJavaPackageName(u"android.test_thing")); + EXPECT_FALSE(util::isJavaPackageName(u"_android")); + EXPECT_FALSE(util::isJavaPackageName(u"android_")); + EXPECT_FALSE(util::isJavaPackageName(u"android.")); + EXPECT_FALSE(util::isJavaPackageName(u".android")); + EXPECT_FALSE(util::isJavaPackageName(u"android._test")); + EXPECT_FALSE(util::isJavaPackageName(u"..")); +} + TEST(UtilTest, FullyQualifiedClassName) { Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf"); - ASSERT_TRUE(res); + AAPT_ASSERT_TRUE(res); EXPECT_EQ(res.value(), u"android.asdf"); res = util::getFullyQualifiedClassName(u"android", u".asdf"); - ASSERT_TRUE(res); + AAPT_ASSERT_TRUE(res); EXPECT_EQ(res.value(), u"android.asdf"); res = util::getFullyQualifiedClassName(u"android", u".a.b"); - ASSERT_TRUE(res); + AAPT_ASSERT_TRUE(res); EXPECT_EQ(res.value(), u"android.a.b"); res = util::getFullyQualifiedClassName(u"android", u"a.b"); - ASSERT_TRUE(res); + AAPT_ASSERT_TRUE(res); EXPECT_EQ(res.value(), u"a.b"); res = util::getFullyQualifiedClassName(u"", u"a.b"); - ASSERT_TRUE(res); + AAPT_ASSERT_TRUE(res); EXPECT_EQ(res.value(), u"a.b"); res = util::getFullyQualifiedClassName(u"", u""); - ASSERT_FALSE(res); + AAPT_ASSERT_FALSE(res); res = util::getFullyQualifiedClassName(u"android", u"./Apple"); - ASSERT_FALSE(res); + AAPT_ASSERT_FALSE(res); } +TEST(UtilTest, ExtractResourcePathComponents) { + StringPiece16 prefix, entry, suffix; + ASSERT_TRUE(util::extractResFilePathParts(u"res/xml-sw600dp/entry.xml", &prefix, &entry, + &suffix)); + EXPECT_EQ(prefix, u"res/xml-sw600dp/"); + EXPECT_EQ(entry, u"entry"); + EXPECT_EQ(suffix, u".xml"); + + ASSERT_TRUE(util::extractResFilePathParts(u"res/xml-sw600dp/entry.9.png", &prefix, &entry, + &suffix)); + + EXPECT_EQ(prefix, u"res/xml-sw600dp/"); + EXPECT_EQ(entry, u"entry"); + EXPECT_EQ(suffix, u".9.png"); + + EXPECT_FALSE(util::extractResFilePathParts(u"AndroidManifest.xml", &prefix, &entry, &suffix)); + EXPECT_FALSE(util::extractResFilePathParts(u"res/.xml", &prefix, &entry, &suffix)); + + ASSERT_TRUE(util::extractResFilePathParts(u"res//.", &prefix, &entry, &suffix)); + EXPECT_EQ(prefix, u"res//"); + EXPECT_EQ(entry, u""); + EXPECT_EQ(suffix, u"."); +} } // namespace aapt |