diff options
| -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 |
6 files changed, 152 insertions, 165 deletions
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 a9b49d9d9904..e42145dff47e 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 c928458786e4..7c949b90c10a 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")); |