diff options
| author | 2016-04-12 15:50:55 -0700 | |
|---|---|---|
| committer | 2016-04-13 21:12:42 +0000 | |
| commit | b55f1ac873f9541f391625c13fe9129fbd38e74c (patch) | |
| tree | 996e696a06437f93c6f87e773e76d9e3c6a4fe8a | |
| parent | 336dd6a0989dafb356be5f689028d983b0931335 (diff) | |
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
Change-Id: Ib8120a8f6cb036021334d9af0ed78ae372974ecb
| -rw-r--r-- | runtime/dex_file.h | 1 | ||||
| -rw-r--r-- | runtime/dex_file_verifier.cc | 16 | ||||
| -rw-r--r-- | runtime/verifier/method_verifier.cc | 18 | ||||
| -rwxr-xr-x | test/955-lambda-smali/build | 20 | ||||
| -rwxr-xr-x | test/975-iface-private/build | 20 | ||||
| -rw-r--r-- | test/975-iface-private/expected.txt | 4 | ||||
| -rw-r--r-- | test/975-iface-private/info.txt | 5 | ||||
| -rw-r--r-- | test/975-iface-private/smali/Iface.smali | 45 | ||||
| -rw-r--r-- | test/975-iface-private/smali/Main.smali | 71 | ||||
| -rwxr-xr-x | test/etc/default-build | 6 | ||||
| -rwxr-xr-x | test/run-test | 2 |
11 files changed, 196 insertions, 12 deletions
diff --git a/runtime/dex_file.h b/runtime/dex_file.h index 3a28422067..ce7f62acb5 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -57,6 +57,7 @@ class ZipArchive; // 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 681c5f977f..3df4e98c84 100644 --- a/runtime/dex_file_verifier.cc +++ b/runtime/dex_file_verifier.cc @@ -2465,7 +2465,7 @@ bool DexFileVerifier::CheckFieldAccessFlags(uint32_t idx, 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 @@ bool DexFileVerifier::CheckFieldAccessFlags(uint32_t idx, 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 @@ bool DexFileVerifier::CheckMethodAccessFlags(uint32_t method_index, // 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 @@ bool DexFileVerifier::CheckMethodAccessFlags(uint32_t method_index, *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 83da6b7b4e..d5319fdb0c 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -790,9 +790,16 @@ bool MethodVerifier::Verify() { } 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 @@ ArtMethod* MethodVerifier::ResolveMethodAndCheckAccess( // 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 0000000000..14230c2e1d --- /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 0000000000..14230c2e1d --- /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 0000000000..908a8f2131 --- /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 0000000000..d5a8d3f6d5 --- /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 0000000000..a9a44d1bf4 --- /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 0000000000..dbde20362a --- /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 3d84821bf0..962ae38041 100755 --- a/test/etc/default-build +++ b/test/etc/default-build @@ -69,10 +69,13 @@ declare -A JACK_EXPERIMENTAL_ARGS 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 @@ done # 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 01464cd6b6..013fc63e83 100755 --- a/test/run-test +++ b/test/run-test @@ -46,7 +46,7 @@ export RUN="${progdir}/etc/run-test-jar" 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 |