hiddenapi: Fix class hierarchy traversal

`hiddenapi` builds and traverses the class hierarchy, visiting all
class members that methods/fields in stubs may resolve to.

The algorithm wouldn't work when:
* an interface declares a method which is in stubs, and
* a class implements the interface by inheriting the method from its
  superclass; neither the class nor its superclass are in stubs.
The problem was that once a matching method was found, only subclasses
would be traversed. In this case, the method would be found in the
interface, the class which implements it would be traversed but its
superclass would not.

This patch simplifies the algorithm and partially reverts a performance
optimization which caused the problem. As a result, there is a build
time regression from 5s to 8s.

The patch also adds gtests which test this behaviour. There were no
tests until now because stubs are not present in master-art manifest.
Get around this issue by using the actual core JARs as stubs but test
the behaviour on other classes.

Bug: 122551864
Test: m test-art-host-gtest-hiddenapi_test
Change-Id: I63751c5ef517c8e9d3a157dfbec8de01bd99c2d4
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 67d85c1..eac3511 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -38,6 +38,7 @@
   GetMethodSignature \
   HiddenApi \
   HiddenApiSignatures \
+  HiddenApiStubs \
   ImageLayoutA \
   ImageLayoutB \
   IMTA \
@@ -188,7 +189,7 @@
 ART_GTEST_dex2oat_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) ManyMethods Statics VerifierDeps MainUncompressed EmptyUncompressed StringLiterals
 ART_GTEST_dex2oat_image_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) Statics VerifierDeps
 ART_GTEST_exception_test_DEX_DEPS := ExceptionHandle
-ART_GTEST_hiddenapi_test_DEX_DEPS := HiddenApi
+ART_GTEST_hiddenapi_test_DEX_DEPS := HiddenApi HiddenApiStubs
 ART_GTEST_hidden_api_test_DEX_DEPS := HiddenApiSignatures
 ART_GTEST_image_test_DEX_DEPS := ImageLayoutA ImageLayoutB DefaultMethods VerifySoftFailDuringClinit
 ART_GTEST_imtable_test_DEX_DEPS := IMTA IMTB
diff --git a/test/HiddenApi/AbstractPackageClass.java b/test/HiddenApi/AbstractPackageClass.java
new file mode 100644
index 0000000..8f955ca
--- /dev/null
+++ b/test/HiddenApi/AbstractPackageClass.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+abstract class AbstractPackageClass {
+  public void publicMethod2() {}
+}
diff --git a/test/HiddenApi/PackageClass.java b/test/HiddenApi/PackageClass.java
new file mode 100644
index 0000000..eece100
--- /dev/null
+++ b/test/HiddenApi/PackageClass.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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 PackageClass extends AbstractPackageClass implements PublicInterface {
+  public void publicMethod1() {}
+}
diff --git a/test/HiddenApi/PublicInterface.java b/test/HiddenApi/PublicInterface.java
new file mode 100644
index 0000000..77a3709
--- /dev/null
+++ b/test/HiddenApi/PublicInterface.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2019 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 PublicInterface {
+  void publicMethod1();
+  void publicMethod2();
+}
diff --git a/test/HiddenApiStubs/HiddenApi b/test/HiddenApiStubs/HiddenApi
new file mode 100644
index 0000000..6841ab5
--- /dev/null
+++ b/test/HiddenApiStubs/HiddenApi
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 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 PublicInterface {
+  void publicMethod();
+}
diff --git a/test/HiddenApiStubs/PublicInterface.java b/test/HiddenApiStubs/PublicInterface.java
new file mode 100644
index 0000000..77a3709
--- /dev/null
+++ b/test/HiddenApiStubs/PublicInterface.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2019 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 PublicInterface {
+  void publicMethod1();
+  void publicMethod2();
+}
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc
index f426d02..2692f68 100644
--- a/tools/hiddenapi/hiddenapi.cc
+++ b/tools/hiddenapi/hiddenapi.cc
@@ -124,6 +124,7 @@
   }
 
   inline bool IsPublic() const { return HasAccessFlags(kAccPublic); }
