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 \