Allow space characters in SimpleName for DEX format 040.
am: ab5f4c17a8
Change-Id: I9c8fbc52cf1926320295e029ea553b17b2596054
diff --git a/libdexfile/dex/descriptors_names.cc b/libdexfile/dex/descriptors_names.cc
index 1e8eb33..44cb7cb 100644
--- a/libdexfile/dex/descriptors_names.cc
+++ b/libdexfile/dex/descriptors_names.cc
@@ -165,7 +165,7 @@
// Helper for IsValidPartOfMemberNameUtf8(), a bit vector indicating valid low ascii.
static constexpr uint32_t DEX_MEMBER_VALID_LOW_ASCII[4] = {
0x00000000, // 00..1f low control characters; nothing valid
- 0x03ff2010, // 20..3f digits and symbols; valid: '0'..'9', '$', '-'
+ 0x03ff2011, // 20..3f space, digits and symbols; valid: ' ', '0'..'9', '$', '-'
0x87fffffe, // 40..5f uppercase etc.; valid: 'A'..'Z', '_'
0x07fffffe // 60..7f lowercase etc.; valid: 'a'..'z'
};
@@ -175,12 +175,17 @@
static bool IsValidPartOfMemberNameUtf8Slow(const char** pUtf8Ptr) {
/*
* It's a multibyte encoded character. Decode it and analyze. We
- * accept anything that isn't (a) an improperly encoded low value,
- * (b) an improper surrogate pair, (c) an encoded '\0', (d) a high
- * control character, or (e) a high space, layout, or special
- * character (U+00a0, U+2000..U+200f, U+2028..U+202f,
- * U+fff0..U+ffff). This is all specified in the dex format
- * document.
+ * accept anything that isn't:
+ * - an improperly encoded low value
+ * - an improper surrogate pair
+ * - an encoded '\0'
+ * - a C1 control character U+0080..U+009f
+ * - a format character U+200b..U+200f, U+2028..U+202e
+ * - a special character U+fff0..U+ffff
+ * Prior to DEX format version 040, we also excluded some of the Unicode
+ * space characters:
+ * - U+00a0, U+2000..U+200a, U+202f
+ * This is all specified in the dex format document.
*/
const uint32_t pair = GetUtf16FromUtf8(pUtf8Ptr);
@@ -200,8 +205,8 @@
// three byte UTF-8 sequence could be one half of a surrogate pair.
switch (leading >> 8) {
case 0x00:
- // It's only valid if it's above the ISO-8859-1 high space (0xa0).
- return (leading > 0x00a0);
+ // It's in the range that has C1 control characters.
+ return (leading >= 0x00a0);
case 0xd8:
case 0xd9:
case 0xda:
@@ -222,11 +227,12 @@
return false;
case 0x20:
case 0xff:
- // It's in the range that has spaces, controls, and specials.
+ // It's in the range that has format characters and specials.
switch (leading & 0xfff8) {
- case 0x2000:
case 0x2008:
+ return (leading <= 0x200a);
case 0x2028:
+ return (leading == 0x202f);
case 0xfff0:
case 0xfff8:
return false;
diff --git a/libdexfile/dex/dex_file_loader_test.cc b/libdexfile/dex/dex_file_loader_test.cc
index 8b7ca17..30c60b1 100644
--- a/libdexfile/dex/dex_file_loader_test.cc
+++ b/libdexfile/dex/dex_file_loader_test.cc
@@ -336,23 +336,13 @@
EXPECT_EQ(39u, header.GetVersion());
}
-TEST_F(DexFileLoaderTest, Version40Rejected) {
+TEST_F(DexFileLoaderTest, Version40Accepted) {
std::vector<uint8_t> dex_bytes;
- DecodeDexFile(kRawDex40, &dex_bytes);
+ std::unique_ptr<const DexFile> raw(OpenDexFileBase64(kRawDex40, kLocationString, &dex_bytes));
+ ASSERT_TRUE(raw.get() != nullptr);
- static constexpr bool kVerifyChecksum = true;
- DexFileLoaderErrorCode error_code;
- std::string error_msg;
- std::vector<std::unique_ptr<const DexFile>> dex_files;
- const DexFileLoader dex_file_loader;
- ASSERT_FALSE(dex_file_loader.OpenAll(dex_bytes.data(),
- dex_bytes.size(),
- kLocationString,
- /* verify= */ true,
- kVerifyChecksum,
- &error_code,
- &error_msg,
- &dex_files));
+ const DexFile::Header& header = raw->GetHeader();
+ EXPECT_EQ(40u, header.GetVersion());
}
TEST_F(DexFileLoaderTest, Version41Rejected) {
diff --git a/libdexfile/dex/standard_dex_file.cc b/libdexfile/dex/standard_dex_file.cc
index 8bac44e..9c4cb45 100644
--- a/libdexfile/dex/standard_dex_file.cc
+++ b/libdexfile/dex/standard_dex_file.cc
@@ -32,8 +32,10 @@
{'0', '3', '7', '\0'},
// Dex version 038: Android "O" and beyond.
{'0', '3', '8', '\0'},
- // Dex verion 039: Beyond Android "O".
+ // Dex verion 039: Android "P" and beyond.
{'0', '3', '9', '\0'},
+ // Dex verion 040: beyond Android "10" (previously known as Android "Q").
+ {'0', '4', '0', '\0'},
};
void StandardDexFile::WriteMagic(uint8_t* magic) {
diff --git a/libdexfile/dex/standard_dex_file.h b/libdexfile/dex/standard_dex_file.h
index 48671c9..db82a9b 100644
--- a/libdexfile/dex/standard_dex_file.h
+++ b/libdexfile/dex/standard_dex_file.h
@@ -68,7 +68,7 @@
static void WriteCurrentVersion(uint8_t* magic);
static const uint8_t kDexMagic[kDexMagicSize];
- static constexpr size_t kNumDexVersions = 4;
+ static constexpr size_t kNumDexVersions = 5;
static const uint8_t kDexMagicVersions[kNumDexVersions][kDexVersionLen];
// Returns true if the byte string points to the magic value.
diff --git a/test/2029-spaces-in-SimpleName/build b/test/2029-spaces-in-SimpleName/build
new file mode 100755
index 0000000..9c3cc79
--- /dev/null
+++ b/test/2029-spaces-in-SimpleName/build
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+
+# Stop on failure and be verbose.
+set -e -x
+
+export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-6.0.jar"
+
+cd src
+
+# generate Java bytecode with ASM
+${JAVAC:-java} -cp "$ASM_JAR:." SpacesInSimpleName.java
+${JAVA:-java} -cp "$ASM_JAR:." SpacesInSimpleName
+
+# compile Java bytecode to DEX bytecode
+# TODO: replace DX with D8 when it adds support for spaces in SimpleName
+# ${D8} --min-api 10000 Main.class
+$ANDROID_HOST_OUT/bin/dx --dex --output=classes.dex Main.class
+
+# move the resulting DEX file and cleanup
+mv classes.dex ../classes.dex
+rm *.class
+
+cd ..
+
+# Use API level 10000 for spaces in SimpleName
+DESUGAR=false ./default-build "$@" --api-level 10000
diff --git a/test/2029-spaces-in-SimpleName/classes.dex b/test/2029-spaces-in-SimpleName/classes.dex
new file mode 100644
index 0000000..3804ca7
--- /dev/null
+++ b/test/2029-spaces-in-SimpleName/classes.dex
Binary files differ
diff --git a/test/2029-spaces-in-SimpleName/expected.txt b/test/2029-spaces-in-SimpleName/expected.txt
new file mode 100644
index 0000000..af5626b
--- /dev/null
+++ b/test/2029-spaces-in-SimpleName/expected.txt
@@ -0,0 +1 @@
+Hello, world!
diff --git a/test/2029-spaces-in-SimpleName/info.txt b/test/2029-spaces-in-SimpleName/info.txt
new file mode 100644
index 0000000..106ebeb
--- /dev/null
+++ b/test/2029-spaces-in-SimpleName/info.txt
@@ -0,0 +1,5 @@
+Whitespace support in DEX format 040.
+
+This test uses ASM Java bytecode generator to generate a simple class with
+the methods 'main' and some unpronounceable method which name contains all
+space characters in the Unicode 'Zs' category.
diff --git a/test/2029-spaces-in-SimpleName/src/SpacesInSimpleName.java b/test/2029-spaces-in-SimpleName/src/SpacesInSimpleName.java
new file mode 100644
index 0000000..847da5a
--- /dev/null
+++ b/test/2029-spaces-in-SimpleName/src/SpacesInSimpleName.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+import java.io.*;
+
+import org.objectweb.asm.*;
+
+public class SpacesInSimpleName {
+ public static void main(String args[]) throws Exception {
+ String methodName = "method_with_spaces_"
+ + "20 "
+ + "a0\u00a0"
+ + "1680\u1680"
+ + "2000\u2000"
+ + "2001\u2001"
+ + "2002\u2002"
+ + "2003\u2003"
+ + "2004\u2004"
+ + "2005\u2005"
+ + "2006\u2006"
+ + "2007\u2007"
+ + "2008\u2008"
+ + "2009\u2009"
+ + "200a\u200a"
+ + "202f\u202f"
+ + "205f\u205f"
+ + "3000\u3000";
+
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+
+ cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Main",
+ null, "java/lang/Object", null);
+
+ MethodVisitor mvMain = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
+ "main", "([Ljava/lang/String;)V", null, null);
+ mvMain.visitCode();
+ mvMain.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
+ "Ljava/io/PrintStream;");
+ mvMain.visitLdcInsn("Hello, world!");
+ mvMain.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
+ "println", "(Ljava/lang/String;)V", false);
+ mvMain.visitMethodInsn(Opcodes.INVOKESTATIC, "Main", methodName, "()V", false);
+ mvMain.visitInsn(Opcodes.RETURN);
+ mvMain.visitMaxs(0, 0); // args are ignored with COMPUTE_MAXS
+ mvMain.visitEnd();
+ MethodVisitor mvSpaces = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
+ methodName, "()V", null, null);
+ mvSpaces.visitCode();
+ mvSpaces.visitInsn(Opcodes.RETURN);
+ mvSpaces.visitMaxs(0, 0); // args are ignored with COMPUTE_MAXS
+ mvSpaces.visitEnd();
+
+ cw.visitEnd();
+
+ byte[] b = cw.toByteArray();
+ OutputStream out = new FileOutputStream("Main.class");
+ out.write(b, 0, b.length);
+ out.close();
+ }
+}
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index 7b9c083..2b8d8ef 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -19,6 +19,7 @@
# Dependencies for actually running a run-test.
TEST_ART_RUN_TEST_DEPENDENCIES := \
+ $(HOST_OUT_EXECUTABLES)/dx \
$(HOST_OUT_EXECUTABLES)/d8 \
$(HOST_OUT_EXECUTABLES)/d8-compat-dx \
$(HOST_OUT_EXECUTABLES)/hiddenapi \