+  inline bool IsInterface() const { return HasAccessFlags(kAccInterface); }
 
   inline bool Equals(const DexClass& other) const {
     bool equals = strcmp(GetDescriptor(), other.GetDescriptor()) == 0;
@@ -344,13 +345,13 @@
   // See comment on Hierarchy::ForEachResolvableMember.
   template<typename Fn>
   bool ForEachResolvableMember(const DexMember& other, Fn fn) {
-    return ForEachResolvableMember_Impl(other, fn) != ResolutionResult::kNotFound;
+    std::vector<HierarchyClass*> visited;
+    return ForEachResolvableMember_Impl(other, fn, true, true, visited);
   }
 
   // Returns true if this class contains at least one member matching `other`.
   bool HasMatchingMember(const DexMember& other) {
-    return ForEachMatchingMember(
-        other, [](const DexMember&) { return true; }) != ResolutionResult::kNotFound;
+    return ForEachMatchingMember(other, [](const DexMember&) { return true; });
   }
 
   // Recursively iterates over all subclasses of this class and invokes `fn`
@@ -366,62 +367,60 @@
   }
 
  private:
-  // Result of resolution which takes into account whether the member was found
-  // for the first time or not. This is just a performance optimization to prevent
-  // re-visiting previously visited members.
-  // Note that order matters. When accumulating results, we always pick the maximum.
-  enum class ResolutionResult {
-    kNotFound,
-    kFoundOld,
-    kFoundNew,
-  };
-
-  inline ResolutionResult Accumulate(ResolutionResult a, ResolutionResult b) {
-    return static_cast<ResolutionResult>(
-        std::max(static_cast<unsigned>(a), static_cast<unsigned>(b)));
-  }
-
   template<typename Fn>
