Simplify hidden_api.h logic

Refactor GetMemberAction to return a boolean whether access to a class
member should be denied. This also moves StrictMode consumer
notification into hidden_api.cc and removes notifications for toasts.
Tests are changed accordingly.

Test: phone boots
Test: m test-art
Merged-In: I02902143de0ff91d402ba79c83f28226b1822a6f
Change-Id: I02902143de0ff91d402ba79c83f28226b1822a6f
(cherry picked from commit 51995f90adaa0e5047dee56d22f15e4225e70517)
diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc
index f355276..3b7b938 100644
--- a/runtime/hidden_api.cc
+++ b/runtime/hidden_api.cc
@@ -44,38 +44,53 @@
 
 static inline std::ostream& operator<<(std::ostream& os, AccessMethod value) {
   switch (value) {
-    case kNone:
+    case AccessMethod::kNone:
       LOG(FATAL) << "Internal access to hidden API should not be logged";
       UNREACHABLE();
-    case kReflection:
+    case AccessMethod::kReflection:
       os << "reflection";
       break;
-    case kJNI:
+    case AccessMethod::kJNI:
       os << "JNI";
       break;
-    case kLinking:
+    case AccessMethod::kLinking:
       os << "linking";
       break;
   }
   return os;
 }
 
-static constexpr bool EnumsEqual(EnforcementPolicy policy, hiddenapi::ApiList apiList) {
-  return static_cast<int>(policy) == static_cast<int>(apiList);
-}
-
-// GetMemberAction-related static_asserts.
-static_assert(
-    EnumsEqual(EnforcementPolicy::kDarkGreyAndBlackList, hiddenapi::ApiList::kDarkGreylist) &&
-    EnumsEqual(EnforcementPolicy::kBlacklistOnly, hiddenapi::ApiList::kBlacklist),
-    "Mismatch between EnforcementPolicy and ApiList enums");
-static_assert(
-    EnforcementPolicy::kJustWarn < EnforcementPolicy::kDarkGreyAndBlackList &&
-    EnforcementPolicy::kDarkGreyAndBlackList < EnforcementPolicy::kBlacklistOnly,
-    "EnforcementPolicy values ordering not correct");
-
 namespace detail {
 
+// Do not change the values of items in this enum, as they are written to the
+// event log for offline analysis. Any changes will interfere with that analysis.
+enum AccessContextFlags {
+  // Accessed member is a field if this bit is set, else a method
+  kMemberIsField = 1 << 0,
+  // Indicates if access was denied to the member, instead of just printing a warning.
+  kAccessDenied  = 1 << 1,
+};
+
+static int32_t GetMaxAllowedSdkVersionForApiList(ApiList api_list) {
+  SdkCodes sdk = SdkCodes::kVersionNone;
+  switch (api_list) {
+    case ApiList::kWhitelist:
+    case ApiList::kLightGreylist:
+      sdk = SdkCodes::kVersionUnlimited;
+      break;
+    case ApiList::kDarkGreylist:
+      sdk = SdkCodes::kVersionO_MR1;
+      break;
+    case ApiList::kBlacklist:
+      sdk = SdkCodes::kVersionNone;
+      break;
+    case ApiList::kNoList:
+      LOG(FATAL) << "Unexpected value";
+      UNREACHABLE();
+  }
+  return static_cast<int32_t>(sdk);
+}
+
 MemberSignature::MemberSignature(ArtField* field) {
   class_name_ = field->GetDeclaringClass()->GetDescriptor(&tmp_);
   member_name_ = field->GetName();
@@ -137,6 +152,7 @@
   LOG(WARNING) << "Accessing hidden " << (type_ == kField ? "field " : "method ")
                << Dumpable<MemberSignature>(*this) << " (" << list << ", " << access_method << ")";
 }
+
 #ifdef ART_TARGET_ANDROID
 // Convert an AccessMethod enum to a value for logging from the proto enum.
 // This method may look odd (the enum values are current the same), but it
@@ -145,13 +161,13 @@
 // future.
 inline static int32_t GetEnumValueForLog(AccessMethod access_method) {
   switch (access_method) {
-    case kNone:
+    case AccessMethod::kNone:
       return android::metricslogger::ACCESS_METHOD_NONE;
-    case kReflection:
+    case AccessMethod::kReflection:
       return android::metricslogger::ACCESS_METHOD_REFLECTION;
-    case kJNI:
+    case AccessMethod::kJNI:
       return android::metricslogger::ACCESS_METHOD_JNI;
-    case kLinking:
+    case AccessMethod::kLinking:
       return android::metricslogger::ACCESS_METHOD_LINKING;
     default:
       DCHECK(false);
@@ -159,9 +175,9 @@
 }
 #endif
 
-void MemberSignature::LogAccessToEventLog(AccessMethod access_method, Action action_taken) {
+void MemberSignature::LogAccessToEventLog(AccessMethod access_method, bool access_denied) {
 #ifdef ART_TARGET_ANDROID
-  if (access_method == kLinking || access_method == kNone) {
+  if (access_method == AccessMethod::kLinking || access_method == AccessMethod::kNone) {
     // Linking warnings come from static analysis/compilation of the bytecode
     // and can contain false positives (i.e. code that is never run). We choose
     // not to log these in the event log.
@@ -170,7 +186,7 @@
   }
   ComplexEventLogger log_maker(ACTION_HIDDEN_API_ACCESSED);
   log_maker.AddTaggedData(FIELD_HIDDEN_API_ACCESS_METHOD, GetEnumValueForLog(access_method));
-  if (action_taken == kDeny) {
+  if (access_denied) {
     log_maker.AddTaggedData(FIELD_HIDDEN_API_ACCESS_DENIED, 1);
   }
   const std::string& package_name = Runtime::Current()->GetProcessPackageName();
@@ -183,10 +199,42 @@
   log_maker.Record();
 #else
   UNUSED(access_method);
-  UNUSED(action_taken);
+  UNUSED(access_denied);
 #endif
 }
 
+void MemberSignature::NotifyHiddenApiListener(AccessMethod access_method) {
+  if (access_method != AccessMethod::kReflection && access_method != AccessMethod::kJNI) {
+    // We can only up-call into Java during reflection and JNI down-calls.
+    return;
+  }
+
+  Runtime* runtime = Runtime::Current();
+  if (!runtime->IsAotCompiler()) {
+    ScopedObjectAccessUnchecked soa(Thread::Current());
+
+    ScopedLocalRef<jobject> consumer_object(soa.Env(),
+        soa.Env()->GetStaticObjectField(
+            WellKnownClasses::dalvik_system_VMRuntime,
+            WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer));
+    // If the consumer is non-null, we call back to it to let it know that we
+    // have encountered an API that's in one of our lists.
+    if (consumer_object != nullptr) {
+      std::ostringstream member_signature_str;
+      Dump(member_signature_str);
+
+      ScopedLocalRef<jobject> signature_str(
+          soa.Env(),
+          soa.Env()->NewStringUTF(member_signature_str.str().c_str()));
+
+      // Call through to Consumer.accept(String memberSignature);
+      soa.Env()->CallVoidMethod(consumer_object.get(),
+                                WellKnownClasses::java_util_function_Consumer_accept,
+                                signature_str.get());
+    }
+  }
+}
+
 static ALWAYS_INLINE bool CanUpdateMemberAccessFlags(ArtField*) {
   return true;
 }
@@ -205,116 +253,68 @@
 }
 
 template<typename T>
-Action GetMemberActionImpl(T* member,
-                           hiddenapi::ApiList api_list,
-                           Action action,
-                           AccessMethod access_method) {
-  DCHECK_NE(action, kAllow);
-
-  // Get the signature, we need it later.
-  MemberSignature member_signature(member);
+bool ShouldDenyAccessToMemberImpl(T* member,
+                                  hiddenapi::ApiList api_list,
+                                  AccessMethod access_method) {
+  DCHECK(member != nullptr);
 
   Runtime* runtime = Runtime::Current();
+  EnforcementPolicy policy = runtime->GetHiddenApiEnforcementPolicy();
+
+  const bool deny_access =
+      (policy == EnforcementPolicy::kEnabled) &&
+      (runtime->GetTargetSdkVersion() > GetMaxAllowedSdkVersionForApiList(api_list));
+
+  MemberSignature member_signature(member);
 
   // Check for an exemption first. Exempted APIs are treated as white list.
-  // We only do this if we're about to deny, or if the app is debuggable. This is because:
-  // - we only print a warning for light greylist violations for debuggable apps
-  // - for non-debuggable apps, there is no distinction between light grey & whitelisted APIs.
-  // - we want to avoid the overhead of checking for exemptions for light greylisted APIs whenever
-  //   possible.
-  const bool shouldWarn = kLogAllAccesses || runtime->IsJavaDebuggable();
-  if (shouldWarn || action == kDeny) {
-    if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {
-      action = kAllow;
-      // Avoid re-examining the exemption list next time.
-      // Note this results in no warning for the member, which seems like what one would expect.
-      // Exemptions effectively adds new members to the whitelist.
-      MaybeWhitelistMember(runtime, member);
-      return kAllow;
-    }
+  if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {
+    // Avoid re-examining the exemption list next time.
+    // Note this results in no warning for the member, which seems like what one would expect.
+    // Exemptions effectively adds new members to the whitelist.
+    MaybeWhitelistMember(runtime, member);
+    return false;
+  }
 
-    if (access_method != kNone) {
-      // Print a log message with information about this class member access.
-      // We do this if we're about to block access, or the app is debuggable.
+  if (access_method != AccessMethod::kNone) {
+    // Print a log message with information about this class member access.
+    // We do this if we're about to deny access, or the app is debuggable.
+    if (kLogAllAccesses || deny_access || runtime->IsJavaDebuggable()) {
       member_signature.WarnAboutAccess(access_method, api_list);
     }
-  }
 
-  if (kIsTargetBuild && !kIsTargetLinux) {
-    uint32_t eventLogSampleRate = runtime->GetHiddenApiEventLogSampleRate();
-    // Assert that RAND_MAX is big enough, to ensure sampling below works as expected.
-    static_assert(RAND_MAX >= 0xffff, "RAND_MAX too small");
-    if (eventLogSampleRate != 0 &&
-        (static_cast<uint32_t>(std::rand()) & 0xffff) < eventLogSampleRate) {
-      member_signature.LogAccessToEventLog(access_method, action);
+    // If there is a StrictMode listener, notify it about this violation.
+    member_signature.NotifyHiddenApiListener(access_method);
+
+    // If event log sampling is enabled, report this violation.
+    if (kIsTargetBuild && !kIsTargetLinux) {
+      uint32_t eventLogSampleRate = runtime->GetHiddenApiEventLogSampleRate();
+      // Assert that RAND_MAX is big enough, to ensure sampling below works as expected.
+      static_assert(RAND_MAX >= 0xffff, "RAND_MAX too small");
+      if (eventLogSampleRate != 0 &&
+          (static_cast<uint32_t>(std::rand()) & 0xffff) < eventLogSampleRate) {
+        member_signature.LogAccessToEventLog(access_method, deny_access);
+      }
+    }
+
+    // If this access was not denied, move the member into whitelist and skip
+    // the warning the next time the member is accessed.
+    if (!deny_access) {
+      MaybeWhitelistMember(runtime, member);
     }
   }
 
-  if (action == kDeny) {
-    // Block access
-    return action;
-  }
-
-  // Allow access to this member but print a warning.
-  DCHECK(action == kAllowButWarn || action == kAllowButWarnAndToast);
-
-  if (access_method != kNone) {
-    // Depending on a runtime flag, we might move the member into whitelist and
-    // skip the warning the next time the member is accessed.
-    MaybeWhitelistMember(runtime, member);
-
-    // If this action requires a UI warning, set the appropriate flag.
-    if (shouldWarn &&
-        (action == kAllowButWarnAndToast || runtime->ShouldAlwaysSetHiddenApiWarningFlag())) {
-      runtime->SetPendingHiddenApiWarning(true);
-    }
-  }
-
-  return action;
+  return deny_access;
 }
 
 // Need to instantiate this.
-template Action GetMemberActionImpl<ArtField>(ArtField* member,
-                                              hiddenapi::ApiList api_list,
-                                              Action action,
-                                              AccessMethod access_method);
-template Action GetMemberActionImpl<ArtMethod>(ArtMethod* member,
-                                               hiddenapi::ApiList api_list,
-                                               Action action,
-                                               AccessMethod access_method);
+template bool ShouldDenyAccessToMemberImpl<ArtField>(ArtField* member,
+                                                     hiddenapi::ApiList api_list,
+                                                     AccessMethod access_method);
+template bool ShouldDenyAccessToMemberImpl<ArtMethod>(ArtMethod* member,
+                                                      hiddenapi::ApiList api_list,
+                                                      AccessMethod access_method);
 }  // namespace detail
 
-template<typename T>
-void NotifyHiddenApiListener(T* member) {
-  Runtime* runtime = Runtime::Current();
-  if (!runtime->IsAotCompiler()) {
-    ScopedObjectAccessUnchecked soa(Thread::Current());
-
-    ScopedLocalRef<jobject> consumer_object(soa.Env(),
-        soa.Env()->GetStaticObjectField(
-            WellKnownClasses::dalvik_system_VMRuntime,
-            WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer));
-    // If the consumer is non-null, we call back to it to let it know that we
-    // have encountered an API that's in one of our lists.
-    if (consumer_object != nullptr) {
-      detail::MemberSignature member_signature(member);
-      std::ostringstream member_signature_str;
-      member_signature.Dump(member_signature_str);
-
-      ScopedLocalRef<jobject> signature_str(
-          soa.Env(),
-          soa.Env()->NewStringUTF(member_signature_str.str().c_str()));
-
-      // Call through to Consumer.accept(String memberSignature);
-      soa.Env()->CallVoidMethod(consumer_object.get(),
-                                WellKnownClasses::java_util_function_Consumer_accept,
-                                signature_str.get());
-    }
-  }
-}
-
-template void NotifyHiddenApiListener<ArtMethod>(ArtMethod* member);
-template void NotifyHiddenApiListener<ArtField>(ArtField* member);
-
 }  // namespace hiddenapi
 }  // namespace art