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
This commit is contained in:
@ -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)
|
||||
|
||||
# ==========================================================
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
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
|
||||
|
@ -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"),
|
||||
|
@ -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 @@ 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.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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 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() {
|
||||
|
112
tools/aapt2/xml/XmlActionExecutor.cpp
Normal file
112
tools/aapt2/xml/XmlActionExecutor.cpp
Normal file
@ -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
|
108
tools/aapt2/xml/XmlActionExecutor.h
Normal file
108
tools/aapt2/xml/XmlActionExecutor.h
Normal file
@ -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 */
|
62
tools/aapt2/xml/XmlActionExecutor_test.cpp
Normal file
62
tools/aapt2/xml/XmlActionExecutor_test.cpp
Normal file
@ -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
|
Reference in New Issue
Block a user