-  ResolutionResult ForEachResolvableMember_Impl(const DexMember& other, Fn fn) {
-    // First try to find a member matching `other` in this class.
-    ResolutionResult foundInClass = ForEachMatchingMember(other, fn);
-
-    switch (foundInClass) {
-      case ResolutionResult::kFoundOld:
-        // A matching member was found and previously explored. All subclasses
-        // must have been explored too.
-        break;
-
-      case ResolutionResult::kFoundNew:
-        // A matching member was found and this was the first time it was visited.
-        // If it is a virtual method, visit all methods overriding/implementing it too.
-        if (other.IsVirtualMethod()) {
-          for (HierarchyClass* subclass : extended_by_) {
-            subclass->ForEachOverridingMember(other, fn);
-          }
-        }
-        break;
-
-      case ResolutionResult::kNotFound:
-        // A matching member was not found in this class. Explore the superclasses
-        // and implemented interfaces.
-        for (HierarchyClass* superclass : extends_) {
-          foundInClass = Accumulate(
-              foundInClass, superclass->ForEachResolvableMember_Impl(other, fn));
-        }
-        break;
+  bool ForEachResolvableMember_Impl(const DexMember& other,
+                                    Fn fn,
+                                    bool allow_explore_up,
+                                    bool allow_explore_down,
+                                    std::vector<HierarchyClass*> visited) {
+    if (std::find(visited.begin(), visited.end(), this) == visited.end()) {
+      visited.push_back(this);
+    } else {
+      return false;
     }
 
-    return foundInClass;
+    // First try to find a member matching `other` in this class.
+    bool found = ForEachMatchingMember(other, fn);
+
+    // If not found, see if it is inherited from parents. Note that this will not
+    // revisit parents already in `visited`.
+    if (!found && allow_explore_up) {
+      for (HierarchyClass* superclass : extends_) {
+        found |= superclass->ForEachResolvableMember_Impl(
+            other,
+            fn,
+            /* allow_explore_up */ true,
+            /* allow_explore_down */ false,
+            visited);
+      }
+    }
+
+    // If this is a virtual method, continue exploring into subclasses so as to visit
+    // all overriding methods. Allow subclasses to explore their superclasses if this
+    // is an interface. This is needed to find implementations of this interface's
+    // methods inherited from superclasses (b/122551864).
+    if (allow_explore_down && other.IsVirtualMethod()) {
+      for (HierarchyClass* subclass : extended_by_) {
+        subclass->ForEachResolvableMember_Impl(
+            other,
+            fn,
+            /* allow_explore_up */ GetOneDexClass().IsInterface(),
+            /* allow_explore_down */ true,
+            visited);
+      }
+    }
+
+    return found;
   }
 
   template<typename Fn>
-  ResolutionResult ForEachMatchingMember(const DexMember& other, Fn fn) {
-    ResolutionResult found = ResolutionResult::kNotFound;
+  bool ForEachMatchingMember(const DexMember& other, Fn fn) {
+    bool found = false;
     auto compare_member = [&](const DexMember& member) {
+      // TODO(dbrazdil): Check whether class of `other` can access `member`.
       if (member == other) {
-        found = Accumulate(found, fn(member) ? ResolutionResult::kFoundNew
-                                             : ResolutionResult::kFoundOld);
+        found = true;
+        fn(member);
       }
     };
     for (const DexClass& dex_class : dex_classes_) {
@@ -435,20 +434,6 @@
     return found;
   }
 
-  template<typename Fn>
-  void ForEachOverridingMember(const DexMember& other, Fn fn) {
-    CHECK(other.IsVirtualMethod());
-    ResolutionResult found = ForEachMatchingMember(other, fn);
-    if (found == ResolutionResult::kFoundOld) {
-      // No need to explore further.
-      return;
-    } else {
-      for (HierarchyClass* subclass : extended_by_) {
-        subclass->ForEachOverridingMember(other, fn);
-      }
-    }
-  }
-
   // DexClass entries of this class found across all the provided dex files.
   std::vector<DexClass> dex_classes_;
 
@@ -1070,12 +1055,7 @@
                   std::string entry = boot_member.GetApiEntry();
                   auto it = boot_members.find(entry);
                   CHECK(it != boot_members.end());
-                  if (it->second.Contains(stub_api_list)) {
-                    return false;  // has been marked before
-                  } else {
-                    it->second |= stub_api_list;
-                    return true;  // marked for the first time
-                  }
+                  it->second |= stub_api_list;
                 });
             if (!resolved) {
               unresolved.insert(stub_member.GetApiEntry());
diff --git a/tools/hiddenapi/hiddenapi_test.cc b/tools/hiddenapi/hiddenapi_test.cc
index 7ef5b3d..74feb8a 100644
--- a/tools/hiddenapi/hiddenapi_test.cc
+++ b/tools/hiddenapi/hiddenapi_test.cc
@@ -16,6 +16,8 @@
 
 #include <fstream>
 
+#include "android-base/strings.h"
+
 #include "base/unix_file/fd_file.h"
 #include "base/zip_archive.h"
 #include "common_runtime_test.h"
@@ -41,9 +43,9 @@
     return file_path;
   }
 
-  std::unique_ptr<const DexFile> RunHiddenApi(const ScratchFile& flags_csv,
-                                              const std::vector<std::string>& extra_args,
-                                              ScratchFile* out_dex) {
+  std::unique_ptr<const DexFile> RunHiddenapiEncode(const ScratchFile& flags_csv,
+                                                    const std::vector<std::string>& extra_args,
+                                                    const ScratchFile& out_dex) {
     std::string error;
     ScratchFile in_dex;
     std::unique_ptr<ZipArchive> jar(
@@ -68,18 +70,42 @@
     argv_str.insert(argv_str.end(), extra_args.begin(), extra_args.end());
     argv_str.push_back("encode");
     argv_str.push_back("--input-dex=" + in_dex.GetFilename());
-    argv_str.push_back("--output-dex=" + out_dex->GetFilename());
+    argv_str.push_back("--output-dex=" + out_dex.GetFilename());
     argv_str.push_back("--api-flags=" + flags_csv.GetFilename());
     argv_str.push_back("--no-force-assign-all");
     int return_code = ExecAndReturnCode(argv_str, &error);
     if (return_code == 0) {
-      return OpenDex(*out_dex);
+      return OpenDex(out_dex);
     } else {
       LOG(ERROR) << "HiddenApi binary exited with unexpected return code " << return_code;
       return nullptr;
     }
   }
 
+  bool RunHiddenapiList(const ScratchFile& out_flags_csv) {
+    std::string error;
+    std::string boot_jar = GetTestDexFileName("HiddenApi");
+    std::string stub_jar = GetTestDexFileName("HiddenApiStubs");
+    std::string boot_cp = android::base::Join(GetLibCoreDexFileNames(), ":");
+
+    std::vector<std::string> argv_str;
+    argv_str.push_back(GetHiddenApiCmd());
+    argv_str.push_back("list");
+    for (const std::string& core_jar : GetLibCoreDexFileNames()) {
+      argv_str.push_back("--boot-dex=" + core_jar);
+    }
+    argv_str.push_back("--boot-dex=" + boot_jar);
+    argv_str.push_back("--public-stub-classpath=" + boot_cp + ":" + stub_jar);
+    argv_str.push_back("--out-api-flags=" + out_flags_csv.GetFilename());
+    int return_code = ExecAndReturnCode(argv_str, &error);
+    if (return_code == 0) {
+      return true;
+    } else {
+      LOG(ERROR) << "HiddenApi binary exited with unexpected return code " << return_code;
+      return false;
+    }
+  }
+
   std::unique_ptr<const DexFile> OpenDex(const ScratchFile& file) {
     ArtDexFileLoader dex_loader;
     std::string error_msg;
@@ -113,6 +139,31 @@
     return ofs;
   }
 
+  std::map<std::string, std::string> ReadFlagsCsvFile(const ScratchFile& file) {
+    std::ifstream ifs(file.GetFilename());
+    std::map<std::string, std::string> flags;
+
+    for (std::string line; std::getline(ifs, line);) {
+      std::size_t comma = line.find(",");
+      if (comma == std::string::npos) {
+        flags.emplace(line, "");
+      } else {
+        flags.emplace(line.substr(0, comma), line.substr(comma + 1));
+      }
+    }
+
+    return flags;
+  }
+
+  std::string SafeMapGet(const std::string& key, const std::map<std::string, std::string>& map) {
+    auto it = map.find(key);
+    if (it == map.end()) {
+      LOG(FATAL) << "Key not found: " << key;
+      UNREACHABLE();
+    }
+    return it->second;
+  }
+
   const dex::ClassDef& FindClass(const char* desc, const DexFile& dex_file) {
     const dex::TypeId* type_id = dex_file.FindTypeId(desc);
     CHECK(type_id != nullptr) << "Could not find class " << desc;
@@ -220,7 +271,7 @@
       << "LMain;->ifield:LBadType1;,greylist" << std::endl
       << "LMain;->ifield:LBadType2;,greylist-max-o" << std::endl
       << "LMain;->ifield:LBadType3;,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Whitelist(), GetIFieldHiddenFlags(*dex_file));
 }
@@ -231,7 +282,7 @@
       << "LMain;->ifield:I,greylist" << std::endl
       << "LMain;->ifield:LBadType2;,greylist-max-o" << std::endl
       << "LMain;->ifield:LBadType3;,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Greylist(), GetIFieldHiddenFlags(*dex_file));
 }
@@ -242,7 +293,7 @@
       << "LMain;->ifield:LBadType1;,greylist" << std::endl
       << "LMain;->ifield:I,greylist-max-o" << std::endl
       << "LMain;->ifield:LBadType3;,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::GreylistMaxO(), GetIFieldHiddenFlags(*dex_file));
 }
@@ -253,7 +304,7 @@
       << "LMain;->ifield:LBadType1;,greylist" << std::endl
       << "LMain;->ifield:LBadType2;,greylist-max-o" << std::endl
       << "LMain;->ifield:I,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Blacklist(), GetIFieldHiddenFlags(*dex_file));
 }
@@ -263,7 +314,7 @@
   OpenStream(flags_csv)
       << "LMain;->ifield:LBadType1;,greylist" << std::endl
       << "LMain;->ifield:I,blacklist,greylist-max-o" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -272,7 +323,7 @@
   OpenStream(flags_csv)
       << "LMain;->ifield:LBadType2;,greylist-max-o" << std::endl
       << "LMain;->ifield:I,blacklist,greylist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -281,7 +332,7 @@
   OpenStream(flags_csv)
       << "LMain;->ifield:I,greylist,greylist-max-o" << std::endl
       << "LMain;->ifield:LBadType3;,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -291,7 +342,7 @@
       << "LMain;->sfield:LBadType1;,greylist" << std::endl
       << "LMain;->sfield:LBadType2;,greylist-max-o" << std::endl
       << "LMain;->sfield:LBadType3;,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Whitelist(), GetSFieldHiddenFlags(*dex_file));
 }
