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
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 876a422..ef11d66 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -66,6 +66,7 @@
ResourceValues.cpp \
SdkConstants.cpp \
StringPool.cpp \
+ xml/XmlActionExecutor.cpp \
xml/XmlDom.cpp \
xml/XmlPullParser.cpp \
xml/XmlUtil.cpp
@@ -107,6 +108,7 @@
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 @@
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 ab4d284..e86f2a8 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 @@
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 @@
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
+ };
+
+ virtual void log(Level level, DiagMessageActual& actualMsg) = 0;
+
+ 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);
+ }
};
-struct StdErrDiagnostics : public IDiagnostics {
+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;
+ }
+
+ if (!actualMsg.source.path.empty()) {
+ std::cerr << actualMsg.source << ": ";
+ }
+ std::cerr << tag << ": " << actualMsg.message << "." << std::endl;
+ }
+
+private:
size_t mNumErrors = 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;
+ DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics);
+};
+
+class SourcePathDiagnostics : public IDiagnostics {
+public:
+ SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : mSource(src), mDiag(diag) {
}
- void error(const DiagMessage& msg) override {
- if (mNumErrors < 20) {
- emit(msg, "error: ");
- }
- mNumErrors++;
+ void log(Level level, DiagMessageActual& actualMsg) override {
+ actualMsg.source.path = mSource.path;
+ mDiag->log(level, actualMsg);
}
- void warn(const DiagMessage& msg) override {
- emit(msg, "warn: ");
- }
+private:
+ Source mSource;
+ IDiagnostics* mDiag;
- void note(const DiagMessage& msg) override {
- emit(msg, "note: ");
- }
+ DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics);
};
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 180bd11..d6c52ab 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::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 @@
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::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 @@
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 9baf1d8..b994664 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;
}
+
+ // 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;
+}
+
+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 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;
}
-static bool includeVersionName(IAaptContext* context, const Source& source,
- const StringPiece16& versionName, xml::Element* manifestEl) {
- if (manifestEl->findAttribute(xml::kSchemaAndroid, u"versionName")) {
- return true;
+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;
}
-
- manifestEl->attributes.push_back(xml::Attribute{
- xml::kSchemaAndroid, u"versionName", versionName.toString() });
return true;
}
-static bool includeVersionCode(IAaptContext* context, const Source& source,
- const StringPiece16& versionCode, xml::Element* manifestEl) {
- if (manifestEl->findAttribute(xml::kSchemaAndroid, u"versionCode")) {
+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 (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;
- }
+ });
- manifestEl->attributes.push_back(xml::Attribute{
- xml::kSchemaAndroid, u"versionCode", versionCode.toString() });
- 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() });
+ }
-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() });
- }
+ 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;
+ });
- 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() });
- }
+ // 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 @@
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 @@
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 @@
return false;
}
- if (!verifyManifest(context, doc->file.source, root)) {
- return false;
- }
-
- if (mOptions.versionCodeDefault) {
- if (!includeVersionCode(context, doc->file.source, mOptions.versionCodeDefault.value(),
- root)) {
- return false;
- }
- }
-
- if (mOptions.versionNameDefault) {
- if (!includeVersionName(context, doc->file.source, mOptions.versionNameDefault.value(),
- root)) {
- 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)) {
- 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)) {
+ 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";
- fixUsesSdk(context, doc->file.source, usesSdk.get(), mOptions);
root->addChild(std::move(usesSdk));
}
+ xml::XmlActionExecutor executor;
+ if (!buildRules(&executor, context->getDiagnostics())) {
+ return false;
+ }
+
+ if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, context->getDiagnostics(),
+ doc)) {
+ return false;
+ }
+
+ if (mOptions.renameManifestPackage) {
+ // 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;
+ }
+ }
return true;
}
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index b8d9c83..4d9356a 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 @@
* 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 18c47df..f993720 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -166,9 +166,9 @@
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 @@
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 @@
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 8c56ebc..8eb4bc8 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -238,7 +238,7 @@
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 348c32a..faccd477 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -41,15 +41,20 @@
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 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 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 IDiagnostics* getDiagnostics() {
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
new file mode 100644
index 0000000..0ef67ea
--- /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 0000000..36b94db
--- /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 0000000..ebf287a
--- /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