Start enforcing hidden API blacklist

Insert checks into reflection, JNI and Class::CanAccessMember to
make blacklisted hidden APIs undiscoverable.

The change was tested with internal microbenchmarks of reflection
and those showed no measurable performance impact.

Test: art/test.py -b -r -t 674-hiddenapi
Bug: 64382372
Change-Id: I9e39804b837ae9ffeba926f2a5b1e8e9986c472b
diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h
new file mode 100644
index 0000000..241850e
--- /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 d1436fa..b9b0051 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 @@
   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 @@
       }
     }
   }
+  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 @@
           self, klass, name, args);
     }
   }
+  if (method != nullptr &&
+      hiddenapi::ShouldBlockAccessToMember(method->GetArtMethod(), shadow_frame->GetMethod())) {
+    method = nullptr;
+  }
   result->SetL(method);
 }
 
@@ -404,6 +417,11 @@
                                                                   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 b8e6ebe..727f54b 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 @@
   } 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 @@
   } 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 cd313b3..36388eb 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 @@
   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 e22726b..2892967 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -286,7 +286,7 @@
   }
 
   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 7b999c0..4d36e80 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 @@
   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 @@
     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 @@
     }
   }
   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 @@
     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 @@
   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 @@
       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 @@
   }
   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 @@
           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 @@
                                                       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 @@
     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 @@
       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 022a1be..893ebbe 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -520,8 +520,8 @@
   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 f719782..9e12d63 100644
--- a/runtime/verifier/reg_type-inl.h
+++ b/runtime/verifier/reg_type-inl.h
@@ -48,9 +48,6 @@
 
 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 672079b..89cf68c 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 9773e0a..9c691ad 100644
--- a/test/674-hiddenapi/src-art/Main.java
+++ b/test/674-hiddenapi/src-art/Main.java
@@ -31,6 +31,9 @@
     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.
@@ -141,4 +144,5 @@
   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 af615bf..be2a352 100644
--- a/test/674-hiddenapi/src-ex/ChildClass.java
+++ b/test/674-hiddenapi/src-ex/ChildClass.java
@@ -93,9 +93,18 @@
                                  "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();