@@ -302,7 +353,7 @@
       << "LMain;->sfield:Ljava/lang/Object;,greylist" << std::endl
       << "LMain;->sfield:LBadType2;,greylist-max-o" << std::endl
       << "LMain;->sfield:LBadType3;,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Greylist(), GetSFieldHiddenFlags(*dex_file));
 }
@@ -313,7 +364,7 @@
       << "LMain;->sfield:LBadType1;,greylist" << std::endl
       << "LMain;->sfield:Ljava/lang/Object;,greylist-max-o" << std::endl
       << "LMain;->sfield:LBadType3;,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::GreylistMaxO(), GetSFieldHiddenFlags(*dex_file));
 }
@@ -324,7 +375,7 @@
       << "LMain;->sfield:LBadType1;,greylist" << std::endl
       << "LMain;->sfield:LBadType2;,greylist-max-o" << std::endl
       << "LMain;->sfield:Ljava/lang/Object;,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Blacklist(), GetSFieldHiddenFlags(*dex_file));
 }
@@ -334,7 +385,7 @@
   OpenStream(flags_csv)
       << "LMain;->sfield:LBadType1;,greylist" << std::endl
       << "LMain;->sfield:Ljava/lang/Object;,blacklist,greylist-max-o" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -343,7 +394,7 @@
   OpenStream(flags_csv)
       << "LMain;->sfield:LBadType2;,greylist-max-o" << std::endl
       << "LMain;->sfield:Ljava/lang/Object;,blacklist,greylist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -352,7 +403,7 @@
   OpenStream(flags_csv)
       << "LMain;->sfield:Ljava/lang/Object;,greylist,greylist-max-o" << std::endl
       << "LMain;->sfield:LBadType3;,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -362,7 +413,7 @@
       << "LMain;->imethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->imethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->imethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Whitelist(), GetIMethodHiddenFlags(*dex_file));
 }
