Merge "Make classes of LOS objects non-movable."
diff --git a/libartbase/base/utils.cc b/libartbase/base/utils.cc
index 761c611..74cc5b9 100644
--- a/libartbase/base/utils.cc
+++ b/libartbase/base/utils.cc
@@ -38,6 +38,12 @@
 #include "AvailabilityMacros.h"  // For MAC_OS_X_VERSION_MAX_ALLOWED
 #endif
 
+#if defined(__BIONIC__)
+// membarrier(2) is only supported for target builds (b/111199492).
+#include <linux/membarrier.h>
+#include <sys/syscall.h>
+#endif
+
 #if defined(__linux__)
 #include <linux/unistd.h>
 #endif
@@ -207,4 +213,42 @@
   }
 }
 
+bool FlushInstructionPipeline() {
+  // membarrier(2) is only supported for target builds (b/111199492).
+#if defined(__BIONIC__)
+  static constexpr int kSyncCoreMask =
+      MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE |
+      MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE;
+  static bool have_probed = false;
+  static bool have_sync_core = false;
+
+  if (UNLIKELY(!have_probed)) {
+    // Probe membarrier(2) commands supported by kernel.
+    int commands = syscall(__NR_membarrier, MEMBARRIER_CMD_QUERY, 0);
+    if (commands >= 0) {
+      have_sync_core = (commands & kSyncCoreMask) == kSyncCoreMask;
+      if (have_sync_core) {
+        // Register with kernel that we'll be using the private expedited sync core command.
+        CheckedCall(syscall,
+                    "membarrier register sync core",
+                    __NR_membarrier,
+                    MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE,
+                    0);
+      }
+    }
+    have_probed = true;
+  }
+
+  if (have_sync_core) {
+    CheckedCall(syscall,
+                "membarrier sync core",
+                __NR_membarrier,
+                MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE,
+                0);
+    return true;
+  }
+#endif  // defined(__BIONIC__)
+  return false;
+}
+
 }  // namespace art
diff --git a/libartbase/base/utils.h b/libartbase/base/utils.h
index ba61e1b..4449941 100644
--- a/libartbase/base/utils.h
+++ b/libartbase/base/utils.h
@@ -179,16 +179,19 @@
 // Sleep forever and never come back.
 NO_RETURN void SleepForever();
 
-inline void FlushInstructionCache(char* begin, char* end) {
-  __builtin___clear_cache(begin, end);
-}
-
 inline void FlushDataCache(char* begin, char* end) {
   // Same as FlushInstructionCache for lack of other builtin. __builtin___clear_cache
   // flushes both caches.
   __builtin___clear_cache(begin, end);
 }
 
