Log an event on hidden API accesses.

The new event consists of:
- The type of access (reflection, JNI, etc.)
- The action taken (warn or deny)
- The type of member accessed (field or method)
- Name of the class which defined the member accessed
- The name of the member
- The type signature of the member (type of field, or method signature)

The fully qualified member name is also not included to avoid the overhead
of building the string. It can be build from the information included.

Similarly, the package name, version, etc. are not included as they can
be inferred from the context when analyzing the event log.

The event is sampled, according to a sampling rate that can be set by a
configuration option, to reduce log spam.

Test: $ m
Test: $ adb shell settings put global hidden_api_access_log_sampling_rate 65536
Test: $ adb lolcat -b events | grep art_hidden_api_access

Sample output:
16796 16796 I art_hidden_api_access: [1,0,Ldalvik/system/VMRuntime;,getRuntime,()Ldalvik/system/VMRuntime;]
16796 16796 I art_hidden_api_access: [1,2,Ldalvik/system/VMRuntime;,setHiddenApiExemptions,([Ljava/lang/String;)V]
16796 16796 I art_hidden_api_access: [1,3,Landroid/app/Activity;,mDoReportFullyDrawn,Z]
(Timestamps have been elided)

Bug: 64382372
Bug: 77517571
Merged-In: I012b2c9fbffbd00ed3219918e7a736a4f7435ec8
Change-Id: I012b2c9fbffbd00ed3219918e7a736a4f7435ec8
(cherry picked from commit 73ddda4403c8088a730b8d456de46bb8e0307ed8)
diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc
index 98feb4d..c5d09cf 100644
--- a/runtime/hidden_api.cc
+++ b/runtime/hidden_api.cc
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <log/log_event_list.h>
+
 #include "hidden_api.h"
 
 #include <nativehelper/scoped_local_ref.h>
@@ -59,31 +61,37 @@
 
 namespace detail {
 
+// This is the ID of the event log event. It is duplicated from
+// system/core/logcat/event.logtags
+constexpr int EVENT_LOG_TAG_art_hidden_api_access = 20004;
+
 MemberSignature::MemberSignature(ArtField* field) {
-  member_type_ = "field";
-  signature_parts_ = {
-    field->GetDeclaringClass()->GetDescriptor(&tmp_),
-    "->",
-    field->GetName(),
-    ":",
-    field->GetTypeDescriptor()
-  };
+  class_name_ = field->GetDeclaringClass()->GetDescriptor(&tmp_);
+  member_name_ = field->GetName();
+  type_signature_ = field->GetTypeDescriptor();
+  type_ = kField;
 }
 
 MemberSignature::MemberSignature(ArtMethod* method) {
-  member_type_ = "method";
-  signature_parts_ = {
-    method->GetDeclaringClass()->GetDescriptor(&tmp_),
-    "->",
-    method->GetName(),
-    method->GetSignature().ToString()
-  };
+  class_name_ = method->GetDeclaringClass()->GetDescriptor(&tmp_);
+  member_name_ = method->GetName();
+  type_signature_ = method->GetSignature().ToString();
+  type_ = kMethod;
+}
+
+inline std::vector<const char*> MemberSignature::GetSignatureParts() const {
+  if (type_ == kField) {
+    return { class_name_.c_str(), "->", member_name_.c_str(), ":", type_signature_.c_str() };
+  } else {
+    DCHECK_EQ(type_, kMethod);
+    return { class_name_.c_str(), "->", member_name_.c_str(), type_signature_.c_str() };
+  }
 }
 
 bool MemberSignature::DoesPrefixMatch(const std::string& prefix) const {
   size_t pos = 0;
-  for (const std::string& part : signature_parts_) {
-    size_t count = std::min(prefix.length() - pos, part.length());
+  for (const char* part : GetSignatureParts()) {
+    size_t count = std::min(prefix.length() - pos, strlen(part));
     if (prefix.compare(pos, count, part, 0, count) == 0) {
       pos += count;
     } else {
@@ -105,15 +113,38 @@
 }
 
 void MemberSignature::Dump(std::ostream& os) const {
-  for (std::string part : signature_parts_) {
+  for (const char* part : GetSignatureParts()) {
     os << part;
   }
 }
 
 void MemberSignature::WarnAboutAccess(AccessMethod access_method,
                                       HiddenApiAccessFlags::ApiList list) {
-  LOG(WARNING) << "Accessing hidden " << member_type_ << " " << Dumpable<MemberSignature>(*this)
-               << " (" << list << ", " << access_method << ")";
+  LOG(WARNING) << "Accessing hidden " << (type_ == kField ? "field " : "method ")
+               << Dumpable<MemberSignature>(*this) << " (" << list << ", " << access_method << ")";
+}
+
+void MemberSignature::LogAccessToEventLog(AccessMethod access_method, Action action_taken) {
+  if (access_method == kLinking) {
+    // 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.
+    return;
+  }
+  uint32_t flags = 0;
+  if (action_taken == kDeny) {
+    flags |= kAccessDenied;
+  }
+  if (type_ == kField) {
+    flags |= kMemberIsField;
+  }
+  android_log_event_list ctx(EVENT_LOG_TAG_art_hidden_api_access);
+  ctx << access_method;
+  ctx << flags;
+  ctx << class_name_;
+  ctx << member_name_;
+  ctx << type_signature_;
+  ctx << LOG_ID_EVENTS;
 }
 
 template<typename T>
@@ -152,6 +183,16 @@
     }
   }
 
+  if (kIsTargetBuild) {
+    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 (action == kDeny) {
     // Block access
     return action;
diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h
index d2c71a7..4325496 100644
--- a/runtime/hidden_api.h
+++ b/runtime/hidden_api.h
@@ -52,11 +52,22 @@
   kDeny
 };
 
+// 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 AccessMethod {
-  kNone,  // internal test that does not correspond to an actual access by app
-  kReflection,
-  kJNI,
-  kLinking,
+  kNone = 0,  // internal test that does not correspond to an actual access by app
+  kReflection = 1,
+  kJNI = 2,
+  kLinking = 3,
+};
+
+// 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,
 };
 
 inline Action GetActionFromAccessFlags(uint32_t access_flags) {
@@ -93,9 +104,18 @@
 // is used as a helper when matching prefixes, and when logging the signature.
 class MemberSignature {
  private:
-  std::string member_type_;
-  std::vector<std::string> signature_parts_;
+  enum MemberType {
+    kField,
+    kMethod,
+  };
+
+  std::string class_name_;
+  std::string member_name_;
+  std::string type_signature_;
   std::string tmp_;
+  MemberType type_;
+
+  inline std::vector<const char*> GetSignatureParts() const;
 
  public:
   explicit MemberSignature(ArtField* field) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -111,6 +131,8 @@
   bool IsExempted(const std::vector<std::string>& exemptions);
 
   void WarnAboutAccess(AccessMethod access_method, HiddenApiAccessFlags::ApiList list);
+
+  void LogAccessToEventLog(AccessMethod access_method, Action action_taken);
 };
 
 template<typename T>
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index a5ade6f..264f657 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -93,6 +93,10 @@
   Runtime::Current()->SetHiddenApiExemptions(exemptions_vec);
 }
 
+static void VMRuntime_setHiddenApiAccessLogSamplingRate(JNIEnv*, jclass, jint rate) {
+  Runtime::Current()->SetHiddenApiEventLogSampleRate(rate);
+}
+
 static jobject VMRuntime_newNonMovableArray(JNIEnv* env, jobject, jclass javaElementClass,
                                             jint length) {
   ScopedFastNativeObjectAccess soa(env);
@@ -688,6 +692,7 @@
   NATIVE_METHOD(VMRuntime, disableJitCompilation, "()V"),
   NATIVE_METHOD(VMRuntime, hasUsedHiddenApi, "()Z"),
   NATIVE_METHOD(VMRuntime, setHiddenApiExemptions, "([Ljava/lang/String;)V"),
+  NATIVE_METHOD(VMRuntime, setHiddenApiAccessLogSamplingRate, "(I)V"),
   NATIVE_METHOD(VMRuntime, getTargetHeapUtilization, "()F"),
   FAST_NATIVE_METHOD(VMRuntime, isDebuggerActive, "()Z"),
   FAST_NATIVE_METHOD(VMRuntime, isNativeDebuggable, "()Z"),
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index cf0a72a..84c7926 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -363,6 +363,13 @@
       << "Child zygote processes should be forked with EnforcementPolicy::kDisable";
   Runtime::Current()->SetHiddenApiEnforcementPolicy(api_enforcement_policy);
   Runtime::Current()->SetDedupeHiddenApiWarnings(dedupe_hidden_api_warnings);
+  if (api_enforcement_policy != hiddenapi::EnforcementPolicy::kNoChecks &&
+      Runtime::Current()->GetHiddenApiEventLogSampleRate() != 0) {
+    // Hidden API checks are enabled, and we are sampling access for the event log. Initialize the
+    // random seed, to ensure the sampling is actually random. We do this post-fork, as doing it
+    // pre-fork would result in the same sequence for every forked process.
+    std::srand(static_cast<uint32_t>(NanoTime()));
+  }
 
   // Clear the hidden API warning flag, in case it was set.
   Runtime::Current()->SetPendingHiddenApiWarning(false);
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 7823014..e2e315c 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -273,6 +273,7 @@
       pending_hidden_api_warning_(false),
       dedupe_hidden_api_warnings_(true),
       always_set_hidden_api_warning_flag_(false),
+      hidden_api_access_event_log_rate_(0),
       dump_native_stack_on_sig_quit_(true),
       pruned_dalvik_cache_(false),
       // Initially assume we perceive jank in case the process state is never updated.
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 67813a7..87f5b51 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -569,6 +569,14 @@
     return always_set_hidden_api_warning_flag_;
   }
 
+  void SetHiddenApiEventLogSampleRate(uint32_t rate) {
+    hidden_api_access_event_log_rate_ = rate;
+  }
+
+  uint32_t GetHiddenApiEventLogSampleRate() const {
+    return hidden_api_access_event_log_rate_;
+  }
+
   bool IsDexFileFallbackEnabled() const {
     return allow_dex_file_fallback_;
   }
@@ -1023,6 +1031,10 @@
   // when there is a warning. This is only used for testing.
   bool always_set_hidden_api_warning_flag_;
 
+  // How often to log hidden API access to the event log. An integer between 0 (never)
+  // and 0x10000 (always).
+  uint32_t hidden_api_access_event_log_rate_;
+
   // Whether threads should dump their native stack on SIGQUIT.
   bool dump_native_stack_on_sig_quit_;