@@ -373,7 +424,7 @@
       << "LMain;->imethod(J)V,greylist" << std::endl
       << "LMain;->imethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->imethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Greylist(), GetIMethodHiddenFlags(*dex_file));
 }
@@ -384,7 +435,7 @@
       << "LMain;->imethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->imethod(J)V,greylist-max-o" << std::endl
       << "LMain;->imethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::GreylistMaxO(), GetIMethodHiddenFlags(*dex_file));
 }
@@ -395,7 +446,7 @@
       << "LMain;->imethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->imethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->imethod(J)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Blacklist(), GetIMethodHiddenFlags(*dex_file));
 }
@@ -405,7 +456,7 @@
   OpenStream(flags_csv)
       << "LMain;->imethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->imethod(J)V,blacklist,greylist-max-o" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -414,7 +465,7 @@
   OpenStream(flags_csv)
       << "LMain;->imethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->imethod(J)V,blacklist,greylist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -423,7 +474,7 @@
   OpenStream(flags_csv)
       << "LMain;->imethod(J)V,greylist,greylist-max-o" << std::endl
       << "LMain;->imethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -433,7 +484,7 @@
       << "LMain;->smethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->smethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->smethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Whitelist(), GetSMethodHiddenFlags(*dex_file));
 }
@@ -444,7 +495,7 @@
       << "LMain;->smethod(Ljava/lang/Object;)V,greylist" << std::endl
       << "LMain;->smethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->smethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Greylist(), GetSMethodHiddenFlags(*dex_file));
 }
@@ -455,7 +506,7 @@
       << "LMain;->smethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->smethod(Ljava/lang/Object;)V,greylist-max-o" << std::endl
       << "LMain;->smethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::GreylistMaxO(), GetSMethodHiddenFlags(*dex_file));
 }
@@ -466,7 +517,7 @@
       << "LMain;->smethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->smethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->smethod(Ljava/lang/Object;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Blacklist(), GetSMethodHiddenFlags(*dex_file));
 }
@@ -476,7 +527,7 @@
   OpenStream(flags_csv)
       << "LMain;->smethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->smethod(Ljava/lang/Object;)V,blacklist,greylist-max-o" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -485,7 +536,7 @@
   OpenStream(flags_csv)
       << "LMain;->smethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->smethod(Ljava/lang/Object;)V,blacklist,greylist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -494,7 +545,7 @@
   OpenStream(flags_csv)
       << "LMain;->smethod(Ljava/lang/Object;)V,greylist,greylist-max-o" << std::endl
       << "LMain;->smethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -504,7 +555,7 @@
       << "LMain;->inmethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->inmethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->inmethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Whitelist(), GetINMethodHiddenFlags(*dex_file));
 }
@@ -515,7 +566,7 @@
       << "LMain;->inmethod(C)V,greylist" << std::endl
       << "LMain;->inmethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->inmethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Greylist(), GetINMethodHiddenFlags(*dex_file));
 }
