Merge "hiddenapi: Fix class hierarchy traversal"
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index bd306b6..21eee7a 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