ahat: Expand test coverage using static heap dumps.
A limitation of the existing ahat test framework was that it was only
easy to test heap dumps generated as part of building the ahat tests.
This change adds static heap dumps from previous versions of Android
to test issues that were specific to previous versions or that are
easier to test with a hard-coded heap dump file.
The test heap dumps are now stored as resources in ahat-test.jar
rather than being passed to ahat-test.jar using system properties.
Fixes a couple of minor bugs encountered when expanding tests.
There are currently two failing tests:
1. Thread roots are not properly marked.
2. Root records not in the default heap are not properly marked.
Test: m ahat-test, from clean build, with new tests added.
Bug: 65356532
Change-Id: I3fa77e2e75d535a1dd68a763c7c45913a9e4074d
diff --git a/tools/ahat/Android.mk b/tools/ahat/Android.mk
index 2ce61cf..1d869af 100644
--- a/tools/ahat/Android.mk
+++ b/tools/ahat/Android.mk
@@ -24,7 +24,6 @@
LOCAL_JAR_MANIFEST := src/manifest.txt
LOCAL_JAVA_RESOURCE_FILES := \
$(LOCAL_PATH)/src/style.css
-
LOCAL_STATIC_JAVA_LIBRARIES := perflib-prebuilt guavalib trove-prebuilt
LOCAL_IS_HOST_MODULE := true
LOCAL_MODULE_TAGS := optional
@@ -43,17 +42,6 @@
LOCAL_SRC_FILES := ahat
include $(BUILD_PREBUILT)
-# --- ahat-tests.jar --------------
-include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-java-files-under, test)
-LOCAL_JAR_MANIFEST := test/manifest.txt
-LOCAL_STATIC_JAVA_LIBRARIES := ahat junit-host
-LOCAL_IS_HOST_MODULE := true
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE := ahat-tests
-include $(BUILD_HOST_JAVA_LIBRARY)
-AHAT_TEST_JAR := $(LOCAL_BUILT_MODULE)
-
# --- ahat-test-dump.jar --------------
include $(CLEAR_VARS)
LOCAL_MODULE := ahat-test-dump
@@ -69,7 +57,13 @@
AHAT_TEST_DUMP_JAR := $(LOCAL_BUILT_MODULE)
AHAT_TEST_DUMP_HPROF := $(intermediates.COMMON)/test-dump.hprof
AHAT_TEST_DUMP_BASE_HPROF := $(intermediates.COMMON)/test-dump-base.hprof
-AHAT_TEST_DUMP_PROGUARD_MAP := $(proguard_dictionary)
+AHAT_TEST_DUMP_PROGUARD_MAP := $(intermediates.COMMON)/test-dump.map
+
+# Generate the proguard map in the desired location by copying it from
+# wherever the build system generates it by default.
+$(AHAT_TEST_DUMP_PROGUARD_MAP): PRIVATE_AHAT_SOURCE_PROGUARD_MAP := $(proguard_dictionary)
+$(AHAT_TEST_DUMP_PROGUARD_MAP): $(proguard_dictionary)
+ cp $(PRIVATE_AHAT_SOURCE_PROGUARD_MAP) $@
# Run ahat-test-dump.jar to generate test-dump.hprof and test-dump-base.hprof
AHAT_TEST_DUMP_DEPENDENCIES := \
@@ -80,23 +74,36 @@
$(AHAT_TEST_DUMP_HPROF): PRIVATE_AHAT_TEST_ART := $(HOST_OUT_EXECUTABLES)/art
$(AHAT_TEST_DUMP_HPROF): PRIVATE_AHAT_TEST_DUMP_JAR := $(AHAT_TEST_DUMP_JAR)
-$(AHAT_TEST_DUMP_HPROF): PRIVATE_AHAT_TEST_DUMP_DEPENDENCIES := $(AHAT_TEST_DUMP_DEPENDENCIES)
$(AHAT_TEST_DUMP_HPROF): $(AHAT_TEST_DUMP_JAR) $(AHAT_TEST_DUMP_DEPENDENCIES)
$(PRIVATE_AHAT_TEST_ART) -cp $(PRIVATE_AHAT_TEST_DUMP_JAR) Main $@
$(AHAT_TEST_DUMP_BASE_HPROF): PRIVATE_AHAT_TEST_ART := $(HOST_OUT_EXECUTABLES)/art
$(AHAT_TEST_DUMP_BASE_HPROF): PRIVATE_AHAT_TEST_DUMP_JAR := $(AHAT_TEST_DUMP_JAR)
-$(AHAT_TEST_DUMP_BASE_HPROF): PRIVATE_AHAT_TEST_DUMP_DEPENDENCIES := $(AHAT_TEST_DUMP_DEPENDENCIES)
$(AHAT_TEST_DUMP_BASE_HPROF): $(AHAT_TEST_DUMP_JAR) $(AHAT_TEST_DUMP_DEPENDENCIES)
$(PRIVATE_AHAT_TEST_ART) -cp $(PRIVATE_AHAT_TEST_DUMP_JAR) Main $@ --base
+# --- ahat-tests.jar --------------
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, test)
+LOCAL_JAR_MANIFEST := test/manifest.txt
+LOCAL_JAVA_RESOURCE_FILES := \
+ $(AHAT_TEST_DUMP_HPROF) \
+ $(AHAT_TEST_DUMP_BASE_HPROF) \
+ $(AHAT_TEST_DUMP_PROGUARD_MAP) \
+ $(LOCAL_PATH)/test-dump/L.hprof \
+ $(LOCAL_PATH)/test-dump/O.hprof \
+ $(LOCAL_PATH)/test-dump/RI.hprof
+LOCAL_STATIC_JAVA_LIBRARIES := ahat junit-host
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := ahat-tests
+include $(BUILD_HOST_JAVA_LIBRARY)
+AHAT_TEST_JAR := $(LOCAL_BUILT_MODULE)
+
.PHONY: ahat-test
-ahat-test: PRIVATE_AHAT_TEST_DUMP_HPROF := $(AHAT_TEST_DUMP_HPROF)
-ahat-test: PRIVATE_AHAT_TEST_DUMP_BASE_HPROF := $(AHAT_TEST_DUMP_BASE_HPROF)
ahat-test: PRIVATE_AHAT_TEST_JAR := $(AHAT_TEST_JAR)
-ahat-test: PRIVATE_AHAT_PROGUARD_MAP := $(AHAT_TEST_DUMP_PROGUARD_MAP)
-ahat-test: $(AHAT_TEST_JAR) $(AHAT_TEST_DUMP_HPROF) $(AHAT_TEST_DUMP_BASE_HPROF)
- java -enableassertions -Dahat.test.dump.hprof=$(PRIVATE_AHAT_TEST_DUMP_HPROF) -Dahat.test.dump.base.hprof=$(PRIVATE_AHAT_TEST_DUMP_BASE_HPROF) -Dahat.test.dump.map=$(PRIVATE_AHAT_PROGUARD_MAP) -jar $(PRIVATE_AHAT_TEST_JAR)
+ahat-test: $(AHAT_TEST_JAR)
+ java -enableassertions -jar $(PRIVATE_AHAT_TEST_JAR)
# Clean up local variables.
AHAT_TEST_JAR :=
diff --git a/tools/ahat/src/heapdump/AhatClassInstance.java b/tools/ahat/src/heapdump/AhatClassInstance.java
index f7d8431..a2f61ca 100644
--- a/tools/ahat/src/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/heapdump/AhatClassInstance.java
@@ -123,7 +123,7 @@
}
Value value = getField("value");
- if (!value.isAhatInstance()) {
+ if (value == null || !value.isAhatInstance()) {
return null;
}
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
index 8b4c679..d7f94dc 100644
--- a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
@@ -29,6 +29,10 @@
baseline.setBaseline(this);
}
+ @Override public Size getSize() {
+ return Size.ZERO;
+ }
+
@Override public Size getRetainedSize(AhatHeap heap) {
return Size.ZERO;
}
diff --git a/tools/ahat/test-dump/L.hprof b/tools/ahat/test-dump/L.hprof
new file mode 100644
index 0000000..cf82557
--- /dev/null
+++ b/tools/ahat/test-dump/L.hprof
Binary files differ
diff --git a/tools/ahat/test-dump/O.hprof b/tools/ahat/test-dump/O.hprof
new file mode 100644
index 0000000..d474c6c
--- /dev/null
+++ b/tools/ahat/test-dump/O.hprof
Binary files differ
diff --git a/tools/ahat/test-dump/README.txt b/tools/ahat/test-dump/README.txt
new file mode 100644
index 0000000..344271c
--- /dev/null
+++ b/tools/ahat/test-dump/README.txt
@@ -0,0 +1,5 @@
+
+Main.java - A program used to generate a heap dump used for tests.
+L.hprof - A version of the test dump generated on Android L.
+O.hprof - A version of the test dump generated on Android O.
+RI.hprof - A version of the test dump generated on the reference implementation.
diff --git a/tools/ahat/test-dump/RI.hprof b/tools/ahat/test-dump/RI.hprof
new file mode 100644
index 0000000..9482542
--- /dev/null
+++ b/tools/ahat/test-dump/RI.hprof
Binary files differ
diff --git a/tools/ahat/test/DiffTest.java b/tools/ahat/test/DiffTest.java
index d0349fd..cfd0236 100644
--- a/tools/ahat/test/DiffTest.java
+++ b/tools/ahat/test/DiffTest.java
@@ -93,6 +93,13 @@
}
@Test
+ public void diffClassRemoved() throws IOException {
+ TestDump dump = TestDump.getTestDump("O.hprof", "L.hprof", null);
+ AhatHandler handler = new ObjectsHandler(dump.getAhatSnapshot());
+ TestHandler.testNoCrash(handler, "http://localhost:7100/objects?class=java.lang.Class");
+ }
+
+ @Test
public void nullClassObj() throws IOException {
// Set up a heap dump that has a null classObj.
// The heap dump is derived from the InstanceTest.asStringEmbedded test.
diff --git a/tools/ahat/test/InstanceTest.java b/tools/ahat/test/InstanceTest.java
index 63055db..49a21e2 100644
--- a/tools/ahat/test/InstanceTest.java
+++ b/tools/ahat/test/InstanceTest.java
@@ -23,23 +23,7 @@
import com.android.ahat.heapdump.PathElement;
import com.android.ahat.heapdump.Size;
import com.android.ahat.heapdump.Value;
-import com.android.tools.perflib.heap.hprof.HprofClassDump;
-import com.android.tools.perflib.heap.hprof.HprofConstant;
-import com.android.tools.perflib.heap.hprof.HprofDumpRecord;
-import com.android.tools.perflib.heap.hprof.HprofHeapDump;
-import com.android.tools.perflib.heap.hprof.HprofInstanceDump;
-import com.android.tools.perflib.heap.hprof.HprofInstanceField;
-import com.android.tools.perflib.heap.hprof.HprofLoadClass;
-import com.android.tools.perflib.heap.hprof.HprofPrimitiveArrayDump;
-import com.android.tools.perflib.heap.hprof.HprofRecord;
-import com.android.tools.perflib.heap.hprof.HprofRootDebugger;
-import com.android.tools.perflib.heap.hprof.HprofStaticField;
-import com.android.tools.perflib.heap.hprof.HprofStringBuilder;
-import com.android.tools.perflib.heap.hprof.HprofType;
-import com.google.common.io.ByteArrayDataOutput;
-import com.google.common.io.ByteStreams;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
@@ -395,44 +379,63 @@
@Test
public void asStringEmbedded() throws IOException {
- // Set up a heap dump with an instance of java.lang.String of
- // "hello" with instance id 0x42 that is backed by a char array that is
- // bigger. This is how ART used to represent strings, and we should still
- // support it in case the heap dump is from a previous platform version.
- HprofStringBuilder strings = new HprofStringBuilder(0);
- List<HprofRecord> records = new ArrayList<HprofRecord>();
- List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>();
+ // On Android L, image strings were backed by a single big char array.
+ // Verify we show just the relative part of the string, not the entire
+ // char array.
+ TestDump dump = TestDump.getTestDump("L.hprof", null, null);
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
- final int stringClassObjectId = 1;
- records.add(new HprofLoadClass(0, 0, stringClassObjectId, 0, strings.get("java.lang.String")));
- dump.add(new HprofClassDump(stringClassObjectId, 0, 0, 0, 0, 0, 0, 0, 0,
- new HprofConstant[0], new HprofStaticField[0],
- new HprofInstanceField[]{
- new HprofInstanceField(strings.get("count"), HprofType.TYPE_INT),
- new HprofInstanceField(strings.get("hashCode"), HprofType.TYPE_INT),
- new HprofInstanceField(strings.get("offset"), HprofType.TYPE_INT),
- new HprofInstanceField(strings.get("value"), HprofType.TYPE_OBJECT)}));
+ // java.lang.String@0x6fe17050 is an image string "char" backed by a
+ // shared char array.
+ AhatInstance str = snapshot.findInstance(0x6fe17050);
+ assertEquals("char", str.asString());
+ }
- dump.add(new HprofPrimitiveArrayDump(0x41, 0, HprofType.TYPE_CHAR,
- new long[]{'n', 'o', 't', ' ', 'h', 'e', 'l', 'l', 'o', 'o', 'p'}));
+ @Test
+ public void nonDefaultHeapRoot() throws IOException {
+ TestDump dump = TestDump.getTestDump("O.hprof", null, null);
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
- ByteArrayDataOutput values = ByteStreams.newDataOutput();
- values.writeInt(5); // count
- values.writeInt(0); // hashCode
- values.writeInt(4); // offset
- values.writeInt(0x41); // value
- dump.add(new HprofInstanceDump(0x42, 0, stringClassObjectId, values.toByteArray()));
- dump.add(new HprofRootDebugger(stringClassObjectId));
- dump.add(new HprofRootDebugger(0x42));
+ // java.util.HashMap@6004fdb8 is marked as a VM INTERNAL root.
+ // Previously we had a bug where roots not on the default heap were not
+ // properly treated as roots (b/65356532).
+ AhatInstance map = snapshot.findInstance(0x6004fdb8);
+ assertEquals("java.util.HashMap", map.getClassName());
+ assertTrue(map.isRoot());
+ }
- records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0])));
- AhatSnapshot snapshot = SnapshotBuilder.makeSnapshot(strings, records);
- AhatInstance chars = snapshot.findInstance(0x41);
- assertNotNull(chars);
- assertEquals("not helloop", chars.asString());
+ @Test
+ public void threadRoot() throws IOException {
+ TestDump dump = TestDump.getTestDump("O.hprof", null, null);
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
- AhatInstance stringInstance = snapshot.findInstance(0x42);
- assertNotNull(stringInstance);
- assertEquals("hello", stringInstance.asString());
+ // java.lang.Thread@12c03470 is marked as a thread root.
+ // Previously we had a bug where thread roots were not properly treated as
+ // roots (b/65356532).
+ AhatInstance thread = snapshot.findInstance(0x12c03470);
+ assertEquals("java.lang.Thread", thread.getClassName());
+ assertTrue(thread.isRoot());
+ }
+
+ @Test
+ public void classOfClass() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("anObject");
+ AhatClassObj cls = obj.getClassObj();
+ AhatClassObj clscls = cls.getClassObj();
+ assertNotNull(clscls);
+ assertEquals("java.lang.Class", clscls.getName());
+ }
+
+ @Test
+ public void nullValueString() throws IOException {
+ TestDump dump = TestDump.getTestDump("RI.hprof", null, null);
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
+
+ // java.lang.String@500001a8 has a null 'value' field, which should not
+ // cause ahat to crash.
+ AhatInstance str = snapshot.findInstance(0x500001a8);
+ assertEquals("java.lang.String", str.getClassName());
+ assertNull(str.asString());
}
}
diff --git a/tools/ahat/test/TestDump.java b/tools/ahat/test/TestDump.java
index db9b256..d1afe43 100644
--- a/tools/ahat/test/TestDump.java
+++ b/tools/ahat/test/TestDump.java
@@ -23,73 +23,114 @@
import com.android.ahat.heapdump.FieldValue;
import com.android.ahat.heapdump.Site;
import com.android.ahat.heapdump.Value;
+import com.android.tools.perflib.captures.DataBuffer;
import com.android.tools.perflib.heap.ProguardMap;
-import java.io.File;
+import com.android.tools.perflib.heap.io.InMemoryBuffer;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
/**
- * The TestDump class is used to get an AhatSnapshot for the test-dump
- * program.
+ * The TestDump class is used to get the current and baseline AhatSnapshots
+ * for heap dumps generated by the test-dump program that are stored as
+ * resources in this jar file.
*/
public class TestDump {
- // It can take on the order of a second to parse and process the test-dump
- // hprof. To avoid repeating this overhead for each test case, we cache the
- // loaded instance of TestDump and reuse it when possible. In theory the
- // test cases should not be able to modify the cached snapshot in a way that
- // is visible to other test cases.
- private static TestDump mCachedTestDump = null;
+ // It can take on the order of a second to parse and process test dumps.
+ // To avoid repeating this overhead for each test case, we provide a way to
+ // cache loaded instance of TestDump and reuse it when possible. In theory
+ // the test cases should not be able to modify the cached snapshot in a way
+ // that is visible to other test cases.
+ private static List<TestDump> mCachedTestDumps = new ArrayList<TestDump>();
+
+ // The name of the resources this test dump is loaded from.
+ private String mHprofResource;
+ private String mHprofBaseResource;
+ private String mMapResource;
// If the test dump fails to load the first time, it will likely fail every
// other test we try. Rather than having to wait a potentially very long
// time for test dump loading to fail over and over again, record when it
// fails and don't try to load it again.
- private static boolean mTestDumpFailed = false;
+ private boolean mTestDumpFailed = true;
+ // The loaded heap dumps.
private AhatSnapshot mSnapshot;
private AhatSnapshot mBaseline;
+
+ // Cached reference to the 'Main' class object in the snapshot and baseline
+ // heap dumps.
private AhatClassObj mMain;
private AhatClassObj mBaselineMain;
/**
- * Load the test-dump.hprof and test-dump-base.hprof files.
- * The location of the files are read from the system properties
- * "ahat.test.dump.hprof" and "ahat.test.dump.base.hprof", which is expected
- * to be set on the command line.
- * The location of the proguard map for both hprof files is read from the
- * system property "ahat.test.dump.map". For example:
- * java -Dahat.test.dump.hprof=test-dump.hprof \
- * -Dahat.test.dump.base.hprof=test-dump-base.hprof \
- * -Dahat.test.dump.map=proguard.map \
- * -jar ahat-tests.jar
+ * Read the named resource into a DataBuffer.
+ */
+ private static DataBuffer dataBufferFromResource(String name) throws IOException {
+ ClassLoader loader = TestDump.class.getClassLoader();
+ InputStream is = loader.getResourceAsStream(name);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buf = new byte[4096];
+ int read;
+ while ((read = is.read(buf)) != -1) {
+ baos.write(buf, 0, read);
+ }
+ return new InMemoryBuffer(baos.toByteArray());
+ }
+
+ /**
+ * Create a TestDump instance.
+ * The load() method should be called to load and process the heap dumps.
+ * The files are specified as names of resources compiled into the jar file.
+ * The baseline resouce may be null to indicate that no diffing should be
+ * performed.
+ * The map resource may be null to indicate no proguard map will be used.
*
+ */
+ private TestDump(String hprofResource, String hprofBaseResource, String mapResource) {
+ mHprofResource = hprofResource;
+ mHprofBaseResource = hprofBaseResource;
+ mMapResource = mapResource;
+ }
+
+ /**
+ * Load the heap dumps for this TestDump.
* An IOException is thrown if there is a failure reading the hprof files or
* the proguard map.
*/
- private TestDump() throws IOException {
- // TODO: Make use of the baseline hprof for tests.
- String hprof = System.getProperty("ahat.test.dump.hprof");
- String hprofBase = System.getProperty("ahat.test.dump.base.hprof");
-
- String mapfile = System.getProperty("ahat.test.dump.map");
+ private void load() throws IOException {
ProguardMap map = new ProguardMap();
- try {
- map.readFromFile(new File(mapfile));
- } catch (ParseException e) {
- throw new IOException("Unable to load proguard map", e);
+ if (mMapResource != null) {
+ try {
+ ClassLoader loader = TestDump.class.getClassLoader();
+ InputStream is = loader.getResourceAsStream(mMapResource);
+ map.readFromReader(new InputStreamReader(is));
+ } catch (ParseException e) {
+ throw new IOException("Unable to load proguard map", e);
+ }
}
- mSnapshot = AhatSnapshot.fromHprof(new File(hprof), map);
- mBaseline = AhatSnapshot.fromHprof(new File(hprofBase), map);
- Diff.snapshots(mSnapshot, mBaseline);
-
+ DataBuffer hprof = dataBufferFromResource(mHprofResource);
+ mSnapshot = AhatSnapshot.fromDataBuffer(hprof, map);
mMain = findClass(mSnapshot, "Main");
assert(mMain != null);
- mBaselineMain = findClass(mBaseline, "Main");
- assert(mBaselineMain != null);
+ if (mHprofBaseResource != null) {
+ DataBuffer hprofBase = dataBufferFromResource(mHprofBaseResource);
+ mBaseline = AhatSnapshot.fromDataBuffer(hprofBase, map);
+ mBaselineMain = findClass(mBaseline, "Main");
+ assert(mBaselineMain != null);
+
+ Diff.snapshots(mSnapshot, mBaseline);
+ }
+
+ mTestDumpFailed = false;
}
/**
@@ -182,22 +223,42 @@
}
/**
- * Get the test dump.
+ * Get the default (cached) test dump.
* An IOException is thrown if there is an error reading the test dump hprof
* file.
* To improve performance, this returns a cached instance of the TestDump
* when possible.
*/
public static synchronized TestDump getTestDump() throws IOException {
- if (mTestDumpFailed) {
- throw new RuntimeException("Test dump failed before, assuming it will again");
+ return getTestDump("test-dump.hprof", "test-dump-base.hprof", "test-dump.map");
+ }
+
+ /**
+ * Get a (cached) test dump.
+ * @param hprof - The string resouce name of the hprof file.
+ * @param base - The string resouce name of the baseline hprof, may be null.
+ * @param map - The string resouce name of the proguard map, may be null.
+ * An IOException is thrown if there is an error reading the test dump hprof
+ * file.
+ * To improve performance, this returns a cached instance of the TestDump
+ * when possible.
+ */
+ public static synchronized TestDump getTestDump(String hprof, String base, String map)
+ throws IOException {
+ for (TestDump loaded : mCachedTestDumps) {
+ if (Objects.equals(loaded.mHprofResource, hprof)
+ && Objects.equals(loaded.mHprofBaseResource, base)
+ && Objects.equals(loaded.mMapResource, map)) {
+ if (loaded.mTestDumpFailed) {
+ throw new IOException("Test dump failed before, assuming it will again");
+ }
+ return loaded;
+ }
}
- if (mCachedTestDump == null) {
- mTestDumpFailed = true;
- mCachedTestDump = new TestDump();
- mTestDumpFailed = false;
- }
- return mCachedTestDump;
+ TestDump dump = new TestDump(hprof, base, map);
+ mCachedTestDumps.add(dump);
+ dump.load();
+ return dump;
}
}