@@ -526,7 +577,7 @@
       << "LMain;->inmethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->inmethod(C)V,greylist-max-o" << std::endl
       << "LMain;->inmethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::GreylistMaxO(), GetINMethodHiddenFlags(*dex_file));
 }
@@ -537,7 +588,7 @@
       << "LMain;->inmethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->inmethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->inmethod(C)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Blacklist(), GetINMethodHiddenFlags(*dex_file));
 }
@@ -547,7 +598,7 @@
   OpenStream(flags_csv)
       << "LMain;->inmethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->inmethod(C)V,blacklist,greylist-max-o" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -556,7 +607,7 @@
   OpenStream(flags_csv)
       << "LMain;->inmethod(C)V,blacklist,greylist" << std::endl
       << "LMain;->inmethod(LBadType2;)V,greylist-max-o" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -565,7 +616,7 @@
   OpenStream(flags_csv)
       << "LMain;->inmethod(C)V,greylist,greylist-max-o" << std::endl
       << "LMain;->inmethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -575,7 +626,7 @@
       << "LMain;->snmethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->snmethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->snmethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Whitelist(), GetSNMethodHiddenFlags(*dex_file));
 }
@@ -586,7 +637,7 @@
       << "LMain;->snmethod(Ljava/lang/Integer;)V,greylist" << std::endl
       << "LMain;->snmethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->snmethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Greylist(), GetSNMethodHiddenFlags(*dex_file));
 }
@@ -597,7 +648,7 @@
       << "LMain;->snmethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->snmethod(Ljava/lang/Integer;)V,greylist-max-o" << std::endl
       << "LMain;->snmethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::GreylistMaxO(), GetSNMethodHiddenFlags(*dex_file));
 }
@@ -608,7 +659,7 @@
       << "LMain;->snmethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->snmethod(LBadType2;)V,greylist-max-o" << std::endl
       << "LMain;->snmethod(Ljava/lang/Integer;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_NE(dex_file.get(), nullptr);
   ASSERT_EQ(hiddenapi::ApiList::Blacklist(), GetSNMethodHiddenFlags(*dex_file));
 }
@@ -618,7 +669,7 @@
   OpenStream(flags_csv)
       << "LMain;->snmethod(LBadType1;)V,greylist" << std::endl
       << "LMain;->snmethod(Ljava/lang/Integer;)V,blacklist,greylist-max-o" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -627,7 +678,7 @@
   OpenStream(flags_csv)
       << "LMain;->snmethod(Ljava/lang/Integer;)V,blacklist,greylist" << std::endl
       << "LMain;->snmethod(LBadType2;)V,greylist-max-o" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
@@ -636,8 +687,35 @@
   OpenStream(flags_csv)
       << "LMain;->snmethod(Ljava/lang/Integer;)V,greylist,greylist-max-o" << std::endl
       << "LMain;->snmethod(LBadType3;)V,blacklist" << std::endl;
-  auto dex_file = RunHiddenApi(flags_csv, {}, &dex);
+  auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
   ASSERT_EQ(dex_file.get(), nullptr);
 }
 
+// The following tests use this class hierarchy:
+//
+//    AbstractPackageClass  PublicInterface
+//           |                     |
+//           |    ┌----------------┘
+//           |    |
+//        PackageClass
+//
+// Only PublicInterface is in stubs.
+
+// Test a method declared in PublicInterface and defined in PackageClass.
+TEST_F(HiddenApiTest, InterfaceMethodImplemented) {
+  ScratchFile flags_csv;
+  ASSERT_TRUE(RunHiddenapiList(flags_csv));
+  auto flags = ReadFlagsCsvFile(flags_csv);
+  ASSERT_EQ(SafeMapGet("LPackageClass;->publicMethod1()V", flags), "whitelist");
+}
+
+// Test a method declared in PublicInterface, defined in AbstractPackageClass and
+// inherited by PackageClass.
+TEST_F(HiddenApiTest, InterfaceMethodImplementedInParent) {
+  ScratchFile flags_csv;
+  ASSERT_TRUE(RunHiddenapiList(flags_csv));
+  auto flags = ReadFlagsCsvFile(flags_csv);
+  ASSERT_EQ(SafeMapGet("LAbstractPackageClass;->publicMethod2()V", flags), "whitelist");
+}
+
 }  // namespace art