diff options
author | 2016-04-05 12:41:07 -0700 | |
---|---|---|
committer | 2016-04-06 11:37:05 -0700 | |
commit | cc5609d8e484ec82ef1dced793af7f674f059b1c (patch) | |
tree | 0a0197eeecb0f1d8cf6106b38c8043f4356b960e | |
parent | 9d8ec0f12ee67129afb4020aa1d91599073cfeab (diff) |
AAPT2: Implement XmlActionExecutor to verify manifest
Defines a set of actions to perform on XML elements defined by their
hierarchy, eg: manifest -> application -> activity.
This can be used to easily add rules to check more tags in AndroidManifest.xml
Change-Id: I76c6916a98b6403075a7e56e16230979dc6cbee1
-rw-r--r-- | tools/aapt2/Android.mk | 4 | ||||
-rw-r--r-- | tools/aapt2/Diagnostics.h | 93 | ||||
-rw-r--r-- | tools/aapt2/ResourceTable_test.cpp | 42 | ||||
-rw-r--r-- | tools/aapt2/link/ManifestFixer.cpp | 306 | ||||
-rw-r--r-- | tools/aapt2/link/ManifestFixer.h | 11 | ||||
-rw-r--r-- | tools/aapt2/link/ManifestFixer_test.cpp | 12 | ||||
-rw-r--r-- | tools/aapt2/test/Builders.h | 2 | ||||
-rw-r--r-- | tools/aapt2/test/Common.h | 21 | ||||
-rw-r--r-- | tools/aapt2/xml/XmlActionExecutor.cpp | 112 | ||||
-rw-r--r-- | tools/aapt2/xml/XmlActionExecutor.h | 108 | ||||
-rw-r--r-- | tools/aapt2/xml/XmlActionExecutor_test.cpp | 62 |
11 files changed, 589 insertions, 184 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 876a422a2678..ef11d662b2ce 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -66,6 +66,7 @@ sources := \ ResourceValues.cpp \ SdkConstants.cpp \ StringPool.cpp \ + xml/XmlActionExecutor.cpp \ xml/XmlDom.cpp \ xml/XmlPullParser.cpp \ xml/XmlUtil.cpp @@ -107,6 +108,7 @@ testSources := \ SdkConstants_test.cpp \ StringPool_test.cpp \ ValueVisitor_test.cpp \ + xml/XmlActionExecutor_test.cpp \ xml/XmlDom_test.cpp \ xml/XmlPullParser_test.cpp \ xml/XmlUtil_test.cpp @@ -140,7 +142,7 @@ else endif cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG -cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti +cppFlags := -std=c++14 -Wno-missing-field-initializers -fno-exceptions -fno-rtti protoIncludes := $(call generated-sources-dir-for,STATIC_LIBRARIES,libaapt2,HOST) # ========================================================== diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h index ab4d284516e1..e86f2a8830e8 100644 --- a/tools/aapt2/Diagnostics.h +++ b/tools/aapt2/Diagnostics.h @@ -21,6 +21,7 @@ #include "util/StringPiece.h" #include "util/Util.h" +#include <android-base/macros.h> #include <iostream> #include <sstream> #include <string> @@ -46,7 +47,11 @@ public: DiagMessage(const Source& src) : mSource(src) { } - template <typename T> DiagMessage& operator<<(const T& value) { + DiagMessage(size_t line) : mSource(Source().withLine(line)) { + } + + template <typename T> + DiagMessage& operator<<(const T& value) { mMessage << value; return *this; } @@ -59,36 +64,82 @@ public: 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; -}; + enum class Level { + Note, + Warn, + Error + }; -struct StdErrDiagnostics : public IDiagnostics { - size_t mNumErrors = 0; + virtual void log(Level level, DiagMessageActual& actualMsg) = 0; - 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; + virtual void error(const DiagMessage& message) { + DiagMessageActual actual = message.build(); + log(Level::Error, actual); + } + + virtual void warn(const DiagMessage& message) { + DiagMessageActual actual = message.build(); + log(Level::Warn, actual); + } + + virtual void note(const DiagMessage& message) { + DiagMessageActual actual = message.build(); + log(Level::Note, actual); } +}; - void error(const DiagMessage& msg) override { - if (mNumErrors < 20) { - emit(msg, "error: "); +class StdErrDiagnostics : public IDiagnostics { +public: + StdErrDiagnostics() = default; + + void log(Level level, DiagMessageActual& actualMsg) override { + const char* tag; + + switch (level) { + case Level::Error: + mNumErrors++; + if (mNumErrors > 20) { + return; + } + tag = "error"; + break; + + case Level::Warn: + tag = "warn"; + break; + + case Level::Note: + tag = "note"; + break; } - mNumErrors++; + + if (!actualMsg.source.path.empty()) { + std::cerr << actualMsg.source << ": "; + } + std::cerr << tag << ": " << actualMsg.message << "." << std::endl; } - void warn(const DiagMessage& msg) override { - emit(msg, "warn: "); +private: + size_t mNumErrors = 0; + + DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics); +}; + +class SourcePathDiagnostics : public IDiagnostics { +public: + SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : mSource(src), mDiag(diag) { } - void note(const DiagMessage& msg) override { - emit(msg, "note: "); + void log(Level level, DiagMessageActual& actualMsg) override { + actualMsg.source.path = mSource.path; + mDiag->log(level, actualMsg); } + +private: + Source mSource; + IDiagnostics* mDiag; + + DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics); }; } // namespace aapt diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 180bd11275df..d6c52ab83d93 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -28,33 +28,23 @@ namespace aapt { -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 {} - }; - - EmptyDiagnostics mDiagnostics; -}; - -TEST_F(ResourceTableTest, FailToAddResourceWithBadName) { +TEST(ResourceTableTest, FailToAddResourceWithBadName) { ResourceTable table; EXPECT_FALSE(table.addResource( ResourceNameRef(u"android", ResourceType::kId, u"hey,there"), ConfigDescription{}, "", test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), - &mDiagnostics)); + test::getDiagnostics())); EXPECT_FALSE(table.addResource( ResourceNameRef(u"android", ResourceType::kId, u"hey:there"), ConfigDescription{}, "", test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), - &mDiagnostics)); + test::getDiagnostics())); } -TEST_F(ResourceTableTest, AddOneResource) { +TEST(ResourceTableTest, AddOneResource) { ResourceTable table; EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), @@ -62,12 +52,12 @@ TEST_F(ResourceTableTest, AddOneResource) { "", test::ValueBuilder<Id>() .setSource("test/path/file.xml", 23u).build(), - &mDiagnostics)); + test::getDiagnostics())); ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id")); } -TEST_F(ResourceTableTest, AddMultipleResources) { +TEST(ResourceTableTest, AddMultipleResources) { ResourceTable table; ConfigDescription config; @@ -79,21 +69,21 @@ TEST_F(ResourceTableTest, AddMultipleResources) { config, "", test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(), - &mDiagnostics)); + test::getDiagnostics())); EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:attr/id"), config, "", test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(), - &mDiagnostics)); + test::getDiagnostics())); EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:string/ok"), config, "", test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(), - &mDiagnostics)); + test::getDiagnostics())); EXPECT_TRUE(table.addResource( test::parseNameOrDie(u"@android:string/ok"), @@ -102,7 +92,7 @@ TEST_F(ResourceTableTest, AddMultipleResources) { test::ValueBuilder<BinaryPrimitive>(android::Res_value{}) .setSource("test/path/file.xml", 20u) .build(), - &mDiagnostics)); + test::getDiagnostics())); ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width")); ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id")); @@ -111,37 +101,37 @@ TEST_F(ResourceTableTest, AddMultipleResources) { languageConfig)); } -TEST_F(ResourceTableTest, OverrideWeakResourceValue) { +TEST(ResourceTableTest, OverrideWeakResourceValue) { ResourceTable table; ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{}, - "", util::make_unique<Attribute>(true), &mDiagnostics)); + "", util::make_unique<Attribute>(true), test::getDiagnostics())); 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"), ConfigDescription{}, - "", util::make_unique<Attribute>(false), &mDiagnostics)); + "", util::make_unique<Attribute>(false), test::getDiagnostics())); attr = test::getValue<Attribute>(&table, u"@android:attr/foo"); ASSERT_NE(nullptr, attr); EXPECT_FALSE(attr->isWeak()); } -TEST_F(ResourceTableTest, ProductVaryingValues) { +TEST(ResourceTableTest, ProductVaryingValues) { ResourceTable table; EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"), test::parseConfigOrDie("land"), "tablet", util::make_unique<Id>(), - &mDiagnostics)); + test::getDiagnostics())); EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"), test::parseConfigOrDie("land"), "phone", util::make_unique<Id>(), - &mDiagnostics)); + test::getDiagnostics())); EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo", test::parseConfigOrDie("land"), diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 9baf1d86795c..b9946649e4f0 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -17,66 +17,197 @@ #include "ResourceUtils.h" #include "link/ManifestFixer.h" #include "util/Util.h" +#include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" namespace aapt { -static bool verifyManifest(IAaptContext* context, const Source& source, xml::Element* manifestEl) { - xml::Attribute* attr = manifestEl->findAttribute({}, u"package"); - if (!attr) { - context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber)) - << "missing 'package' attribute"); - } else if (ResourceUtils::isReference(attr->value)) { - context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber)) - << "value for attribute 'package' must not be a " - "reference"); - } else if (!util::isJavaPackageName(attr->value)) { - context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber)) - << "invalid package name '" << attr->value << "'"); - } else { - return true; +/** + * This is how PackageManager builds class names from AndroidManifest.xml entries. + */ +static bool nameIsJavaClassName(xml::Element* el, xml::Attribute* attr, + SourcePathDiagnostics* diag) { + std::u16string className = attr->value; + if (className.find(u'.') == std::u16string::npos) { + // There is no '.', so add one to the beginning. + className = u"."; + className += attr->value; } - return false; -} -static bool includeVersionName(IAaptContext* context, const Source& source, - const StringPiece16& versionName, xml::Element* manifestEl) { - if (manifestEl->findAttribute(xml::kSchemaAndroid, u"versionName")) { - return true; + // We allow unqualified class names (ie: .HelloActivity) + // Since we don't know the package name, we can just make a fake one here and + // the test will be identical as long as the real package name is valid too. + Maybe<std::u16string> fullyQualifiedClassName = + util::getFullyQualifiedClassName(u"a", className); + + StringPiece16 qualifiedClassName = fullyQualifiedClassName + ? fullyQualifiedClassName.value() : className; + if (!util::isJavaClassName(qualifiedClassName)) { + diag->error(DiagMessage(el->lineNumber) + << "attribute 'android:name' in <" + << el->name << "> tag must be a valid Java class name"); + return false; } + return true; +} - manifestEl->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, u"versionName", versionName.toString() }); +static bool optionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { + if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name")) { + return nameIsJavaClassName(el, attr, diag); + } return true; } -static bool includeVersionCode(IAaptContext* context, const Source& source, - const StringPiece16& versionCode, xml::Element* manifestEl) { - if (manifestEl->findAttribute(xml::kSchemaAndroid, u"versionCode")) { - return true; +static bool requiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { + if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name")) { + return nameIsJavaClassName(el, attr, diag); } + diag->error(DiagMessage(el->lineNumber) + << "<" << el->name << "> is missing attribute 'android:name'"); + return false; +} - manifestEl->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, u"versionCode", versionCode.toString() }); +static bool verifyManifest(xml::Element* el, SourcePathDiagnostics* diag) { + xml::Attribute* attr = el->findAttribute({}, u"package"); + if (!attr) { + diag->error(DiagMessage(el->lineNumber) << "<manifest> tag is missing 'package' attribute"); + return false; + } else if (ResourceUtils::isReference(attr->value)) { + diag->error(DiagMessage(el->lineNumber) + << "attribute 'package' in <manifest> tag must not be a reference"); + return false; + } else if (!util::isJavaPackageName(attr->value)) { + diag->error(DiagMessage(el->lineNumber) + << "attribute 'package' in <manifest> tag is not a valid Java package name: '" + << attr->value << "'"); + return false; + } return true; } -static bool fixUsesSdk(IAaptContext* context, const Source& source, xml::Element* el, - const ManifestFixerOptions& options) { - if (options.minSdkVersionDefault && - el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion") == nullptr) { - // There was no minSdkVersion defined and we have a default to assign. - el->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, u"minSdkVersion", options.minSdkVersionDefault.value() }); +bool ManifestFixer::buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag) { + // First verify some options. + if (mOptions.renameManifestPackage) { + if (!util::isJavaPackageName(mOptions.renameManifestPackage.value())) { + diag->error(DiagMessage() << "invalid manifest package override '" + << mOptions.renameManifestPackage.value() << "'"); + return false; + } } - if (options.targetSdkVersionDefault && - el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion") == nullptr) { - // There was no targetSdkVersion defined and we have a default to assign. - el->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, u"targetSdkVersion", - options.targetSdkVersionDefault.value() }); + if (mOptions.renameInstrumentationTargetPackage) { + if (!util::isJavaPackageName(mOptions.renameInstrumentationTargetPackage.value())) { + diag->error(DiagMessage() << "invalid instrumentation target package override '" + << mOptions.renameInstrumentationTargetPackage.value() << "'"); + return false; + } } + + // Common intent-filter actions. + xml::XmlNodeAction intentFilterAction; + intentFilterAction[u"action"]; + intentFilterAction[u"category"]; + intentFilterAction[u"data"]; + + // Common meta-data actions. + xml::XmlNodeAction metaDataAction; + + // Manifest actions. + xml::XmlNodeAction& manifestAction = (*executor)[u"manifest"]; + manifestAction.action(verifyManifest); + manifestAction.action([&](xml::Element* el) -> bool { + if (mOptions.versionNameDefault) { + if (el->findAttribute(xml::kSchemaAndroid, u"versionName") == nullptr) { + el->attributes.push_back(xml::Attribute{ + xml::kSchemaAndroid, + u"versionName", + mOptions.versionNameDefault.value() }); + } + } + + if (mOptions.versionCodeDefault) { + if (el->findAttribute(xml::kSchemaAndroid, u"versionCode") == nullptr) { + el->attributes.push_back(xml::Attribute{ + xml::kSchemaAndroid, + u"versionCode", + mOptions.versionCodeDefault.value() }); + } + } + return true; + }); + + // Uses-sdk actions. + manifestAction[u"uses-sdk"].action([&](xml::Element* el) -> bool { + if (mOptions.minSdkVersionDefault && + el->findAttribute(xml::kSchemaAndroid, u"minSdkVersion") == nullptr) { + // There was no minSdkVersion defined and we have a default to assign. + el->attributes.push_back(xml::Attribute{ + xml::kSchemaAndroid, u"minSdkVersion", + mOptions.minSdkVersionDefault.value() }); + } + + if (mOptions.targetSdkVersionDefault && + el->findAttribute(xml::kSchemaAndroid, u"targetSdkVersion") == nullptr) { + // There was no targetSdkVersion defined and we have a default to assign. + el->attributes.push_back(xml::Attribute{ + xml::kSchemaAndroid, u"targetSdkVersion", + mOptions.targetSdkVersionDefault.value() }); + } + return true; + }); + + // Instrumentation actions. + manifestAction[u"instrumentation"].action([&](xml::Element* el) -> bool { + if (!mOptions.renameInstrumentationTargetPackage) { + return true; + } + + if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"targetPackage")) { + attr->value = mOptions.renameInstrumentationTargetPackage.value(); + } + return true; + }); + + manifestAction[u"uses-permission"]; + manifestAction[u"permission"]; + manifestAction[u"permission-tree"]; + manifestAction[u"permission-group"]; + + manifestAction[u"uses-configuration"]; + manifestAction[u"uses-feature"]; + manifestAction[u"uses-library"]; + manifestAction[u"supports-screens"]; + manifestAction[u"compatible-screens"]; + manifestAction[u"supports-gl-texture"]; + + // Application actions. + xml::XmlNodeAction& applicationAction = (*executor)[u"manifest"][u"application"]; + applicationAction.action(optionalNameIsJavaClassName); + + // Activity actions. + applicationAction[u"activity"].action(requiredNameIsJavaClassName); + applicationAction[u"activity"][u"intent-filter"] = intentFilterAction; + applicationAction[u"activity"][u"meta-data"] = metaDataAction; + + // Activity alias actions. + applicationAction[u"activity-alias"][u"intent-filter"] = intentFilterAction; + applicationAction[u"activity-alias"][u"meta-data"] = metaDataAction; + + // Service actions. + applicationAction[u"service"].action(requiredNameIsJavaClassName); + applicationAction[u"service"][u"intent-filter"] = intentFilterAction; + applicationAction[u"service"][u"meta-data"] = metaDataAction; + + // Receiver actions. + applicationAction[u"receiver"].action(requiredNameIsJavaClassName); + applicationAction[u"receiver"][u"intent-filter"] = intentFilterAction; + applicationAction[u"receiver"][u"meta-data"] = metaDataAction; + + // Provider actions. + applicationAction[u"provider"].action(requiredNameIsJavaClassName); + applicationAction[u"provider"][u"grant-uri-permissions"]; + applicationAction[u"provider"][u"meta-data"] = metaDataAction; + applicationAction[u"provider"][u"path-permissions"]; return true; } @@ -103,14 +234,7 @@ private: StringPiece16 mPackage; }; -static bool renameManifestPackage(IAaptContext* context, const Source& source, - const StringPiece16& packageOverride, xml::Element* manifestEl) { - if (!util::isJavaPackageName(packageOverride)) { - context->getDiagnostics()->error(DiagMessage() << "invalid manifest package override '" - << packageOverride << "'"); - return false; - } - +static bool renameManifestPackage(const StringPiece16& packageOverride, xml::Element* manifestEl) { xml::Attribute* attr = manifestEl->findAttribute({}, u"package"); // We've already verified that the manifest element is present, with a package name specified. @@ -124,32 +248,6 @@ static bool renameManifestPackage(IAaptContext* context, const Source& source, return true; } -static bool renameInstrumentationTargetPackage(IAaptContext* context, const Source& source, - const StringPiece16& packageOverride, - xml::Element* manifestEl) { - if (!util::isJavaPackageName(packageOverride)) { - context->getDiagnostics()->error(DiagMessage() - << "invalid instrumentation target package override '" - << packageOverride << "'"); - return false; - } - - xml::Element* instrumentationEl = manifestEl->findChild({}, u"instrumentation"); - if (!instrumentationEl) { - // No error if there is no work to be done. - return true; - } - - xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, u"targetPackage"); - if (!attr) { - // No error if there is no work to be done. - return true; - } - - attr->value = packageOverride.toString(); - return true; -} - bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) { xml::Element* root = xml::findRootElement(doc->root.get()); if (!root || !root->namespaceUri.empty() || root->name != u"manifest") { @@ -158,59 +256,31 @@ bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) { return false; } - if (!verifyManifest(context, doc->file.source, root)) { - return false; + if ((mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault) + && root->findChild({}, u"uses-sdk") == nullptr) { + // Auto insert a <uses-sdk> element. + std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>(); + usesSdk->name = u"uses-sdk"; + root->addChild(std::move(usesSdk)); } - if (mOptions.versionCodeDefault) { - if (!includeVersionCode(context, doc->file.source, mOptions.versionCodeDefault.value(), - root)) { - return false; - } + xml::XmlActionExecutor executor; + if (!buildRules(&executor, context->getDiagnostics())) { + return false; } - if (mOptions.versionNameDefault) { - if (!includeVersionName(context, doc->file.source, mOptions.versionNameDefault.value(), - root)) { - return false; - } + if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, context->getDiagnostics(), + doc)) { + return false; } if (mOptions.renameManifestPackage) { - // Rename manifest package. - if (!renameManifestPackage(context, doc->file.source, - mOptions.renameManifestPackage.value(), root)) { - return false; - } - } - - if (mOptions.renameInstrumentationTargetPackage) { - if (!renameInstrumentationTargetPackage(context, doc->file.source, - mOptions.renameInstrumentationTargetPackage.value(), - root)) { + // Rename manifest package outside of the XmlActionExecutor. + // We need to extract the old package name and FullyQualify all class names. + if (!renameManifestPackage(mOptions.renameManifestPackage.value(), root)) { return false; } } - - bool foundUsesSdk = false; - for (xml::Element* el : root->getChildElements()) { - if (!el->namespaceUri.empty()) { - continue; - } - - if (el->name == u"uses-sdk") { - foundUsesSdk = true; - fixUsesSdk(context, doc->file.source, el, mOptions); - } - } - - if (!foundUsesSdk && (mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault)) { - std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>(); - usesSdk->name = u"uses-sdk"; - fixUsesSdk(context, doc->file.source, usesSdk.get(), mOptions); - root->addChild(std::move(usesSdk)); - } - return true; } diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h index b8d9c833ff05..4d9356a933c2 100644 --- a/tools/aapt2/link/ManifestFixer.h +++ b/tools/aapt2/link/ManifestFixer.h @@ -19,6 +19,7 @@ #include "process/IResourceTableConsumer.h" #include "util/Maybe.h" +#include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" #include <string> @@ -38,13 +39,17 @@ struct ManifestFixerOptions { * Verifies that the manifest is correctly formed and inserts defaults * where specified with ManifestFixerOptions. */ -struct ManifestFixer : public IXmlResourceConsumer { - ManifestFixerOptions mOptions; - +class ManifestFixer : public IXmlResourceConsumer { +public: ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) { } bool consume(IAaptContext* context, xml::XmlResource* doc) override; + +private: + bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag); + + ManifestFixerOptions mOptions; }; } // namespace aapt diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 18c47dff33ec..f993720b9566 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -166,9 +166,9 @@ TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) { std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> - <application name=".MainApplication" text="hello"> - <activity name=".activity.Start" /> - <receiver name="com.google.android.Receiver" /> + <application android:name=".MainApplication" text="hello"> + <activity android:name=".activity.Start" /> + <receiver android:name="com.google.android.Receiver" /> </application> </manifest>)EOF", options); ASSERT_NE(nullptr, doc); @@ -185,7 +185,7 @@ TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) { xml::Element* applicationEl = manifestEl->findChild({}, u"application"); ASSERT_NE(nullptr, applicationEl); - attr = applicationEl->findAttribute({}, u"name"); + attr = applicationEl->findAttribute(xml::kSchemaAndroid, u"name"); ASSERT_NE(nullptr, attr); EXPECT_EQ(std::u16string(u"android.MainApplication"), attr->value); @@ -197,14 +197,14 @@ TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) { el = applicationEl->findChild({}, u"activity"); ASSERT_NE(nullptr, el); - attr = el->findAttribute({}, u"name"); + attr = el->findAttribute(xml::kSchemaAndroid, u"name"); ASSERT_NE(nullptr, el); EXPECT_EQ(std::u16string(u"android.activity.Start"), attr->value); el = applicationEl->findChild({}, u"receiver"); ASSERT_NE(nullptr, el); - attr = el->findAttribute({}, u"name"); + attr = el->findAttribute(xml::kSchemaAndroid, u"name"); ASSERT_NE(nullptr, el); EXPECT_EQ(std::u16string(u"com.google.android.Receiver"), attr->value); } diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 8c56ebc67b32..8eb4bc88168d 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -238,7 +238,7 @@ inline std::unique_ptr<xml::XmlResource> buildXmlDom(const StringPiece& str) { std::stringstream in; in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str; StdErrDiagnostics diag; - std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, {}); + std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, Source("test.xml")); assert(doc); return doc; } diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 348c32a04e88..faccd47783ff 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -41,15 +41,20 @@ 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 log(Level level, DiagMessageActual& actualMsg) override { + switch (level) { + case Level::Note: + return; + + case Level::Warn: + std::cerr << actualMsg.source << ": warn: " << actualMsg.message << "." << std::endl; + break; + + case Level::Error: + std::cerr << actualMsg.source << ": error: " << actualMsg.message << "." << std::endl; + break; + } } - void note(const DiagMessage& message) override {} }; inline IDiagnostics* getDiagnostics() { diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp new file mode 100644 index 000000000000..0ef67eaf3dc5 --- /dev/null +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 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 "xml/XmlActionExecutor.h" + +namespace aapt { +namespace xml { + +static bool wrapperOne(XmlNodeAction::ActionFunc& f, Element* el, SourcePathDiagnostics*) { + return f(el); +} + +static bool wrapperTwo(XmlNodeAction::ActionFuncWithDiag& f, Element* el, + SourcePathDiagnostics* diag) { + return f(el, diag); +} + +void XmlNodeAction::action(XmlNodeAction::ActionFunc f) { + mActions.emplace_back(std::bind(wrapperOne, std::move(f), + std::placeholders::_1, + std::placeholders::_2)); +} + +void XmlNodeAction::action(XmlNodeAction::ActionFuncWithDiag f) { + mActions.emplace_back(std::bind(wrapperTwo, std::move(f), + std::placeholders::_1, + std::placeholders::_2)); +} + +static void printElementToDiagMessage(const Element* el, DiagMessage* msg) { + *msg << "<"; + if (!el->namespaceUri.empty()) { + *msg << el->namespaceUri << ":"; + } + *msg << el->name << ">"; +} + +bool XmlNodeAction::execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, + Element* el) const { + bool error = false; + for (const ActionFuncWithDiag& action : mActions) { + error |= !action(el, diag); + } + + for (Element* childEl : el->getChildElements()) { + if (childEl->namespaceUri.empty()) { + std::map<std::u16string, XmlNodeAction>::const_iterator iter = + mMap.find(childEl->name); + if (iter != mMap.end()) { + error |= !iter->second.execute(policy, diag, childEl); + continue; + } + } + + if (policy == XmlActionExecutorPolicy::Whitelist) { + DiagMessage errorMsg(childEl->lineNumber); + errorMsg << "unknown element "; + printElementToDiagMessage(childEl, &errorMsg); + errorMsg << " found"; + diag->error(errorMsg); + error = true; + } + } + return !error; +} + +bool XmlActionExecutor::execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, + XmlResource* doc) const { + SourcePathDiagnostics sourceDiag(doc->file.source, diag); + + Element* el = findRootElement(doc); + if (!el) { + if (policy == XmlActionExecutorPolicy::Whitelist) { + sourceDiag.error(DiagMessage() << "no root XML tag found"); + return false; + } + return true; + } + + if (el->namespaceUri.empty()) { + std::map<std::u16string, XmlNodeAction>::const_iterator iter = mMap.find(el->name); + if (iter != mMap.end()) { + return iter->second.execute(policy, &sourceDiag, el); + } + } + + if (policy == XmlActionExecutorPolicy::Whitelist) { + DiagMessage errorMsg(el->lineNumber); + errorMsg << "unknown element "; + printElementToDiagMessage(el, &errorMsg); + errorMsg << " found"; + sourceDiag.error(errorMsg); + return false; + } + return true; +} + +} // namespace xml +} // namespace aapt diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h new file mode 100644 index 000000000000..36b94dbfde05 --- /dev/null +++ b/tools/aapt2/xml/XmlActionExecutor.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2016 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_XMLPATTERN_H +#define AAPT_XML_XMLPATTERN_H + +#include "Diagnostics.h" +#include "xml/XmlDom.h" + +#include <android-base/macros.h> +#include <functional> +#include <map> +#include <string> +#include <vector> + +namespace aapt { +namespace xml { + +enum class XmlActionExecutorPolicy { + /** + * Actions on run if elements are matched, errors occur only when actions return false. + */ + None, + + /** + * The actions defined must match and run. If an element is found that does not match + * an action, an error occurs. + */ + Whitelist, +}; + +/** + * Contains the actions to perform at this XML node. This is a recursive data structure that + * holds XmlNodeActions for child XML nodes. + */ +class XmlNodeAction { +public: + using ActionFuncWithDiag = std::function<bool(Element*, SourcePathDiagnostics*)>; + using ActionFunc = std::function<bool(Element*)>; + + /** + * Find or create a child XmlNodeAction that will be performed for the child element + * with the name `name`. + */ + XmlNodeAction& operator[](const std::u16string& name) { + return mMap[name]; + } + + /** + * Add an action to be performed at this XmlNodeAction. + */ + void action(ActionFunc f); + void action(ActionFuncWithDiag); + +private: + friend class XmlActionExecutor; + + bool execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, Element* el) const; + + std::map<std::u16string, XmlNodeAction> mMap; + std::vector<ActionFuncWithDiag> mActions; +}; + +/** + * Allows the definition of actions to execute at specific XML elements defined by their + * hierarchy. + */ +class XmlActionExecutor { +public: + XmlActionExecutor() = default; + + /** + * Find or create a root XmlNodeAction that will be performed for the root XML element + * with the name `name`. + */ + XmlNodeAction& operator[](const std::u16string& name) { + return mMap[name]; + } + + /** + * Execute the defined actions for this XmlResource. + * Returns true if all actions return true, otherwise returns false. + */ + bool execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, XmlResource* doc) const; + +private: + std::map<std::u16string, XmlNodeAction> mMap; + + DISALLOW_COPY_AND_ASSIGN(XmlActionExecutor); +}; + +} // namespace xml +} // namespace aapt + +#endif /* AAPT_XML_XMLPATTERN_H */ diff --git a/tools/aapt2/xml/XmlActionExecutor_test.cpp b/tools/aapt2/xml/XmlActionExecutor_test.cpp new file mode 100644 index 000000000000..ebf287a251f2 --- /dev/null +++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 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 "test/Test.h" +#include "xml/XmlActionExecutor.h" + +namespace aapt { +namespace xml { + +TEST(XmlActionExecutorTest, BuildsAccessibleNestedPattern) { + XmlActionExecutor executor; + XmlNodeAction& manifestAction = executor[u"manifest"]; + XmlNodeAction& applicationAction = manifestAction[u"application"]; + + Element* manifestEl = nullptr; + manifestAction.action([&](Element* manifest) -> bool { + manifestEl = manifest; + return true; + }); + + Element* applicationEl = nullptr; + applicationAction.action([&](Element* application) -> bool { + applicationEl = application; + return true; + }); + + std::unique_ptr<XmlResource> doc = test::buildXmlDom("<manifest><application /></manifest>"); + + StdErrDiagnostics diag; + ASSERT_TRUE(executor.execute(XmlActionExecutorPolicy::None, &diag, doc.get())); + ASSERT_NE(nullptr, manifestEl); + EXPECT_EQ(std::u16string(u"manifest"), manifestEl->name); + + ASSERT_NE(nullptr, applicationEl); + EXPECT_EQ(std::u16string(u"application"), applicationEl->name); +} + +TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) { + XmlActionExecutor executor; + executor[u"manifest"][u"application"]; + + std::unique_ptr<XmlResource> doc = test::buildXmlDom( + "<manifest><application /><activity /></manifest>"); + StdErrDiagnostics diag; + ASSERT_FALSE(executor.execute(XmlActionExecutorPolicy::Whitelist, &diag, doc.get())); +} + +} // namespace xml +} // namespace aapt |