diff options
-rw-r--r-- | runtime/hidden_api.h | 84 | ||||
-rw-r--r-- | runtime/interpreter/unstarted_runtime.cc | 20 | ||||
-rw-r--r-- | runtime/jni_internal.cc | 9 | ||||
-rw-r--r-- | runtime/mirror/class-inl.h | 5 | ||||
-rw-r--r-- | runtime/native/dalvik_system_ZygoteHooks.cc | 2 | ||||
-rw-r--r-- | runtime/native/java_lang_Class.cc | 147 | ||||
-rw-r--r-- | runtime/runtime.h | 4 | ||||
-rw-r--r-- | runtime/verifier/reg_type-inl.h | 3 | ||||
-rw-r--r-- | test/674-hiddenapi/hiddenapi.cc | 4 | ||||
-rw-r--r-- | test/674-hiddenapi/src-art/Main.java | 4 | ||||
-rw-r--r-- | test/674-hiddenapi/src-ex/ChildClass.java | 11 |
11 files changed, 256 insertions, 37 deletions
diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h new file mode 100644 index 0000000000..241850ed6e --- /dev/null +++ b/runtime/hidden_api.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef ART_RUNTIME_HIDDEN_API_H_ +#define ART_RUNTIME_HIDDEN_API_H_ + +#include "hidden_api_access_flags.h" +#include "reflection.h" +#include "runtime.h" + +namespace art { +namespace hiddenapi { + +// Returns true if member with `access flags` should only be accessed from +// boot class path. +inline bool IsMemberHidden(uint32_t access_flags) { + if (!Runtime::Current()->AreHiddenApiChecksEnabled()) { + return false; + } + + switch (HiddenApiAccessFlags::DecodeFromRuntime(access_flags)) { + case HiddenApiAccessFlags::kWhitelist: + case HiddenApiAccessFlags::kLightGreylist: + case HiddenApiAccessFlags::kDarkGreylist: + return false; + case HiddenApiAccessFlags::kBlacklist: + return true; + } +} + +// Returns true if caller `num_frames` up the stack is in boot class path. +inline bool IsCallerInBootClassPath(Thread* self, size_t num_frames) + REQUIRES_SHARED(Locks::mutator_lock_) { + ObjPtr<mirror::Class> klass = GetCallingClass(self, num_frames); + if (klass == nullptr) { + // Unattached native thread. Assume that this is *not* boot class path. + return false; + } + return klass->IsBootStrapClassLoaded(); +} + +// Returns true if `caller` should not be allowed to access member with `access_flags`. +inline bool ShouldBlockAccessToMember(uint32_t access_flags, mirror::Class* caller) + REQUIRES_SHARED(Locks::mutator_lock_) { + return IsMemberHidden(access_flags) && + !caller->IsBootStrapClassLoaded(); +} + +// Returns true if `caller` should not be allowed to access `member`. +template<typename T> +inline bool ShouldBlockAccessToMember(T* member, ArtMethod* caller) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(member != nullptr); + DCHECK(!caller->IsRuntimeMethod()); + return ShouldBlockAccessToMember(member->GetAccessFlags(), caller->GetDeclaringClass()); +} + +// Returns true if the caller `num_frames` up the stack should not be allowed +// to access `member`. +template<typename T> +inline bool ShouldBlockAccessToMember(T* member, Thread* self, size_t num_frames) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(member != nullptr); + return IsMemberHidden(member->GetAccessFlags()) && + !IsCallerInBootClassPath(self, num_frames); // This is expensive. Save it for last. +} + +} // namespace hiddenapi +} // namespace art + +#endif // ART_RUNTIME_HIDDEN_API_H_ diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc index d1436fa9cf..b9b00519d1 100644 --- a/runtime/interpreter/unstarted_runtime.cc +++ b/runtime/interpreter/unstarted_runtime.cc @@ -38,6 +38,7 @@ #include "entrypoints/entrypoint_utils-inl.h" #include "gc/reference_processor.h" #include "handle_scope-inl.h" +#include "hidden_api.h" #include "interpreter/interpreter_common.h" #include "jvalue-inl.h" #include "mirror/array-inl.h" @@ -265,7 +266,11 @@ void UnstartedRuntime::UnstartedClassNewInstance( bool ok = false; auto* cl = Runtime::Current()->GetClassLinker(); if (cl->EnsureInitialized(self, h_klass, true, true)) { - auto* cons = h_klass->FindConstructor("()V", cl->GetImagePointerSize()); + ArtMethod* cons = h_klass->FindConstructor("()V", cl->GetImagePointerSize()); + if (cons != nullptr && + hiddenapi::ShouldBlockAccessToMember(cons, shadow_frame->GetMethod())) { + cons = nullptr; + } if (cons != nullptr) { Handle<mirror::Object> h_obj(hs.NewHandle(klass->AllocObject(self))); CHECK(h_obj != nullptr); // We don't expect OOM at compile-time. @@ -308,6 +313,10 @@ void UnstartedRuntime::UnstartedClassGetDeclaredField( } } } + if (found != nullptr && + hiddenapi::ShouldBlockAccessToMember(found, shadow_frame->GetMethod())) { + found = nullptr; + } if (found == nullptr) { AbortTransactionOrFail(self, "Failed to find field in Class.getDeclaredField in un-started " " runtime. name=%s class=%s", name2->ToModifiedUtf8().c_str(), @@ -370,6 +379,10 @@ void UnstartedRuntime::UnstartedClassGetDeclaredMethod( self, klass, name, args); } } + if (method != nullptr && + hiddenapi::ShouldBlockAccessToMember(method->GetArtMethod(), shadow_frame->GetMethod())) { + method = nullptr; + } result->SetL(method); } @@ -404,6 +417,11 @@ void UnstartedRuntime::UnstartedClassGetDeclaredConstructor( false>(self, klass, args); } } + if (constructor != nullptr && + hiddenapi::ShouldBlockAccessToMember( + constructor->GetArtMethod(), shadow_frame->GetMethod())) { + constructor = nullptr; + } result->SetL(constructor); } diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc index b8e6ebe8d8..727f54b356 100644 --- a/runtime/jni_internal.cc +++ b/runtime/jni_internal.cc @@ -34,6 +34,7 @@ #include "class_linker-inl.h" #include "dex/dex_file-inl.h" #include "fault_handler.h" +#include "hidden_api.h" #include "gc/accounting/card_table-inl.h" #include "gc_root.h" #include "indirect_reference_table-inl.h" @@ -238,6 +239,10 @@ static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class, } else { method = c->FindClassMethod(name, sig, pointer_size); } + if (method != nullptr && + hiddenapi::ShouldBlockAccessToMember(method, soa.Self(), /* num_frames */ 1)) { + method = nullptr; + } if (method == nullptr || method->IsStatic() != is_static) { ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static"); return nullptr; @@ -314,6 +319,10 @@ static jfieldID FindFieldID(const ScopedObjectAccess& soa, jclass jni_class, con } else { field = c->FindInstanceField(name, field_type->GetDescriptor(&temp)); } + if (field != nullptr && + hiddenapi::ShouldBlockAccessToMember(field, soa.Self(), /* num_frames */ 1)) { + field = nullptr; + } if (field == nullptr) { soa.Self()->ThrowNewExceptionF("Ljava/lang/NoSuchFieldError;", "no \"%s\" field \"%s\" in class \"%s\" or its superclasses", diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h index cd313b32ab..36388eb3aa 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -30,6 +30,7 @@ #include "dex/invoke_type.h" #include "dex_cache.h" #include "gc/heap-inl.h" +#include "hidden_api.h" #include "iftable.h" #include "subtype_check.h" #include "object-inl.h" @@ -1143,6 +1144,10 @@ inline bool Class::CanAccessMember(ObjPtr<Class> access_to, uint32_t member_flag if (this == access_to) { return true; } + // Do not allow non-boot class path classes access hidden APIs. + if (hiddenapi::ShouldBlockAccessToMember(member_flags, this)) { + return false; + } // Public members are trivially accessible if (member_flags & kAccPublic) { return true; diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc index e22726b79b..2892967a51 100644 --- a/runtime/native/dalvik_system_ZygoteHooks.cc +++ b/runtime/native/dalvik_system_ZygoteHooks.cc @@ -286,7 +286,7 @@ static void ZygoteHooks_nativePostForkChild(JNIEnv* env, } if ((runtime_flags & DISABLE_HIDDEN_API_CHECKS) != 0) { - Runtime::Current()->DisableHiddenApiChecks(); + Runtime::Current()->SetHiddenApiChecksEnabled(false); runtime_flags &= ~DISABLE_HIDDEN_API_CHECKS; } diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc index 7b999c04af..4d36e80c5c 100644 --- a/runtime/native/java_lang_Class.cc +++ b/runtime/native/java_lang_Class.cc @@ -25,6 +25,7 @@ #include "common_throws.h" #include "dex/dex_file-inl.h" #include "dex/dex_file_annotations.h" +#include "hidden_api.h" #include "jni_internal.h" #include "mirror/class-inl.h" #include "mirror/class_loader.h" @@ -47,6 +48,75 @@ namespace art { +ALWAYS_INLINE static bool ShouldEnforceHiddenApi(Thread* self) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (!Runtime::Current()->AreHiddenApiChecksEnabled()) { + return false; + } + + // Walk the stack and find the first frame not from java.lang.Class. + // This is very expensive. Save this till the last. + struct FirstNonClassClassCallerVisitor : public StackVisitor { + explicit FirstNonClassClassCallerVisitor(Thread* thread) + : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), + caller(nullptr) { + } + + bool VisitFrame() REQUIRES_SHARED(Locks::mutator_lock_) { + ArtMethod *m = GetMethod(); + if (m == nullptr) { + // Attached native thread. Assume this is *not* boot class path. + caller = nullptr; + return false; + } else if (m->IsRuntimeMethod()) { + // Internal runtime method, continue walking the stack. + return true; + } else if (m->GetDeclaringClass()->IsClassClass()) { + return true; + } else { + caller = m; + return false; + } + } + + ArtMethod* caller; + }; + + FirstNonClassClassCallerVisitor visitor(self); + visitor.WalkStack(); + return visitor.caller == nullptr || + !visitor.caller->GetDeclaringClass()->IsBootStrapClassLoaded(); +} + +// Returns true if the first non-ClassClass caller up the stack should not be +// allowed access to `member`. +template<typename T> +ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(member != nullptr); + return hiddenapi::IsMemberHidden(member->GetAccessFlags()) && + ShouldEnforceHiddenApi(self); +} + +// Returns true if a class member should be discoverable with reflection given +// the criteria. Some reflection calls only return public members +// (public_only == true), some members should be hidden from non-boot class path +// callers (enforce_hidden_api == true). +ALWAYS_INLINE static bool IsDiscoverable(bool public_only, + bool enforce_hidden_api, + uint32_t access_flags) { + if (public_only && ((access_flags & kAccPublic) == 0)) { + return false; + } + + if (enforce_hidden_api && hiddenapi::IsMemberHidden(access_flags)) { + return false; + } + + return true; +} + + ALWAYS_INLINE static inline ObjPtr<mirror::Class> DecodeClass( const ScopedFastNativeObjectAccess& soa, jobject java_class) REQUIRES_SHARED(Locks::mutator_lock_) { @@ -164,17 +234,16 @@ static mirror::ObjectArray<mirror::Field>* GetDeclaredFields( IterationRange<StrideIterator<ArtField>> ifields = klass->GetIFields(); IterationRange<StrideIterator<ArtField>> sfields = klass->GetSFields(); size_t array_size = klass->NumInstanceFields() + klass->NumStaticFields(); - if (public_only) { - // Lets go subtract all the non public fields. - for (ArtField& field : ifields) { - if (!field.IsPublic()) { - --array_size; - } + bool enforce_hidden_api = ShouldEnforceHiddenApi(self); + // Lets go subtract all the non discoverable fields. + for (ArtField& field : ifields) { + if (!IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) { + --array_size; } - for (ArtField& field : sfields) { - if (!field.IsPublic()) { - --array_size; - } + } + for (ArtField& field : sfields) { + if (!IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) { + --array_size; } } size_t array_idx = 0; @@ -184,7 +253,7 @@ static mirror::ObjectArray<mirror::Field>* GetDeclaredFields( return nullptr; } for (ArtField& field : ifields) { - if (!public_only || field.IsPublic()) { + if (IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) { auto* reflect_field = mirror::Field::CreateFromArtField<kRuntimePointerSize>(self, &field, force_resolve); @@ -199,7 +268,7 @@ static mirror::ObjectArray<mirror::Field>* GetDeclaredFields( } } for (ArtField& field : sfields) { - if (!public_only || field.IsPublic()) { + if (IsDiscoverable(public_only, enforce_hidden_api, field.GetAccessFlags())) { auto* reflect_field = mirror::Field::CreateFromArtField<kRuntimePointerSize>(self, &field, force_resolve); @@ -354,8 +423,13 @@ static jobject Class_getPublicFieldRecursive(JNIEnv* env, jobject javaThis, jstr ThrowNullPointerException("name == null"); return nullptr; } - return soa.AddLocalReference<jobject>( - GetPublicFieldRecursive(soa.Self(), DecodeClass(soa, javaThis), name_string)); + + mirror::Field* field = GetPublicFieldRecursive( + soa.Self(), DecodeClass(soa, javaThis), name_string); + if (field == nullptr || ShouldBlockAccessToMember(field->GetArtField(), soa.Self())) { + return nullptr; + } + return soa.AddLocalReference<jobject>(field); } static jobject Class_getDeclaredField(JNIEnv* env, jobject javaThis, jstring name) { @@ -369,7 +443,7 @@ static jobject Class_getDeclaredField(JNIEnv* env, jobject javaThis, jstring nam Handle<mirror::Class> h_klass = hs.NewHandle(DecodeClass(soa, javaThis)); Handle<mirror::Field> result = hs.NewHandle(GetDeclaredField(soa.Self(), h_klass.Get(), h_string.Get())); - if (result == nullptr) { + if (result == nullptr || ShouldBlockAccessToMember(result->GetArtField(), soa.Self())) { std::string name_str = h_string->ToModifiedUtf8(); if (name_str == "value" && h_klass->IsStringClass()) { // We log the error for this specific case, as the user might just swallow the exception. @@ -399,24 +473,32 @@ static jobject Class_getDeclaredConstructorInternal( soa.Self(), DecodeClass(soa, javaThis), soa.Decode<mirror::ObjectArray<mirror::Class>>(args)); + if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) { + return nullptr; + } return soa.AddLocalReference<jobject>(result); } -static ALWAYS_INLINE inline bool MethodMatchesConstructor(ArtMethod* m, bool public_only) +static ALWAYS_INLINE inline bool MethodMatchesConstructor( + ArtMethod* m, bool public_only, bool enforce_hidden_api) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(m != nullptr); - return (!public_only || m->IsPublic()) && !m->IsStatic() && m->IsConstructor(); + return m->IsConstructor() && + !m->IsStatic() && + IsDiscoverable(public_only, enforce_hidden_api, m->GetAccessFlags()); } static jobjectArray Class_getDeclaredConstructorsInternal( JNIEnv* env, jobject javaThis, jboolean publicOnly) { ScopedFastNativeObjectAccess soa(env); StackHandleScope<2> hs(soa.Self()); + bool public_only = (publicOnly != JNI_FALSE); + bool enforce_hidden_api = ShouldEnforceHiddenApi(soa.Self()); Handle<mirror::Class> h_klass = hs.NewHandle(DecodeClass(soa, javaThis)); size_t constructor_count = 0; // Two pass approach for speed. for (auto& m : h_klass->GetDirectMethods(kRuntimePointerSize)) { - constructor_count += MethodMatchesConstructor(&m, publicOnly != JNI_FALSE) ? 1u : 0u; + constructor_count += MethodMatchesConstructor(&m, public_only, enforce_hidden_api) ? 1u : 0u; } auto h_constructors = hs.NewHandle(mirror::ObjectArray<mirror::Constructor>::Alloc( soa.Self(), mirror::Constructor::ArrayClass(), constructor_count)); @@ -426,7 +508,7 @@ static jobjectArray Class_getDeclaredConstructorsInternal( } constructor_count = 0; for (auto& m : h_klass->GetDirectMethods(kRuntimePointerSize)) { - if (MethodMatchesConstructor(&m, publicOnly != JNI_FALSE)) { + if (MethodMatchesConstructor(&m, public_only, enforce_hidden_api)) { DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize); DCHECK(!Runtime::Current()->IsActiveTransaction()); auto* constructor = mirror::Constructor::CreateFromArtMethod<kRuntimePointerSize, false>( @@ -452,6 +534,9 @@ static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis, DecodeClass(soa, javaThis), soa.Decode<mirror::String>(name), soa.Decode<mirror::ObjectArray<mirror::Class>>(args)); + if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) { + return nullptr; + } return soa.AddLocalReference<jobject>(result); } @@ -459,13 +544,17 @@ static jobjectArray Class_getDeclaredMethodsUnchecked(JNIEnv* env, jobject javaT jboolean publicOnly) { ScopedFastNativeObjectAccess soa(env); StackHandleScope<2> hs(soa.Self()); + + bool enforce_hidden_api = ShouldEnforceHiddenApi(soa.Self()); + bool public_only = (publicOnly != JNI_FALSE); + Handle<mirror::Class> klass = hs.NewHandle(DecodeClass(soa, javaThis)); size_t num_methods = 0; - for (auto& m : klass->GetDeclaredMethods(kRuntimePointerSize)) { - auto modifiers = m.GetAccessFlags(); + for (ArtMethod& m : klass->GetDeclaredMethods(kRuntimePointerSize)) { + uint32_t modifiers = m.GetAccessFlags(); // Add non-constructor declared methods. - if ((publicOnly == JNI_FALSE || (modifiers & kAccPublic) != 0) && - (modifiers & kAccConstructor) == 0) { + if ((modifiers & kAccConstructor) == 0 && + IsDiscoverable(public_only, enforce_hidden_api, modifiers)) { ++num_methods; } } @@ -476,10 +565,10 @@ static jobjectArray Class_getDeclaredMethodsUnchecked(JNIEnv* env, jobject javaT return nullptr; } num_methods = 0; - for (auto& m : klass->GetDeclaredMethods(kRuntimePointerSize)) { - auto modifiers = m.GetAccessFlags(); - if ((publicOnly == JNI_FALSE || (modifiers & kAccPublic) != 0) && - (modifiers & kAccConstructor) == 0) { + for (ArtMethod& m : klass->GetDeclaredMethods(kRuntimePointerSize)) { + uint32_t modifiers = m.GetAccessFlags(); + if ((modifiers & kAccConstructor) == 0 && + IsDiscoverable(public_only, enforce_hidden_api, modifiers)) { DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize); DCHECK(!Runtime::Current()->IsActiveTransaction()); auto* method = @@ -693,11 +782,11 @@ static jobject Class_newInstance(JNIEnv* env, jobject javaThis) { return nullptr; } } - auto* constructor = klass->GetDeclaredConstructor( + ArtMethod* constructor = klass->GetDeclaredConstructor( soa.Self(), ScopedNullHandle<mirror::ObjectArray<mirror::Class>>(), kRuntimePointerSize); - if (UNLIKELY(constructor == nullptr)) { + if (UNLIKELY(constructor == nullptr) || ShouldBlockAccessToMember(constructor, soa.Self())) { soa.Self()->ThrowNewExceptionF("Ljava/lang/InstantiationException;", "%s has no zero argument constructor", klass->PrettyClass().c_str()); diff --git a/runtime/runtime.h b/runtime/runtime.h index 022a1be124..893ebbef68 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -520,8 +520,8 @@ class Runtime { bool IsVerificationEnabled() const; bool IsVerificationSoftFail() const; - void DisableHiddenApiChecks() { - do_hidden_api_checks_ = false; + void SetHiddenApiChecksEnabled(bool value) { + do_hidden_api_checks_ = value; } bool AreHiddenApiChecksEnabled() const { diff --git a/runtime/verifier/reg_type-inl.h b/runtime/verifier/reg_type-inl.h index f719782727..9e12d636d4 100644 --- a/runtime/verifier/reg_type-inl.h +++ b/runtime/verifier/reg_type-inl.h @@ -48,9 +48,6 @@ inline bool RegType::CanAccess(const RegType& other) const { inline bool RegType::CanAccessMember(ObjPtr<mirror::Class> klass, uint32_t access_flags) const { DCHECK(IsReferenceTypes()); - if ((access_flags & kAccPublic) != 0) { - return true; - } if (IsNull()) { return true; } diff --git a/test/674-hiddenapi/hiddenapi.cc b/test/674-hiddenapi/hiddenapi.cc index 672079b9d8..89cf68cf1f 100644 --- a/test/674-hiddenapi/hiddenapi.cc +++ b/test/674-hiddenapi/hiddenapi.cc @@ -25,6 +25,10 @@ namespace art { namespace Test674HiddenApi { +extern "C" JNIEXPORT void JNICALL Java_Main_init(JNIEnv*, jclass) { + Runtime::Current()->SetHiddenApiChecksEnabled(true); +} + extern "C" JNIEXPORT void JNICALL Java_Main_appendToBootClassLoader( JNIEnv* env, jclass, jstring jpath) { ScopedUtfChars utf(env, jpath); diff --git a/test/674-hiddenapi/src-art/Main.java b/test/674-hiddenapi/src-art/Main.java index 56a644f637..a808e946a9 100644 --- a/test/674-hiddenapi/src-art/Main.java +++ b/test/674-hiddenapi/src-art/Main.java @@ -31,6 +31,9 @@ public class Main { System.loadLibrary(args[0]); prepareNativeLibFileName(args[0]); + // Enable hidden API checks in case they are disabled by default. + init(); + // Run test with both parent and child dex files loaded with class loaders. // The expectation is that hidden members in parent should be visible to // the child. @@ -150,4 +153,5 @@ public class Main { private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader(); private static native void appendToBootClassLoader(String dexPath); + private static native void init(); } diff --git a/test/674-hiddenapi/src-ex/ChildClass.java b/test/674-hiddenapi/src-ex/ChildClass.java index af615bfd5f..be2a352509 100644 --- a/test/674-hiddenapi/src-ex/ChildClass.java +++ b/test/674-hiddenapi/src-ex/ChildClass.java @@ -93,9 +93,18 @@ public class ChildClass { "in boot class path"); } + boolean isSameBoot = (isParentInBoot == isChildInBoot); + // Run meaningful combinations of access flags. for (Hiddenness hiddenness : Hiddenness.values()) { - final Behaviour expected = Behaviour.Granted; + final Behaviour expected; + if (isSameBoot || hiddenness == Hiddenness.Whitelist) { + expected = Behaviour.Granted; + } else if (hiddenness == Hiddenness.Blacklist) { + expected = Behaviour.Denied; + } else { + expected = Behaviour.Granted; + } for (boolean isStatic : booleanValues) { String suffix = (isStatic ? "Static" : "") + hiddenness.name(); |