+inline void FlushInstructionCache(char* begin, char* end) {
+  __builtin___clear_cache(begin, end);
+}
+
+// Flush instruction pipeline. Returns true on success, false if feature is unsupported.
+bool FlushInstructionPipeline();
+
 template <typename T>
 constexpr PointerSize ConvertToPointerSize(T any) {
   if (any == 4 || any == 8) {
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index f693524..9b69166 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -417,7 +417,6 @@
       case Intrinsics::kMemoryPokeIntNative:
       case Intrinsics::kMemoryPokeLongNative:
       case Intrinsics::kMemoryPokeShortNative:
-        return HiddenApiAccessFlags::kDarkGreylist;
       case Intrinsics::kVarHandleFullFence:
       case Intrinsics::kVarHandleAcquireFence:
       case Intrinsics::kVarHandleReleaseFence:
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 2b2898c..461eb81 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -28,6 +28,7 @@
 #include "base/stl_util.h"
 #include "base/systrace.h"
 #include "base/time_utils.h"
+#include "base/utils.h"
 #include "cha.h"
 #include "debugger_interface.h"
 #include "dex/dex_file_loader.h"
@@ -53,8 +54,9 @@
 namespace art {
 namespace jit {
 
-static constexpr int kProtData = PROT_READ | PROT_WRITE;
 static constexpr int kProtCode = PROT_READ | PROT_EXEC;
+static constexpr int kProtData = PROT_READ | PROT_WRITE;
+static constexpr int kProtProfile = PROT_READ;
 
 static constexpr size_t kCodeSizeLogThreshold = 50 * KB;
 static constexpr size_t kStackMapSizeLogThreshold = 50 * KB;
@@ -192,7 +194,7 @@
   //         to profile system server.
   // NOTE 2: We could just not create the code section at all but we will need to
   //         special case too many cases.
-  int memmap_flags_prot_code = used_only_for_profile_data ? (kProtCode & ~PROT_EXEC) : kProtCode;
+  int memmap_flags_prot_code = used_only_for_profile_data ? kProtProfile : kProtCode;
 
   std::string error_str;
   // Map name specific for android_os_Debug.cpp accounting.
@@ -801,6 +803,16 @@
     // https://android.googlesource.com/kernel/msm/+/3fbe6bc28a6b9939d0650f2f17eb5216c719950c
     FlushInstructionCache(reinterpret_cast<char*>(code_ptr),
                           reinterpret_cast<char*>(code_ptr + code_size));
+
+    // Ensure CPU instruction pipelines are flushed for all cores. This is necessary for
+    // correctness as code may still be in instruction pipelines despite the i-cache flush. It is
+    // not safe to assume that changing permissions with mprotect (RX->RWX->RX) will cause a TLB
+    // shootdown (incidentally invalidating the CPU pipelines by sending an IPI to all cores to
+    // notify them of the TLB invalidation). Some architectures, notably ARM and ARM64, have
+    // hardware support that broadcasts TLB invalidations and so their kernels have no software
+    // based TLB shootdown. FlushInstructionPipeline() is a wrapper around the Linux
+    // membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED) syscall which does the appropriate flushing.
+    FlushInstructionPipeline();
     DCHECK(!Runtime::Current()->IsAotCompiler());
     if (has_should_deoptimize_flag) {
       method_header->SetHasShouldDeoptimizeFlag();
diff --git a/test/004-StackWalk/build b/test/004-StackWalk/build
index eeecbfc..3de541c 100644
--- a/test/004-StackWalk/build
+++ b/test/004-StackWalk/build
@@ -18,10 +18,8 @@
 set -e
 
 # This test depends on the exact format of the DEX file. Since dx is deprecated,
-# the classes.dex file is packaged as a test input. It was created with:
-#
-# $ javac -g -Xlint:-options -source 1.7 -target 1.7 -d classes src/Main.java
-# $ dx --debug --dex --output=classes.dex classes
+# the classes.dex file is packaged as a test input. See src/Main.java file
+# to check how it was created.
 
 # Wrapper function for javac which for this test does nothing as the
 # test uses a pre-built DEX file.
diff --git a/test/004-StackWalk/classes.dex b/test/004-StackWalk/classes.dex
index ad45296..61a7277 100644
--- a/test/004-StackWalk/classes.dex
+++ b/test/004-StackWalk/classes.dex
Binary files differ
diff --git a/test/004-StackWalk/src/Main.java b/test/004-StackWalk/src/Main.java
index 072b1d0..2a098f7 100644
--- a/test/004-StackWalk/src/Main.java
+++ b/test/004-StackWalk/src/Main.java
@@ -1,19 +1,36 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+// This test depends on the exact format of the DEX file. Since dx is deprecated,
+// the classes.dex file is packaged as a test input. It was created with:
+//
+// $ javac -g -Xlint:-options -source 1.7 -target 1.7 -d classes src/Main.java
+// $ dx --debug --dex --output=classes.dex classes
+
 public class Main {
   public Main() {
   }
 
-  boolean doThrow = false;
-
   int $noinline$f() throws Exception {
-    g(1);
-    g(2);
-
-    // This currently defeats inlining of `f`.
-    if (doThrow) { throw new Error(); }
+    $noinline$g(1);
+    $noinline$g(2);
     return 0;
   }
 
-  void g(int num_calls) {
+  void $noinline$g(int num_calls) {
     if (num_calls == 1) {
       System.out.println("1st call");
     } else if (num_calls == 2) {
@@ -81,11 +98,14 @@
     s4 = s18 = s19;
     s += s4;
     s += s18;
-    stackmap(0);
-    return s;
+    // Add a branch to workaround ART's large methods without branches heuristic.
+    if (testStackWalk(0) != 0) {
+      return s;
+    }
+    return s18;
   }
 
-  native int stackmap(int x);
+  native int testStackWalk(int x);
 
   public static void main(String[] args) throws Exception {
     System.loadLibrary(args[0]);
diff --git a/test/004-StackWalk/stack_walk_jni.cc b/test/004-StackWalk/stack_walk_jni.cc
index 89e2e66..53e0dae 100644
--- a/test/004-StackWalk/stack_walk_jni.cc
+++ b/test/004-StackWalk/stack_walk_jni.cc
@@ -20,7 +20,7 @@
 
 namespace art {
 
-#define CHECK_REGS(...) do { \
+#define CHECK_REGS_ARE_REFERENCES(...) do { \
   int t[] = {__VA_ARGS__}; \
   int t_size = sizeof(t) / sizeof(*t); \
   CheckReferences(t, t_size, GetNativePcOffset()); \
@@ -43,40 +43,50 @@
     // Given the method name and the number of times the method has been called,
     // we know the Dex registers with live reference values. Assert that what we
     // find is what is expected.
-    if (m_name == "f") {
+    if (m_name == "$noinline$f") {
       if (gJava_StackWalk_refmap_calls == 1) {
         CHECK_EQ(1U, GetDexPc());
-        CHECK_REGS(4);
+        CHECK_REGS_ARE_REFERENCES(1);
       } else {
         CHECK_EQ(gJava_StackWalk_refmap_calls, 2);
         CHECK_EQ(5U, GetDexPc());
-        CHECK_REGS(4);
+        CHECK_REGS_ARE_REFERENCES(1);
       }
-    } else if (m_name == "g") {
+      found_f_ = true;
+    } else if (m_name == "$noinline$g") {
       if (gJava_StackWalk_refmap_calls == 1) {
         CHECK_EQ(0xcU, GetDexPc());
-        CHECK_REGS(0, 2);  // Note that v1 is not in the minimal root set
+        CHECK_REGS_ARE_REFERENCES(0, 2);  // Note that v1 is not in the minimal root set
       } else {
         CHECK_EQ(gJava_StackWalk_refmap_calls, 2);
         CHECK_EQ(0xcU, GetDexPc());
-        CHECK_REGS(0, 2);
+        CHECK_REGS_ARE_REFERENCES(0, 2);
       }
+      found_g_ = true;
     } else if (m_name == "shlemiel") {
       if (gJava_StackWalk_refmap_calls == 1) {
         CHECK_EQ(0x380U, GetDexPc());
-        CHECK_REGS(2, 4, 5, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 21, 25);
+        CHECK_REGS_ARE_REFERENCES(2, 4, 5, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 21, 25);
       } else {
         CHECK_EQ(gJava_StackWalk_refmap_calls, 2);
         CHECK_EQ(0x380U, GetDexPc());
-        CHECK_REGS(2, 4, 5, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 21, 25);
+        CHECK_REGS_ARE_REFERENCES(2, 4, 5, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 21, 25);
       }
+      found_shlemiel_ = true;
     }
 
     return true;
   }
+
+  ~TestReferenceMapVisitor() {
+  }
+
+  bool found_f_ = false;
+  bool found_g_ = false;
+  bool found_shlemiel_ = false;
 };
 
-extern "C" JNIEXPORT jint JNICALL Java_Main_stackmap(JNIEnv*, jobject, jint count) {
+extern "C" JNIEXPORT jint JNICALL Java_Main_testStackWalk(JNIEnv*, jobject, jint count) {
   ScopedObjectAccess soa(Thread::Current());
   CHECK_EQ(count, 0);
   gJava_StackWalk_refmap_calls++;
@@ -84,17 +94,9 @@
   // Visitor
   TestReferenceMapVisitor mapper(soa.Self());
   mapper.WalkStack();
-
-  return count + 1;
-}
-
-extern "C" JNIEXPORT jint JNICALL Java_Main_refmap2(JNIEnv*, jobject, jint count) {
-  ScopedObjectAccess soa(Thread::Current());
-  gJava_StackWalk_refmap_calls++;
-
-  // Visitor
-  TestReferenceMapVisitor mapper(soa.Self());
-  mapper.WalkStack();
+  CHECK(mapper.found_f_);
+  CHECK(mapper.found_g_);
+  CHECK(mapper.found_shlemiel_);
 
   return count + 1;
 }
diff --git a/test/StackWalk2/StackWalk2.java b/test/StackWalk2/StackWalk2.java
deleted file mode 100644
index 5e7b22c..0000000
--- a/test/StackWalk2/StackWalk2.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2011 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 class StackWalk2 {
-  // use v1 for this
-
-  String str = new String();  // use v0 for str in <init>
-
-  int f() {
-    g(1);  // use v0 for 1, v1 for this
-    g(2);  // use v0 for 2, v1 for this
-    strTest();  // use v1 for this
-    return 0;
-  }
-
-  void g(int num_calls) throws RuntimeException {
-    if (num_calls == 1) {  // use v0 for 1, v3 for num_calls
-      System.logI("1st call");  // use v0 for PrintStream, v1 for "1st call"
-      refmap2(24);  // use v0 for 24, v2 for this
-    } else if (num_calls == 2) {  // use v0 for 2, v3 for num_calls
-      System.logI("2nd call");  // use v0 for PrintStream, v1 for "2nd call"
-      refmap2(25);  // use v0 for 24, v2 for this
-    }
-    throw new RuntimeException();  // use v0 for new RuntimeException
-  }
-
-  void strTest() {
-    System.logI(str);  // use v1 for PrintStream, v2, v3 for str
-    str = null;  // use v1 for null, v3 for str
-    str = new String("ya");  // use v2 for "ya", v1 for new String
-    String s = str;  // use v0, v1, v3
-    System.logI(str);  // use v1 for PrintStream, v2, v3 for str
-    System.logI(s);  // use v1 for PrintStream, v0 for s
-    s = null;  // use v0
-    System.logI(s);  // use v1 for PrintStream, v0 for s
-  }
-
-  native int refmap2(int x);
-
-  public static void main(String[] args) {
-    System.loadLibrary(args[0]);
-    StackWalk2 st = new StackWalk2();
-    st.f();
-  }
-}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 40b699e..f12b99a 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -697,9 +697,9 @@
         "description": ["Tests that depend on input-vdex are not supported with compact dex"]
     },
     {
-        "tests": "661-oat-writer-layout",
+        "tests": ["661-oat-writer-layout", "004-StackWalk"],
         "variant": "interp-ac | interpreter | jit | no-prebuild | no-image | trace | redefine-stress | jvmti-stress",
-        "description": ["Test is designed to only check --compiler-filter=speed"]
+        "description": ["Tests are designed to only check --optimizing"]
     },
     {
         "tests": "674-HelloWorld-Dm",
diff --git a/tools/ahat/etc/ahat_api.txt b/tools/ahat/etc/ahat_api.txt
index 5426f7b..7aa994a 100644
--- a/tools/ahat/etc/ahat_api.txt
+++ b/tools/ahat/etc/ahat_api.txt
@@ -96,6 +96,7 @@
     method public boolean isArrayInstance();
     method public boolean isClassInstance();
     method public boolean isClassObj();
+    method public boolean isInstanceOfClass(java.lang.String);
     method public boolean isPlaceHolder();
     method public boolean isRoot();
     method public boolean isStronglyReachable();
@@ -226,6 +227,7 @@
     method public int getLineNumber();
     method public java.lang.String getMethodName();
     method public void getObjects(java.lang.String, java.lang.String, java.util.Collection<com.android.ahat.heapdump.AhatInstance>);
+    method public void getObjects(java.util.function.Predicate<com.android.ahat.heapdump.AhatInstance>, java.util.function.Consumer<com.android.ahat.heapdump.AhatInstance>);
     method public java.util.List<com.android.ahat.heapdump.Site.ObjectsInfo> getObjectsInfos();
     method public com.android.ahat.heapdump.Site getParent();
     method public java.lang.String getSignature();
diff --git a/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java b/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java
index 1a8f018..81611b6 100644
--- a/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java
+++ b/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java
@@ -16,6 +16,7 @@
 
 package com.android.ahat;
 
+import com.android.ahat.heapdump.AhatHeap;
 import com.android.ahat.heapdump.AhatInstance;
 import com.android.ahat.heapdump.AhatSnapshot;
 import com.android.ahat.heapdump.Site;
@@ -24,6 +25,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Predicate;
 
 class ObjectsHandler implements AhatHandler {
   private static final String OBJECTS_ID = "objects";
@@ -34,32 +36,102 @@
     mSnapshot = snapshot;
   }
 
+  /**
+   * Get the list of instances that match the given site, class, and heap
+   * filters. This method is public to facilitate testing.
+   *
+   * @param site the site to get instances from
+   * @param className non-null name of the class to restrict instances to.
+   * @param subclass if true, include instances of subclasses of the named class.
+   * @param heapName name of the heap to restrict instances to. May be null to
+   *                 allow instances on any heap.
+   * @return list of matching instances
+   */
+  public static List<AhatInstance> getObjects(
+      Site site, String className, boolean subclass, String heapName) {
+    Predicate<AhatInstance> predicate = (x) -> {
+      return (heapName == null || x.getHeap().getName().equals(heapName))
+        && (subclass ? x.isInstanceOfClass(className) : className.equals(x.getClassName()));
+    };
+
+    List<AhatInstance> insts = new ArrayList<AhatInstance>();
+    site.getObjects(predicate, x -> insts.add(x));
+    return insts;
+  }
+
   @Override
   public void handle(Doc doc, Query query) throws IOException {
     int id = query.getInt("id", 0);
-    String className = query.get("class", null);
+    String className = query.get("class", "java.lang.Object");
     String heapName = query.get("heap", null);
+    boolean subclass = (query.getInt("subclass", 0) != 0);
     Site site = mSnapshot.getSite(id);
 
-    List<AhatInstance> insts = new ArrayList<AhatInstance>();
-    site.getObjects(heapName, className, insts);
+    List<AhatInstance> insts = getObjects(site, className, subclass, heapName);
     Collections.sort(insts, Sort.defaultInstanceCompare(mSnapshot));
 
-    doc.title("Objects");
+    doc.title("Instances");
 
-    SizeTable.table(doc, mSnapshot.isDiffed(),
-        new Column("Heap"),
-        new Column("Object"));
+    // Write a description of the current settings, with links to adjust the
+    // settings, such as:
+    //    Site:           ROOT -
+    //    Class:          android.os.Binder
+    //    Subclasses:     excluded (switch to included)
+    //    Heap:           any (switch to app, image, zygote)
+    //    Count:          17,424
+    doc.descriptions();
+    doc.description(DocString.text("Site"), Summarizer.summarize(site));
+    doc.description(DocString.text("Class"), DocString.text(className));
 
-    SubsetSelector<AhatInstance> selector = new SubsetSelector(query, OBJECTS_ID, insts);
-    for (AhatInstance inst : selector.selected()) {
-      AhatInstance base = inst.getBaseline();
-      SizeTable.row(doc, inst.getSize(), base.getSize(),
-          DocString.text(inst.getHeap().getName()),
-          Summarizer.summarize(inst));
+    DocString subclassChoice = DocString.text(subclass ? "included" : "excluded");
+    subclassChoice.append(" (switch to ");
+    subclassChoice.appendLink(query.with("subclass", subclass ? 0 : 1),
+      DocString.text(subclass ? "excluded" : "included"));
+    subclassChoice.append(")");
+    doc.description(DocString.text("Subclasses"), subclassChoice);
+
+    DocString heapChoice = DocString.text(heapName == null ? "any" : heapName);
+    heapChoice.append(" (switch to ");
+    String comma = "";
+    for (AhatHeap heap : mSnapshot.getHeaps()) {
+      if (!heap.getName().equals(heapName)) {
+        heapChoice.append(comma);
+        heapChoice.appendLink(
+            query.with("heap", heap.getName()),
+            DocString.text(heap.getName()));
+        comma = ", ";
+      }
     }
-    SizeTable.end(doc);
-    selector.render(doc);
+    if (heapName != null) {
+      heapChoice.append(comma);
+      heapChoice.appendLink(
+          query.with("heap", null),
+          DocString.text("any"));
+    }
+    heapChoice.append(")");
+    doc.description(DocString.text("Heap"), heapChoice);
+
+    doc.description(DocString.text("Count"), DocString.format("%,14d", insts.size()));
+    doc.end();
+    doc.println(DocString.text(""));
+
+    if (insts.isEmpty()) {
+      doc.println(DocString.text("(none)"));
+    } else {
+      SizeTable.table(doc, mSnapshot.isDiffed(),
+          new Column("Heap"),
+          new Column("Object"));
+
+      SubsetSelector<AhatInstance> selector = new SubsetSelector(query, OBJECTS_ID, insts);
+      for (AhatInstance inst : selector.selected()) {
+        AhatInstance base = inst.getBaseline();
+        SizeTable.row(doc, inst.getSize(), base.getSize(),
+            DocString.text(inst.getHeap().getName()),
+            Summarizer.summarize(inst));
+      }
+      SizeTable.end(doc);
+      selector.render(doc);
+    }
   }
 }
 
diff --git a/tools/ahat/src/main/com/android/ahat/Query.java b/tools/ahat/src/main/com/android/ahat/Query.java
index 9c2783c..5f064cd 100644
--- a/tools/ahat/src/main/com/android/ahat/Query.java
+++ b/tools/ahat/src/main/com/android/ahat/Query.java
@@ -79,7 +79,9 @@
   /**
    * Return a uri suitable for an href target that links to the current
    * page, except with the named query parameter set to the new value.
-   *
+   * <p>
+   * <code>value</code> may be null to remove the named query parameter.
+   * <p>
    * The generated parameters will be sorted alphabetically so it is easier to
    * test.
    */
@@ -92,11 +94,13 @@
     params.put(name, value);
     String and = "";
     for (Map.Entry<String, String> entry : params.entrySet()) {
-      newQuery.append(and);
-      newQuery.append(entry.getKey());
-      newQuery.append('=');
-      newQuery.append(entry.getValue());
-      and = "&";
+      if (entry.getValue() != null) {
+        newQuery.append(and);
+        newQuery.append(entry.getKey());
+        newQuery.append('=');
+        newQuery.append(entry.getValue());
+        and = "&";
+      }
     }
     return DocString.uri(newQuery.toString());
   }
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
index 4c60d8b..0511798 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
@@ -107,21 +107,6 @@
     return new ReferenceIterator();
   }
 
-  /**
-   * Returns true if this is an instance of a (subclass of a) class with the
-   * given name.
-   */
-  private boolean isInstanceOfClass(String className) {
-    AhatClassObj cls = getClassObj();
-    while (cls != null) {
-      if (className.equals(cls.getName())) {
-        return true;
-      }
-      cls = cls.getSuperClassObj();
-    }
-    return false;
-  }
-
   @Override public String asString(int maxChars) {
     if (!isInstanceOfClass("java.lang.String")) {
       return null;
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
index 3d691c7..c85a057 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
@@ -330,6 +330,25 @@
   }
 
   /**
+   * Returns true if this is an instance of a (subclass of a) class with the
+   * given name.
+   *
+   * @param className the name of the class to check for
+   * @return true if this is an instance of a (subclass of a) class with the
+   *              given name
+   */
+  public boolean isInstanceOfClass(String className) {
+    AhatClassObj cls = getClassObj();
+    while (cls != null) {
+      if (className.equals(cls.getName())) {
+        return true;
+      }
+      cls = cls.getSuperClassObj();
+    }
+    return false;
+  }
+
+  /**
    * Returns true if the given instance is an array instance.
    *
    * @return true if the given instance is an array instance
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/Site.java b/tools/ahat/src/main/com/android/ahat/heapdump/Site.java
index 46a1729..4f0660f 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/Site.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/Site.java
@@ -23,6 +23,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * Used to collection information about objects allocated at a particular
@@ -259,22 +261,37 @@
    *                 every heap should be collected.
    * @param className the name of the class the collected objects should
    *                  belong to. This may be null to indicate objects of
-   *                  every class should be collected.
+   *                  every class should be collected. Instances of subclasses
+   *                  of this class are not included.
    * @param objects out parameter. A collection of objects that all
    *                collected objects should be added to.
    */
   public void getObjects(String heapName, String className, Collection<AhatInstance> objects) {
+    Predicate<AhatInstance> predicate = x -> {
+      return (heapName == null || x.getHeap().getName().equals(heapName))
+        && (className == null || x.getClassName().equals(className));
+    };
+    getObjects(predicate, x -> objects.add(x));
+  }
+
+  /**
+   * Collects the objects allocated under this site, filtered by the given
+   * predicate.
+   * Includes objects allocated in children sites.
+   * @param predicate limit instances to those satisfying this predicate
+   * @param consumer consumer of the objects
+   */
+  public void getObjects(Predicate<AhatInstance> predicate, Consumer<AhatInstance> consumer) {
     for (AhatInstance inst : mObjects) {
-      if ((heapName == null || inst.getHeap().getName().equals(heapName))
-          && (className == null || inst.getClassName().equals(className))) {
-        objects.add(inst);
+      if (predicate.test(inst)) {
+        consumer.accept(inst);
       }
     }
 
     // Recursively visit children. Recursion should be okay here because the
     // stack depth is limited by a reasonable amount (128 frames or so).
     for (Site child : mChildren) {
-      child.getObjects(heapName, className, objects);
+      child.getObjects(predicate, consumer);
     }
   }
 
diff --git a/tools/ahat/src/test/com/android/ahat/AhatTestSuite.java b/tools/ahat/src/test/com/android/ahat/AhatTestSuite.java
index 3aa52b5..abc3cc7 100644
--- a/tools/ahat/src/test/com/android/ahat/AhatTestSuite.java
+++ b/tools/ahat/src/test/com/android/ahat/AhatTestSuite.java
@@ -29,6 +29,7 @@
   InstanceTest.class,
   NativeAllocationTest.class,
   ObjectHandlerTest.class,
+  ObjectsHandlerTest.class,
   OverviewHandlerTest.class,
   PerformanceTest.class,
   ProguardMapTest.class,
diff --git a/tools/ahat/src/test/com/android/ahat/ObjectsHandlerTest.java b/tools/ahat/src/test/com/android/ahat/ObjectsHandlerTest.java
new file mode 100644
index 0000000..927e017
--- /dev/null
+++ b/tools/ahat/src/test/com/android/ahat/ObjectsHandlerTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Site;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ObjectsHandlerTest {
+  @Test
+  public void getObjects() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatSnapshot snapshot = dump.getAhatSnapshot();
+
+    Site root = snapshot.getRootSite();
+
+    // We expect a single instance of DumpedStuff
+    List<AhatInstance> dumped = ObjectsHandler.getObjects(
+        root, "DumpedStuff", /* subclass */ false, /* heapName */ null);
+    assertEquals(1, dumped.size());
+    assertTrue(dumped.get(0).getClassName().equals("DumpedStuff"));
+
+    // We expect no direct instances of SuperDumpedStuff
+    List<AhatInstance> direct = ObjectsHandler.getObjects(
+        root, "SuperDumpedStuff", /* subclass */ false, /* heapName */ null);
+    assertTrue(direct.isEmpty());
+
+    // We expect one subclass instance of SuperDumpedStuff
+    List<AhatInstance> subclass = ObjectsHandler.getObjects(
+        root, "SuperDumpedStuff", /* subclass */ true, /* heapName */ null);
+    assertEquals(1, subclass.size());
+    assertTrue(subclass.get(0).getClassName().equals("DumpedStuff"));
+    assertEquals(dumped.get(0), subclass.get(0));
+  }
+}
diff --git a/tools/ahat/src/test/com/android/ahat/QueryTest.java b/tools/ahat/src/test/com/android/ahat/QueryTest.java
index 5bcf8ea..52cf963 100644
--- a/tools/ahat/src/test/com/android/ahat/QueryTest.java
+++ b/tools/ahat/src/test/com/android/ahat/QueryTest.java
@@ -41,6 +41,7 @@
     assertEquals("/object?answer=43&foo=bar", query.with("answer", "43").toString());
     assertEquals("/object?answer=43&foo=bar", query.with("answer", 43).toString());
     assertEquals("/object?answer=42&bar=finally&foo=bar", query.with("bar", "finally").toString());
+    assertEquals("/object?answer=42", query.with("foo", null).toString());
   }
 
   @Test
@@ -55,6 +56,7 @@
     assertEquals("/object?answer=43&foo=sludge", query.with("answer", "43").toString());
     assertEquals("/object?answer=42&bar=finally&foo=sludge",
         query.with("bar", "finally").toString());
+    assertEquals("/object?answer=42", query.with("foo", null).toString());
   }
 
   @Test
@@ -66,5 +68,6 @@
     assertEquals(2, query.getInt("foo", 2));
     assertEquals("/object?foo=sludge", query.with("foo", "sludge").toString());
     assertEquals("/object?answer=43", query.with("answer", "43").toString());
+    assertEquals("/object?", query.with("foo", null).toString());
   }
 }
diff --git a/tools/veridex/hidden_api_finder.cc b/tools/veridex/hidden_api_finder.cc
index 4eba10e..d81f133 100644
--- a/tools/veridex/hidden_api_finder.cc
+++ b/tools/veridex/hidden_api_finder.cc
@@ -178,7 +178,8 @@
   stats->linking_count = method_locations_.size() + field_locations_.size();
 
   // Dump methods from hidden APIs linked against.
-  for (const std::pair<std::string, std::vector<MethodReference>>& pair : method_locations_) {
+  for (const std::pair<const std::string,
+                       std::vector<MethodReference>>& pair : method_locations_) {
     HiddenApiAccessFlags::ApiList api_list = hidden_api_.GetApiList(pair.first);
     stats->api_counts[api_list]++;
     os << "#" << ++stats->count << ": Linking " << api_list << " " << pair.first << " use(s):";
@@ -190,7 +191,8 @@
   }
 
   // Dump fields from hidden APIs linked against.
-  for (const std::pair<std::string, std::vector<MethodReference>>& pair : field_locations_) {
+  for (const std::pair<const std::string,
+                       std::vector<MethodReference>>& pair : field_locations_) {
     HiddenApiAccessFlags::ApiList api_list = hidden_api_.GetApiList(pair.first);
     stats->api_counts[api_list]++;
     os << "#" << ++stats->count << ": Linking " << api_list << " " << pair.first << " use(s):";