Allow private methods in interfaces.
Private methods may be generated in interfaces during compilation of
some default methods. Change the verifier to allow these methods.
Bug: 27999840
(cherry picked from commit b55f1ac873f9541f391625c13fe9129fbd38e74c)
Change-Id: Ib8120a8f6cb036021334d9af0ed78ae372974ecb
diff --git a/runtime/dex_file.h b/runtime/dex_file.h
index 3a28422..ce7f62a 100644
--- a/runtime/dex_file.h
+++ b/runtime/dex_file.h
@@ -57,6 +57,7 @@
// TODO: move all of the macro functionality into the DexCache class.
class DexFile {
public:
+ static const uint32_t kDefaultMethodsVersion = 37;
static const uint8_t kDexMagic[];
static constexpr size_t kNumDexVersions = 2;
static constexpr size_t kDexVersionLen = 4;
diff --git a/runtime/dex_file_verifier.cc b/runtime/dex_file_verifier.cc
index 681c5f9..3df4e98 100644
--- a/runtime/dex_file_verifier.cc
+++ b/runtime/dex_file_verifier.cc
@@ -2465,7 +2465,7 @@
GetFieldDescriptionOrError(begin_, header_, idx).c_str(),
field_access_flags,
PrettyJavaAccessFlags(field_access_flags).c_str());
- if (header_->GetVersion() >= 37) {
+ if (header_->GetVersion() >= DexFile::kDefaultMethodsVersion) {
return false;
} else {
// Allow in older versions, but warn.
@@ -2480,7 +2480,7 @@
GetFieldDescriptionOrError(begin_, header_, idx).c_str(),
field_access_flags,
PrettyJavaAccessFlags(field_access_flags).c_str());
- if (header_->GetVersion() >= 37) {
+ if (header_->GetVersion() >= DexFile::kDefaultMethodsVersion) {
return false;
} else {
// Allow in older versions, but warn.
@@ -2628,12 +2628,16 @@
// Interfaces are special.
if ((class_access_flags & kAccInterface) != 0) {
- // Non-static interface methods must be public.
- if ((method_access_flags & (kAccPublic | kAccStatic)) == 0) {
+ // Non-static interface methods must be public or private.
+ uint32_t desired_flags = (kAccPublic | kAccStatic);
+ if (dex_file_->GetVersion() >= DexFile::kDefaultMethodsVersion) {
+ desired_flags |= kAccPrivate;
+ }
+ if ((method_access_flags & desired_flags) == 0) {
*error_msg = StringPrintf("Interface virtual method %" PRIu32 "(%s) is not public",
method_index,
GetMethodDescriptionOrError(begin_, header_, method_index).c_str());
- if (header_->GetVersion() >= 37) {
+ if (header_->GetVersion() >= DexFile::kDefaultMethodsVersion) {
return false;
} else {
// Allow in older versions, but warn.
@@ -2686,7 +2690,7 @@
*error_msg = StringPrintf("Interface method %" PRIu32 "(%s) is not public and abstract",
method_index,
GetMethodDescriptionOrError(begin_, header_, method_index).c_str());
- if (header_->GetVersion() >= 37) {
+ if (header_->GetVersion() >= DexFile::kDefaultMethodsVersion) {
return false;
} else {
// Allow in older versions, but warn.
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 83da6b7..d5319fd 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -790,9 +790,16 @@
} else if (method_access_flags_ & kAccFinal) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have final methods";
return false;
- } else if (!(method_access_flags_ & kAccPublic)) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have non-public members";
- return false;
+ } else {
+ uint32_t access_flag_options = kAccPublic;
+ if (dex_file_->GetVersion() >= DexFile::kDefaultMethodsVersion) {
+ access_flag_options |= kAccPrivate;
+ }
+ if (!(method_access_flags_ & access_flag_options)) {
+ Fail(VERIFY_ERROR_BAD_CLASS_HARD)
+ << "interfaces may not have protected or package-private members";
+ return false;
+ }
}
}
}
@@ -3794,9 +3801,12 @@
// Note: this check must be after the initializer check, as those are required to fail a class,
// while this check implies an IncompatibleClassChangeError.
if (klass->IsInterface()) {
- // methods called on interfaces should be invoke-interface, invoke-super, or invoke-static.
+ // methods called on interfaces should be invoke-interface, invoke-super, invoke-direct (if
+ // dex file version is 37 or greater), or invoke-static.
if (method_type != METHOD_INTERFACE &&
method_type != METHOD_STATIC &&
+ ((dex_file_->GetVersion() < DexFile::kDefaultMethodsVersion) ||
+ method_type != METHOD_DIRECT) &&
method_type != METHOD_SUPER) {
Fail(VERIFY_ERROR_CLASS_CHANGE)
<< "non-interface method " << PrettyMethod(dex_method_idx, *dex_file_)
diff --git a/test/955-lambda-smali/build b/test/955-lambda-smali/build
new file mode 100755
index 0000000..14230c2
--- /dev/null
+++ b/test/955-lambda-smali/build
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+
+# make us exit on a failure
+set -e
+
+./default-build "$@" --experimental default-methods
diff --git a/test/975-iface-private/build b/test/975-iface-private/build
new file mode 100755
index 0000000..14230c2
--- /dev/null
+++ b/test/975-iface-private/build
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2015 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.
+
+# make us exit on a failure
+set -e
+
+./default-build "$@" --experimental default-methods
diff --git a/test/975-iface-private/expected.txt b/test/975-iface-private/expected.txt
new file mode 100644
index 0000000..908a8f2
--- /dev/null
+++ b/test/975-iface-private/expected.txt
@@ -0,0 +1,4 @@
+Saying hi from class
+HELLO!
+Saying hi from interface
+HELLO!
diff --git a/test/975-iface-private/info.txt b/test/975-iface-private/info.txt
new file mode 100644
index 0000000..d5a8d3f
--- /dev/null
+++ b/test/975-iface-private/info.txt
@@ -0,0 +1,5 @@
+Smali-based tests for experimental interface private methods.
+
+This test cannot be run with --jvm.
+
+This test checks that synthetic private methods in interfaces work correctly.
diff --git a/test/975-iface-private/smali/Iface.smali b/test/975-iface-private/smali/Iface.smali
new file mode 100644
index 0000000..a9a44d1
--- /dev/null
+++ b/test/975-iface-private/smali/Iface.smali
@@ -0,0 +1,45 @@
+
+# /*
+# * Copyright (C) 2015 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.
+# */
+#
+# public interface Iface {
+# public default void sayHi() {
+# System.out.println(getHiWords());
+# }
+#
+# // Synthetic method
+# private String getHiWords() {
+# return "HELLO!";
+# }
+# }
+
+.class public abstract interface LIface;
+.super Ljava/lang/Object;
+
+.method public sayHi()V
+ .locals 2
+ sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
+ invoke-direct {p0}, LIface;->getHiWords()Ljava/lang/String;
+ move-result-object v1
+ invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
+ return-void
+.end method
+
+.method private synthetic getHiWords()Ljava/lang/String;
+ .locals 1
+ const-string v0, "HELLO!"
+ return-object v0
+.end method
diff --git a/test/975-iface-private/smali/Main.smali b/test/975-iface-private/smali/Main.smali
new file mode 100644
index 0000000..dbde203
--- /dev/null
+++ b/test/975-iface-private/smali/Main.smali
@@ -0,0 +1,71 @@
+# /*
+# * Copyright (C) 2015 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.
+# */
+#
+# class Main implements Iface {
+# public static void main(String[] args) {
+# Main m = new Main();
+# sayHiMain(m);
+# sayHiIface(m);
+# }
+# public static void sayHiMain(Main m) {
+# System.out.println("Saying hi from class");
+# m.sayHi();
+# }
+# public static void sayHiIface(Iface m) {
+# System.out.println("Saying hi from interface");
+# m.sayHi();
+# }
+# }
+.class public LMain;
+.super Ljava/lang/Object;
+.implements LIface;
+
+.method public constructor <init>()V
+ .registers 1
+ invoke-direct {p0}, Ljava/lang/Object;-><init>()V
+ return-void
+.end method
+
+.method public static main([Ljava/lang/String;)V
+ .locals 2
+ new-instance v0, LMain;
+ invoke-direct {v0}, LMain;-><init>()V
+
+ invoke-static {v0}, LMain;->sayHiMain(LMain;)V
+ invoke-static {v0}, LMain;->sayHiIface(LIface;)V
+
+ return-void
+.end method
+
+.method public static sayHiMain(LMain;)V
+ .locals 2
+ sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
+ const-string v1, "Saying hi from class"
+ invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
+
+ invoke-virtual {p0}, LMain;->sayHi()V
+ return-void
+.end method
+
+.method public static sayHiIface(LIface;)V
+ .locals 2
+ sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
+ const-string v1, "Saying hi from interface"
+ invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V
+
+ invoke-interface {p0}, LIface;->sayHi()V
+ return-void
+.end method
diff --git a/test/etc/default-build b/test/etc/default-build
index 3d84821..962ae38 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -69,10 +69,13 @@
JACK_EXPERIMENTAL_ARGS["default-methods"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24"
JACK_EXPERIMENTAL_ARGS["lambdas"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24"
+declare -A SMALI_EXPERIMENTAL_ARGS
+SMALI_EXPERIMENTAL_ARGS["default-methods"]="--api-level 24"
+
while true; do
if [ "x$1" = "x--dx-option" ]; then
shift
- option="$1"
+ on="$1"
DX_FLAGS="${DX_FLAGS} $option"
shift
elif [ "x$1" = "x--jvm" ]; then
@@ -110,6 +113,7 @@
# Add args from the experimental mappings.
for experiment in ${EXPERIMENTAL}; do
JACK_ARGS="${JACK_ARGS} ${JACK_EXPERIMENTAL_ARGS[${experiment}]}"
+ SMALI_ARGS="${SMALI_ARGS} ${SMALI_EXPERIMENTAL_ARGS[${experiment}]}"
done
if [ -e classes.dex ]; then
diff --git a/test/run-test b/test/run-test
index 01464cd..013fc63 100755
--- a/test/run-test
+++ b/test/run-test
@@ -46,7 +46,7 @@
export DEX_LOCATION=/data/run-test/${test_dir}
export NEED_DEX="true"
export USE_JACK="true"
-export SMALI_ARGS="--experimental --api-level 23"
+export SMALI_ARGS="--experimental"
# If dx was not set by the environment variable, assume it is in the path.
if [ -z "$DX" ]; then