diff options
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/aapt/Command.cpp | 11 | ||||
| -rw-r--r-- | tools/aapt/ResourceTable.cpp | 4 | ||||
| -rw-r--r-- | tools/aapt2/Android.bp | 2 | ||||
| -rw-r--r-- | tools/aapt2/ResourceValues.cpp | 8 | ||||
| -rw-r--r-- | tools/aapt2/ResourceValues_test.cpp | 20 | ||||
| -rw-r--r-- | tools/aapt2/StringPool.cpp | 7 | ||||
| -rw-r--r-- | tools/aapt2/StringPool.h | 5 | ||||
| -rw-r--r-- | tools/aapt2/cmd/Link.cpp | 66 | ||||
| -rw-r--r-- | tools/aapt2/cmd/Util.cpp | 32 | ||||
| -rw-r--r-- | tools/aapt2/cmd/Util_test.cpp | 39 | ||||
| -rw-r--r-- | tools/aapt2/compile/InlineXmlFormatParser.cpp | 4 | ||||
| -rw-r--r-- | tools/aapt2/compile/InlineXmlFormatParser_test.cpp | 43 | ||||
| -rw-r--r-- | tools/aapt2/flatten/TableFlattener.cpp | 25 | ||||
| -rw-r--r-- | tools/aapt2/java/ManifestClassGenerator.cpp | 32 | ||||
| -rw-r--r-- | tools/aapt2/link/ManifestFixer.cpp | 4 | ||||
| -rw-r--r-- | tools/aapt2/text/Unicode.cpp | 3 | ||||
| -rw-r--r-- | tools/aapt2/text/Unicode_test.cpp | 5 | ||||
| -rw-r--r-- | tools/aapt2/util/Util.cpp | 84 | ||||
| -rw-r--r-- | tools/aapt2/util/Util.h | 189 | ||||
| -rw-r--r-- | tools/aapt2/util/Util_test.cpp | 32 |
20 files changed, 396 insertions, 219 deletions
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 5e8580255197..cb87737c6868 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -739,12 +739,8 @@ int doDump(Bundle* bundle) AssetManager assets; int32_t assetsCookie; - if (!assets.addAssetPath(String8(filename), &assetsCookie)) { - fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n"); - return 1; - } - // Now add any dependencies passed in. + // Add any dependencies passed in. for (size_t i = 0; i < bundle->getPackageIncludes().size(); i++) { const String8& assetPath = bundle->getPackageIncludes()[i]; if (!assets.addAssetPath(assetPath, NULL)) { @@ -753,6 +749,11 @@ int doDump(Bundle* bundle) } } + if (!assets.addAssetPath(String8(filename), &assetsCookie)) { + fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n"); + return 1; + } + // Make a dummy config for retrieving resources... we need to supply // non-default values for some configs so that we can retrieve resources // in the app that don't have a default. The most important of these is diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 52b93a945433..669afe18af88 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -4847,6 +4847,7 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, const String16 animatedVector16("animated-vector"); const String16 pathInterpolator16("pathInterpolator"); const String16 objectAnimator16("objectAnimator"); + const String16 gradient16("gradient"); const int minSdk = getMinSdkVersion(bundle); if (minSdk >= SDK_LOLLIPOP_MR1) { @@ -4874,7 +4875,8 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, if (bundle->getNoVersionVectors() && (node->getElementName() == vector16 || node->getElementName() == animatedVector16 || node->getElementName() == objectAnimator16 || - node->getElementName() == pathInterpolator16)) { + node->getElementName() == pathInterpolator16 || + node->getElementName() == gradient16)) { // We were told not to version vector tags, so skip the children here. continue; } diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index c6cfd3032762..ade638d2c422 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -166,7 +166,7 @@ cc_test_host { "test/Builders.cpp", "test/Common.cpp", "**/*_test.cpp", - ], + ] + toolSources, static_libs: [ "libaapt2", "libgmock", diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 1cba19462839..0fe1a1f9ddcc 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -55,7 +55,7 @@ bool RawString::Equals(const Value* value) const { } RawString* RawString::Clone(StringPool* new_pool) const { - RawString* rs = new RawString(new_pool->MakeRef(*value)); + RawString* rs = new RawString(new_pool->MakeRef(value)); rs->comment_ = comment_; rs->source_ = source_; return rs; @@ -95,7 +95,7 @@ bool Reference::Equals(const Value* value) const { bool Reference::Flatten(android::Res_value* out_value) const { const ResourceId resid = id.value_or_default(ResourceId(0)); const bool dynamic = resid.is_valid_dynamic() && resid.package_id() != kFrameworkPackageId && - resid.package_id() != kAppPackageId; + resid.package_id() < kAppPackageId; if (reference_type == Reference::Type::kResource) { if (dynamic) { @@ -197,7 +197,7 @@ bool String::Flatten(android::Res_value* out_value) const { } String* String::Clone(StringPool* new_pool) const { - String* str = new String(new_pool->MakeRef(*value)); + String* str = new String(new_pool->MakeRef(value)); str->comment_ = comment_; str->source_ = source_; str->untranslatable_sections = untranslatable_sections; @@ -280,7 +280,7 @@ bool FileReference::Flatten(android::Res_value* out_value) const { } FileReference* FileReference::Clone(StringPool* new_pool) const { - FileReference* fr = new FileReference(new_pool->MakeRef(*path)); + FileReference* fr = new FileReference(new_pool->MakeRef(path)); fr->file = file; fr->comment_ = comment_; fr->source_ = source_; diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp index 10f9b55ede08..a80a9dc177f1 100644 --- a/tools/aapt2/ResourceValues_test.cpp +++ b/tools/aapt2/ResourceValues_test.cpp @@ -18,6 +18,10 @@ #include "test/Test.h" +using ::testing::Eq; +using ::testing::SizeIs; +using ::testing::StrEq; + namespace aapt { TEST(ResourceValuesTest, PluralEquals) { @@ -148,6 +152,22 @@ TEST(ResourceValuesTest, StyleClone) { EXPECT_TRUE(a->Equals(b.get())); } +TEST(ResourcesValuesTest, StringClones) { + StringPool pool_a; + StringPool pool_b; + + String str_a(pool_a.MakeRef("hello", StringPool::Context(test::ParseConfigOrDie("en")))); + + ASSERT_THAT(pool_a, SizeIs(1u)); + EXPECT_THAT(pool_a.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en"))); + EXPECT_THAT(pool_a.strings()[0]->value, StrEq("hello")); + + std::unique_ptr<String> str_b(str_a.Clone(&pool_b)); + ASSERT_THAT(pool_b, SizeIs(1u)); + EXPECT_THAT(pool_b.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en"))); + EXPECT_THAT(pool_b.strings()[0]->value, StrEq("hello")); +} + TEST(ResourceValuesTest, StyleMerges) { StringPool pool_a; StringPool pool_b; diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp index 705b1ab052af..3a1a18c01e1c 100644 --- a/tools/aapt2/StringPool.cpp +++ b/tools/aapt2/StringPool.cpp @@ -191,6 +191,13 @@ StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& c return Ref(borrow); } +StringPool::Ref StringPool::MakeRef(const Ref& ref) { + if (ref.entry_->pool_ == this) { + return ref; + } + return MakeRef(ref.entry_->value, ref.entry_->context); +} + StringPool::StyleRef StringPool::MakeRef(const StyleString& str) { return MakeRef(str, Context{}); } diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h index 8350d0d09108..3c1f3dc3a1bb 100644 --- a/tools/aapt2/StringPool.h +++ b/tools/aapt2/StringPool.h @@ -49,6 +49,8 @@ struct StyleString { // Otherwise, the style data array would have to be sparse and take up more space. class StringPool { public: + using size_type = size_t; + class Context { public: enum : uint32_t { @@ -165,6 +167,9 @@ class StringPool { // when sorting the string pool. Returns a reference to the string in the pool. Ref MakeRef(const android::StringPiece& str, const Context& context); + // Adds a string from another string pool. Returns a reference to the string in the string pool. + Ref MakeRef(const Ref& ref); + // Adds a style to the string pool and returns a reference to it. StyleRef MakeRef(const StyleString& str); diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 7742f36f1610..5fc35b811213 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -442,7 +442,7 @@ static bool IsTransitionElement(const std::string& name) { static bool IsVectorElement(const std::string& name) { return name == "vector" || name == "animated-vector" || name == "pathInterpolator" || - name == "objectAnimator"; + name == "objectAnimator" || name == "gradient"; } template <typename T> @@ -754,6 +754,9 @@ class LinkCommand { for (auto& entry : asset_source->GetAssignedPackageIds()) { if (entry.first > kFrameworkPackageId && entry.first < kAppPackageId) { final_table_.included_packages_[entry.first] = entry.second; + } else if (entry.first == kAppPackageId) { + // Capture the included base feature package. + included_feature_base_ = entry.second; } } @@ -1408,17 +1411,53 @@ class LinkCommand { return false; } - if (context_->GetPackageType() == PackageType::kStaticLib) { - if (!FlattenTableToPb(table, writer)) { - return false; + // Hack to fix b/68820737. + // We need to modify the ResourceTable's package name, but that should NOT affect + // anything else being generated, which includes the Java classes. + // If required, the package name is modifed before flattening, and then modified back + // to its original name. + ResourceTablePackage* package_to_rewrite = nullptr; + if (context_->GetPackageId() > kAppPackageId && + included_feature_base_ == make_value(context_->GetCompilationPackage())) { + // The base APK is included, and this is a feature split. If the base package is + // the same as this package, then we are building an old style Android Instant Apps feature + // split and must apply this workaround to avoid requiring namespaces support. + package_to_rewrite = table->FindPackage(context_->GetCompilationPackage()); + if (package_to_rewrite != nullptr) { + CHECK_EQ(1u, table->packages.size()) << "can't change name of package when > 1 package"; + + std::string new_package_name = + StringPrintf("%s.%s", package_to_rewrite->name.c_str(), + app_info_.split_name.value_or_default("feature").c_str()); + + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note( + DiagMessage() << "rewriting resource package name for feature split to '" + << new_package_name << "'"); + } + package_to_rewrite->name = new_package_name; } + } + + bool success; + if (context_->GetPackageType() == PackageType::kStaticLib) { + success = FlattenTableToPb(table, writer); } else { - if (!FlattenTable(table, writer)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc"); - return false; + success = FlattenTable(table, writer); + } + + if (package_to_rewrite != nullptr) { + // Change the name back. + package_to_rewrite->name = context_->GetCompilationPackage(); + if (package_to_rewrite->id) { + table->included_packages_.erase(package_to_rewrite->id.value()); } } - return true; + + if (!success) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resource table"); + } + return success; } int Run(const std::vector<std::string>& input_files) { @@ -1447,8 +1486,8 @@ class LinkCommand { return 1; } - const AppInfo& app_info = maybe_app_info.value(); - context_->SetMinSdkVersion(app_info.min_sdk_version.value_or_default(0)); + app_info_ = maybe_app_info.value(); + context_->SetMinSdkVersion(app_info_.min_sdk_version.value_or_default(0)); context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()}); @@ -1647,7 +1686,7 @@ class LinkCommand { // Generate an AndroidManifest.xml for each split. std::unique_ptr<xml::XmlResource> split_manifest = - GenerateSplitManifest(app_info, *split_constraints_iter); + GenerateSplitManifest(app_info_, *split_constraints_iter); XmlReferenceLinker linker; if (!linker.Consume(context_, split_manifest.get())) { @@ -1815,6 +1854,8 @@ class LinkCommand { LinkContext* context_; ResourceTable final_table_; + AppInfo app_info_; + std::unique_ptr<TableMerger> table_merger_; // A pointer to the FileCollection representing the filesystem (not archives). @@ -1830,6 +1871,9 @@ class LinkCommand { // The set of shared libraries being used, mapping their assigned package ID to package name. std::map<size_t, std::string> shared_libs_; + + // The package name of the base application, if it is included. + Maybe<std::string> included_feature_base_; }; int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index d17858d45d08..90fc10add7e6 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -72,7 +72,6 @@ bool ParseSplitParameter(const StringPiece& arg, IDiagnostics* diag, std::string } *out_path = parts[0]; - std::vector<ConfigDescription> configs; for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) { ConfigDescription config; if (!ConfigDescription::Parse(config_str, &config)) { @@ -141,6 +140,31 @@ static xml::NamespaceDecl CreateAndroidNamespaceDecl() { return decl; } +// Returns a copy of 'name' which conforms to the regex '[a-zA-Z]+[a-zA-Z0-9_]*' by +// replacing nonconforming characters with underscores. +// +// See frameworks/base/core/java/android/content/pm/PackageParser.java which +// checks this at runtime. +static std::string MakePackageSafeName(const std::string &name) { + std::string result(name); + bool first = true; + for (char &c : result) { + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + first = false; + continue; + } + if (!first) { + if (c >= '0' && c <= '9') { + continue; + } + } + + c = '_'; + first = false; + } + return result; +} + std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, const SplitConstraints& constraints) { const ResourceId kVersionCode(0x0101021b); @@ -172,7 +196,11 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, if (app_info.split_name) { split_name << app_info.split_name.value() << "."; } - split_name << "config." << util::Joiner(constraints.configs, "_"); + std::vector<std::string> sanitized_config_names; + for (const auto &config : constraints.configs) { + sanitized_config_names.push_back(MakePackageSafeName(config.toString().string())); + } + split_name << "config." << util::Joiner(sanitized_config_names, "_"); manifest_el->attributes.push_back(xml::Attribute{"", "split", split_name.str()}); diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp new file mode 100644 index 000000000000..0c527f6a90dc --- /dev/null +++ b/tools/aapt2/cmd/Util_test.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Util.h" + +#include "AppInfo.h" +#include "split/TableSplitter.h" +#include "test/Test.h" + +namespace aapt { + +TEST(UtilTest, SplitNamesAreSanitized) { + AppInfo app_info{"com.pkg"}; + SplitConstraints split_constraints{ + {test::ParseConfigOrDie("en-rUS-land"), test::ParseConfigOrDie("b+sr+Latn")}}; + + const auto doc = GenerateSplitManifest(app_info, split_constraints); + const auto &root = doc->root; + EXPECT_EQ(root->name, "manifest"); + // split names cannot contain hyphens or plus signs. + EXPECT_EQ(root->FindAttribute("", "split")->value, "config.b_sr_Latn_en_rUS_land"); + // but we should use resource qualifiers verbatim in 'targetConfig'. + EXPECT_EQ(root->FindAttribute("", "targetConfig")->value, "b+sr+Latn,en-rUS-land"); +} + +} // namespace aapt diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp index 857cdd5365a0..73a90da6baf0 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser.cpp +++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp @@ -146,6 +146,10 @@ bool InlineXmlFormatParser::Consume(IAaptContext* context, xml::XmlResource* doc } else { new_doc->root.reset(static_cast<xml::Element*>(child.release())); new_doc->root->parent = nullptr; + // Copy down the namespace declarations + new_doc->root->namespace_decls = doc->root->namespace_decls; + // Recurse for nested inlines + Consume(context, new_doc.get()); } } diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp index de7739ada407..a4c602c29b86 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp +++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp @@ -137,4 +137,47 @@ TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) { EXPECT_THAT(extracted_doc_drawable->root->name, StrEq("vector")); } +TEST(InlineXmlFormatParserTest, ExtractNestedXmlResources) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + <base_root xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="inline_xml"> + <inline_root> + <aapt:attr name="nested_inline_xml"> + <nested_inline_root/> + </aapt:attr> + <aapt:attr name="another_nested_inline_xml"> + <root/> + </aapt:attr> + </inline_root> + </aapt:attr> + <aapt:attr name="turtles"> + <root1> + <aapt:attr name="all"> + <root2> + <aapt:attr name="the"> + <root3> + <aapt:attr name="way"> + <root4> + <aapt:attr name="down"> + <root5/> + </aapt:attr> + </root4> + </aapt:attr> + </root3> + </aapt:attr> + </root2> + </aapt:attr> + </root1> + </aapt:attr> + </base_root>)"); + + doc->file.name = test::ParseNameOrDie("layout/main"); + + InlineXmlFormatParser parser; + ASSERT_TRUE(parser.Consume(context.get(), doc.get())); + // Confirm that all of the nested inline xmls are parsed out. + ASSERT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(8u)); +} } // namespace aapt diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index 14b776b1bd99..32924215ab4e 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -23,6 +23,7 @@ #include "android-base/logging.h" #include "android-base/macros.h" +#include "android-base/stringprintf.h" #include "ResourceTable.h" #include "ResourceValues.h" @@ -572,14 +573,6 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE); table_header->packageCount = util::HostToDevice32(table->packages.size()); - // Write a self mapping entry for this package if the ID is non-standard (0x7f). - if (context->GetPackageType() == PackageType::kApp) { - const uint8_t package_id = context->GetPackageId(); - if (package_id != kFrameworkPackageId && package_id != kAppPackageId) { - table->included_packages_[package_id] = context->GetCompilationPackage(); - } - } - // Flatten the values string pool. StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool); @@ -587,6 +580,22 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { // Flatten each package. for (auto& package : table->packages) { + if (context->GetPackageType() == PackageType::kApp) { + // Write a self mapping entry for this package if the ID is non-standard (0x7f). + const uint8_t package_id = package->id.value(); + if (package_id != kFrameworkPackageId && package_id != kAppPackageId) { + auto result = table->included_packages_.insert({package_id, package->name}); + if (!result.second && result.first->second != package->name) { + // A mapping for this package ID already exists, and is a different package. Error! + context->GetDiagnostics()->Error( + DiagMessage() << android::base::StringPrintf( + "can't map package ID %02x to '%s'. Already mapped to '%s'", package_id, + package->name.c_str(), result.first->second.c_str())); + return false; + } + } + } + PackageFlattener flattener(context, package.get(), &table->included_packages_, options_.use_sparse_entries); if (!flattener.FlattenPackage(&package_buffer)) { diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index cad4c6c7c94f..8981e0718be0 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -21,24 +21,21 @@ #include "Source.h" #include "java/AnnotationProcessor.h" #include "java/ClassDefinition.h" +#include "text/Unicode.h" #include "util/Maybe.h" #include "xml/XmlDom.h" using android::StringPiece; +using ::aapt::text::IsJavaIdentifier; namespace aapt { -static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, - const Source& source, - const StringPiece& value) { - const StringPiece sep = "."; - auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end()); - - StringPiece result; - if (iter != value.end()) { - result.assign(iter + sep.size(), value.end() - (iter + sep.size())); - } else { - result = value; +static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, const Source& source, + const std::string& value) { + StringPiece result = value; + size_t pos = value.rfind('.'); + if (pos != std::string::npos) { + result = result.substr(pos + 1); } if (result.empty()) { @@ -46,19 +43,10 @@ static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, return {}; } - iter = util::FindNonAlphaNumericAndNotInSet(result, "_"); - if (iter != result.end()) { - diag->Error(DiagMessage(source) << "invalid character '" - << StringPiece(iter, 1) << "' in '" - << result << "'"); + if (!IsJavaIdentifier(result)) { + diag->Error(DiagMessage(source) << "invalid Java identifier '" << result << "'"); return {}; } - - if (*result.begin() >= '0' && *result.begin() <= '9') { - diag->Error(DiagMessage(source) << "symbol can not start with a digit"); - return {}; - } - return result; } diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 6fb1793d90ed..de4fb736758c 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -127,9 +127,9 @@ static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) { diag->Error(DiagMessage(el->line_number) << "attribute 'package' in <manifest> tag must not be a reference"); return false; - } else if (!util::IsJavaPackageName(attr->value)) { + } else if (!util::IsAndroidPackageName(attr->value)) { diag->Error(DiagMessage(el->line_number) - << "attribute 'package' in <manifest> tag is not a valid Java package name: '" + << "attribute 'package' in <manifest> tag is not a valid Android package name: '" << attr->value << "'"); return false; } diff --git a/tools/aapt2/text/Unicode.cpp b/tools/aapt2/text/Unicode.cpp index 75eeb46c7f5e..3735b3e841e0 100644 --- a/tools/aapt2/text/Unicode.cpp +++ b/tools/aapt2/text/Unicode.cpp @@ -85,7 +85,8 @@ bool IsJavaIdentifier(const StringPiece& str) { return false; } - if (!IsXidStart(iter.Next())) { + const char32_t first_codepoint = iter.Next(); + if (!IsXidStart(first_codepoint) && first_codepoint != U'_' && first_codepoint != U'$') { return false; } diff --git a/tools/aapt2/text/Unicode_test.cpp b/tools/aapt2/text/Unicode_test.cpp index d47fb282bc0a..a8e797cf3d17 100644 --- a/tools/aapt2/text/Unicode_test.cpp +++ b/tools/aapt2/text/Unicode_test.cpp @@ -44,10 +44,11 @@ TEST(UnicodeTest, IsXidContinue) { TEST(UnicodeTest, IsJavaIdentifier) { EXPECT_TRUE(IsJavaIdentifier("FøøBar_12")); EXPECT_TRUE(IsJavaIdentifier("Føø$Bar")); + EXPECT_TRUE(IsJavaIdentifier("_FøøBar")); + EXPECT_TRUE(IsJavaIdentifier("$Føø$Bar")); EXPECT_FALSE(IsJavaIdentifier("12FøøBar")); - EXPECT_FALSE(IsJavaIdentifier("_FøøBar")); - EXPECT_FALSE(IsJavaIdentifier("$Føø$Bar")); + EXPECT_FALSE(IsJavaIdentifier(".Hello")); } TEST(UnicodeTest, IsValidResourceEntryName) { diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index 51a75d7556ad..7c1c1ad6bb4d 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -24,6 +24,7 @@ #include "androidfw/StringPiece.h" #include "utils/Unicode.h" +#include "text/Unicode.h" #include "text/Utf8Iterator.h" #include "util/BigBuffer.h" #include "util/Maybe.h" @@ -94,72 +95,55 @@ StringPiece TrimWhitespace(const StringPiece& str) { return StringPiece(start, end - start); } -StringPiece::const_iterator FindNonAlphaNumericAndNotInSet( - const StringPiece& str, const StringPiece& allowed_chars) { - const auto end_iter = str.end(); - for (auto iter = str.begin(); iter != end_iter; ++iter) { - char c = *iter; - if ((c >= u'a' && c <= u'z') || (c >= u'A' && c <= u'Z') || - (c >= u'0' && c <= u'9')) { - continue; - } - - bool match = false; - for (char i : allowed_chars) { - if (c == i) { - match = true; - break; - } - } - - if (!match) { - return iter; +static int IsJavaNameImpl(const StringPiece& str) { + int pieces = 0; + for (const StringPiece& piece : Tokenize(str, '.')) { + pieces++; + if (!text::IsJavaIdentifier(piece)) { + return -1; } } - return end_iter; + return pieces; } bool IsJavaClassName(const StringPiece& str) { - size_t pieces = 0; - for (const StringPiece& piece : Tokenize(str, '.')) { - pieces++; - if (piece.empty()) { - return false; - } - - // Can't have starting or trailing $ character. - if (piece.data()[0] == '$' || piece.data()[piece.size() - 1] == '$') { - return false; - } - - if (FindNonAlphaNumericAndNotInSet(piece, "$_") != piece.end()) { - return false; - } - } - return pieces >= 2; + return IsJavaNameImpl(str) >= 2; } bool IsJavaPackageName(const StringPiece& str) { - if (str.empty()) { - return false; - } + return IsJavaNameImpl(str) >= 1; +} - size_t pieces = 0; +static int IsAndroidNameImpl(const StringPiece& str) { + int pieces = 0; for (const StringPiece& piece : Tokenize(str, '.')) { - pieces++; if (piece.empty()) { - return false; + return -1; } - if (piece.data()[0] == '_' || piece.data()[piece.size() - 1] == '_') { - return false; + const char first_character = piece.data()[0]; + if (!::isalpha(first_character)) { + return -1; } - if (FindNonAlphaNumericAndNotInSet(piece, "_") != piece.end()) { - return false; + bool valid = std::all_of(piece.begin() + 1, piece.end(), [](const char c) -> bool { + return ::isalnum(c) || c == '_'; + }); + + if (!valid) { + return -1; } + pieces++; } - return pieces >= 1; + return pieces; +} + +bool IsAndroidPackageName(const StringPiece& str) { + return IsAndroidNameImpl(str) > 1 || str == "android"; +} + +bool IsAndroidSplitName(const StringPiece& str) { + return IsAndroidNameImpl(str) > 0; } Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package, @@ -176,7 +160,7 @@ Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package, return {}; } - std::string result(package.data(), package.size()); + std::string result = package.to_string(); if (classname.data()[0] != '.') { result += '.'; } diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 8f021ab8cb8a..f12746fe4bfc 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -53,48 +53,40 @@ struct Range { std::vector<std::string> Split(const android::StringPiece& str, char sep); std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep); -/** - * Returns true if the string starts with prefix. - */ +// Returns true if the string starts with prefix. bool StartsWith(const android::StringPiece& str, const android::StringPiece& prefix); -/** - * Returns true if the string ends with suffix. - */ +// Returns true if the string ends with suffix. bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffix); -/** - * Creates a new StringPiece16 that points to a substring - * of the original string without leading or trailing whitespace. - */ +// Creates a new StringPiece16 that points to a substring of the original string without leading or +// trailing whitespace. android::StringPiece TrimWhitespace(const android::StringPiece& str); -/** - * Returns an iterator to the first character that is not alpha-numeric and that - * is not in the allowedChars set. - */ -android::StringPiece::const_iterator FindNonAlphaNumericAndNotInSet( - const android::StringPiece& str, const android::StringPiece& allowed_chars); - -/** - * Tests that the string is a valid Java class name. - */ +// Tests that the string is a valid Java class name. bool IsJavaClassName(const android::StringPiece& str); -/** - * Tests that the string is a valid Java package name. - */ +// Tests that the string is a valid Java package name. bool IsJavaPackageName(const android::StringPiece& str); -/** - * Converts the class name to a fully qualified class name from the given - * `package`. Ex: - * - * asdf --> package.asdf - * .asdf --> package.asdf - * .a.b --> package.a.b - * asdf.adsf --> asdf.adsf - */ +// Tests that the string is a valid Android package name. More strict than a Java package name. +// - First character of each component (separated by '.') must be an ASCII letter. +// - Subsequent characters of a component can be ASCII alphanumeric or an underscore. +// - Package must contain at least two components, unless it is 'android'. +bool IsAndroidPackageName(const android::StringPiece& str); + +// Tests that the string is a valid Android split name. +// - First character of each component (separated by '.') must be an ASCII letter. +// - Subsequent characters of a component can be ASCII alphanumeric or an underscore. +bool IsAndroidSplitName(const android::StringPiece& str); + +// Converts the class name to a fully qualified class name from the given +// `package`. Ex: +// +// asdf --> package.asdf +// .asdf --> package.asdf +// .a.b --> package.a.b +// asdf.adsf --> asdf.adsf Maybe<std::string> GetFullyQualifiedClassName(const android::StringPiece& package, const android::StringPiece& class_name); @@ -108,23 +100,17 @@ typename std::enable_if<std::is_arithmetic<T>::value, int>::type compare(const T return 0; } -/** - * Makes a std::unique_ptr<> with the template parameter inferred by the compiler. - * This will be present in C++14 and can be removed then. - */ +// Makes a std::unique_ptr<> with the template parameter inferred by the compiler. +// This will be present in C++14 and can be removed then. template <typename T, class... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T>(new T{std::forward<Args>(args)...}); } -/** - * Writes a set of items to the std::ostream, joining the times with the - * provided - * separator. - */ +// Writes a set of items to the std::ostream, joining the times with the provided separator. template <typename Container> -::std::function<::std::ostream&(::std::ostream&)> Joiner( - const Container& container, const char* sep) { +::std::function<::std::ostream&(::std::ostream&)> Joiner(const Container& container, + const char* sep) { using std::begin; using std::end; const auto begin_iter = begin(container); @@ -140,32 +126,19 @@ template <typename Container> }; } -/** - * Helper method to extract a UTF-16 string from a StringPool. If the string is - * stored as UTF-8, - * the conversion to UTF-16 happens within ResStringPool. - */ +// Helper method to extract a UTF-16 string from a StringPool. If the string is stored as UTF-8, +// the conversion to UTF-16 happens within ResStringPool. android::StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx); -/** - * Helper method to extract a UTF-8 string from a StringPool. If the string is - * stored as UTF-16, - * the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is - * done by this method, - * which maintains no state or cache. This means we must return an std::string - * copy. - */ +// Helper method to extract a UTF-8 string from a StringPool. If the string is stored as UTF-16, +// the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is done by this method, +// which maintains no state or cache. This means we must return an std::string copy. std::string GetString(const android::ResStringPool& pool, size_t idx); -/** - * Checks that the Java string format contains no non-positional arguments - * (arguments without - * explicitly specifying an index) when there are more than one argument. This - * is an error - * because translations may rearrange the order of the arguments in the string, - * which will - * break the string interpolation. - */ +// Checks that the Java string format contains no non-positional arguments (arguments without +// explicitly specifying an index) when there are more than one argument. This is an error +// because translations may rearrange the order of the arguments in the string, which will +// break the string interpolation. bool VerifyJavaStringFormat(const android::StringPiece& str); class StringBuilder { @@ -194,36 +167,38 @@ class StringBuilder { std::string error_; }; -inline const std::string& StringBuilder::ToString() const { return str_; } +inline const std::string& StringBuilder::ToString() const { + return str_; +} -inline const std::string& StringBuilder::Error() const { return error_; } +inline const std::string& StringBuilder::Error() const { + return error_; +} -inline bool StringBuilder::IsEmpty() const { return str_.empty(); } +inline bool StringBuilder::IsEmpty() const { + return str_.empty(); +} -inline size_t StringBuilder::Utf16Len() const { return utf16_len_; } +inline size_t StringBuilder::Utf16Len() const { + return utf16_len_; +} -inline StringBuilder::operator bool() const { return error_.empty(); } +inline StringBuilder::operator bool() const { + return error_.empty(); +} -/** - * Converts a UTF8 string to a UTF16 string. - */ +// Converts a UTF8 string to a UTF16 string. std::u16string Utf8ToUtf16(const android::StringPiece& utf8); std::string Utf16ToUtf8(const android::StringPiece16& utf16); -/** - * Writes the entire BigBuffer to the output stream. - */ +// Writes the entire BigBuffer to the output stream. bool WriteAll(std::ostream& out, const BigBuffer& buffer); -/* - * Copies the entire BigBuffer into a single buffer. - */ +// Copies the entire BigBuffer into a single buffer. std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer); -/** - * A Tokenizer implemented as an iterable collection. It does not allocate - * any memory on the heap nor use standard containers. - */ +// A Tokenizer implemented as an iterable collection. It does not allocate any memory on the heap +// nor use standard containers. class Tokenizer { public: class iterator { @@ -269,38 +244,42 @@ class Tokenizer { const iterator end_; }; -inline Tokenizer Tokenize(const android::StringPiece& str, char sep) { return Tokenizer(str, sep); } +inline Tokenizer Tokenize(const android::StringPiece& str, char sep) { + return Tokenizer(str, sep); +} -inline uint16_t HostToDevice16(uint16_t value) { return htods(value); } +inline uint16_t HostToDevice16(uint16_t value) { + return htods(value); +} -inline uint32_t HostToDevice32(uint32_t value) { return htodl(value); } +inline uint32_t HostToDevice32(uint32_t value) { + return htodl(value); +} -inline uint16_t DeviceToHost16(uint16_t value) { return dtohs(value); } +inline uint16_t DeviceToHost16(uint16_t value) { + return dtohs(value); +} -inline uint32_t DeviceToHost32(uint32_t value) { return dtohl(value); } +inline uint32_t DeviceToHost32(uint32_t value) { + return dtohl(value); +} -/** - * Given a path like: res/xml-sw600dp/foo.xml - * - * Extracts "res/xml-sw600dp/" into outPrefix. - * Extracts "foo" into outEntry. - * Extracts ".xml" into outSuffix. - * - * Returns true if successful. - */ +// Given a path like: res/xml-sw600dp/foo.xml +// +// Extracts "res/xml-sw600dp/" into outPrefix. +// Extracts "foo" into outEntry. +// Extracts ".xml" into outSuffix. +// +// Returns true if successful. bool ExtractResFilePathParts(const android::StringPiece& path, android::StringPiece* out_prefix, android::StringPiece* out_entry, android::StringPiece* out_suffix); } // namespace util -/** - * Stream operator for functions. Calls the function with the stream as an - * argument. - * In the aapt namespace for lookup. - */ -inline ::std::ostream& operator<<( - ::std::ostream& out, - const ::std::function<::std::ostream&(::std::ostream&)>& f) { +// Stream operator for functions. Calls the function with the stream as an argument. +// In the aapt namespace for lookup. +inline ::std::ostream& operator<<(::std::ostream& out, + const ::std::function<::std::ostream&(::std::ostream&)>& f) { return f(out); } diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index adb52911ab82..2d1242ada949 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -117,24 +117,46 @@ TEST(UtilTest, IsJavaClassName) { EXPECT_TRUE(util::IsJavaClassName("android.test.Class$Inner")); EXPECT_TRUE(util::IsJavaClassName("android_test.test.Class")); EXPECT_TRUE(util::IsJavaClassName("_android_.test._Class_")); - EXPECT_FALSE(util::IsJavaClassName("android.test.$Inner")); - EXPECT_FALSE(util::IsJavaClassName("android.test.Inner$")); + EXPECT_TRUE(util::IsJavaClassName("android.test.$Inner")); + EXPECT_TRUE(util::IsJavaClassName("android.test.Inner$")); + EXPECT_TRUE(util::IsJavaClassName("com.foo.FøøBar")); + EXPECT_FALSE(util::IsJavaClassName(".test.Class")); EXPECT_FALSE(util::IsJavaClassName("android")); + EXPECT_FALSE(util::IsJavaClassName("FooBar")); } TEST(UtilTest, IsJavaPackageName) { EXPECT_TRUE(util::IsJavaPackageName("android")); EXPECT_TRUE(util::IsJavaPackageName("android.test")); EXPECT_TRUE(util::IsJavaPackageName("android.test_thing")); - EXPECT_FALSE(util::IsJavaPackageName("_android")); - EXPECT_FALSE(util::IsJavaPackageName("android_")); + EXPECT_TRUE(util::IsJavaPackageName("_android")); + EXPECT_TRUE(util::IsJavaPackageName("android_")); + EXPECT_TRUE(util::IsJavaPackageName("android._test")); + EXPECT_TRUE(util::IsJavaPackageName("cøm.foo")); + EXPECT_FALSE(util::IsJavaPackageName("android.")); EXPECT_FALSE(util::IsJavaPackageName(".android")); - EXPECT_FALSE(util::IsJavaPackageName("android._test")); EXPECT_FALSE(util::IsJavaPackageName("..")); } +TEST(UtilTest, IsAndroidPackageName) { + EXPECT_TRUE(util::IsAndroidPackageName("android")); + EXPECT_TRUE(util::IsAndroidPackageName("android.test")); + EXPECT_TRUE(util::IsAndroidPackageName("com.foo")); + EXPECT_TRUE(util::IsAndroidPackageName("com.foo.test_thing")); + EXPECT_TRUE(util::IsAndroidPackageName("com.foo.testing_thing_")); + EXPECT_TRUE(util::IsAndroidPackageName("com.foo.test_99_")); + + EXPECT_FALSE(util::IsAndroidPackageName("android._test")); + EXPECT_FALSE(util::IsAndroidPackageName("com")); + EXPECT_FALSE(util::IsAndroidPackageName("_android")); + EXPECT_FALSE(util::IsAndroidPackageName("android.")); + EXPECT_FALSE(util::IsAndroidPackageName(".android")); + EXPECT_FALSE(util::IsAndroidPackageName("..")); + EXPECT_FALSE(util::IsAndroidPackageName("cøm.foo")); +} + TEST(UtilTest, FullyQualifiedClassName) { EXPECT_THAT(util::GetFullyQualifiedClassName("android", ".asdf"), Eq("android.asdf")); EXPECT_THAT(util::GetFullyQualifiedClassName("android", ".a.b"), Eq("android.a.b")); |