Skip to content

Commit 2b966f7

Browse files
authored
Merge pull request #44 from paypal/issue-43-sanitize-primitive-fields
when --sanitize-byte-char-arrays-only=false is set, retain refs to objects
2 parents e3e4757 + b523723 commit 2b966f7

File tree

8 files changed

+77
-72
lines changed

8 files changed

+77
-72
lines changed

README.md

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,12 @@ Sanitize a heap dump by replacing byte and char array contents
153153
-e, --exclude-string-fields=<excludeStringFields>
154154
String fields to exclude from sanitization. Value in com.example.MyClass#fieldName format
155155
Default: java.lang.Thread#name,java.lang.ThreadGroup#name
156-
-f, --force-string-coder-match=<forceMatchStringCoder>
156+
-f, --force-string-coder-match=<true|false>
157157
Force strings coder values to match sanitizationText.coder value
158158
Default: true
159-
-s, --sanitize-byte-char-arrays-only
159+
-s, --sanitize-byte-char-arrays-only=<true|false>
160160
Sanitize byte/char arrays only
161161
Default: true
162-
-S, --sanitize-arrays-only
163-
Sanitize arrays only
164-
Default: false
165162
-t, --text=<sanitizationText>
166163
Sanitization text to replace with
167164
Default: \0
@@ -172,29 +169,27 @@ Sanitize a heap dump by replacing byte and char array contents
172169
### Explanation of options
173170

174171
* `-a, --tar-input Treat input as tar archive`
175-
* Meant for use with `-` or `stdin` as inputFile when piping heap dump from k8s `kubectl cp` command which produces tar archive.
172+
* Meant for use with `-` or `stdin` as inputFile when piping heap dump from k8s `kubectl cp` command which produces tar
173+
archive.
176174

177175
* `-b, --buffer-size=<bufferSize>`
178-
* Higher buffer size should improve performance when reading and writing large heap dump files at the cost of higher memory usage.
176+
* Higher buffer size should improve performance when reading and writing large heap dump files at the cost of higher
177+
memory usage.
179178

180179
* `-d, --docker-registry=<dockerRegistry>`
181180
* Meant for use with private docker-registry setups.
182181

183182
* `-e, --exclude-string-fields=<excludeStringFields>`
184183
* CSV list of string fields to exclude from sanitization.
185184

186-
* `-f, --force-string-coder-match=<forceMatchStringCoder>`
187-
* Newer Java versions (Java 9+) may encode string instances differently. This setting forces sanitized value in heap dump
188-
to match the coder of the sanitization text provided via `-t` flag. If unset, some sanitized string fields may not be
189-
displayed correctly in analysis tools due to coder mismatch.
190-
191-
* `-s, --sanitize-byte-char-arrays-only`
192-
* When set to true, only byte[] and char[] arrays are sanitized. When false, all fields are sanitized (primitive,
193-
non-primitive, array, non-array). Be warned that some tools like VisualVM may not be able to open such
194-
sanitized heap dumps; Eclipse Memory Analyzer (MAT) is known to work.
185+
* `-f, --force-string-coder-match=<true|false>`
186+
* In Java 9+, string instances may be encoded differently based on content. This setting forces encoding of sanitized
187+
strings in heap dump to match the encoding of the sanitization text provided via `-t` flag. If unset, some sanitized
188+
string fields may not be displayed correctly in analysis tools due to coder mismatch.
195189

196-
* `-S, --sanitize-arrays-only`
197-
* Deprecated. Use `--sanitize-byte-char-arrays-only` instead.
190+
* `-s, --sanitize-byte-char-arrays-only=<true|false>`
191+
* When set to true, only byte and char arrays are sanitized. When false, all primitive array fields and all primitive
192+
non-array fields are sanitized.
198193

199194
* `-t, --text=<sanitizationText>`
200195
* Sanitization text to replace with. Default is null character `\0`.

src/main/java/com/paypal/heapdumptool/sanitizer/HeapDumpSanitizer.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ private void copyHeapDumpClassDump(final Pipe pipe, final long classObjectId) th
246246

247247
final int numStaticFields = pipe.pipeU2();
248248
for (int i = 0; i < numStaticFields; i++) {
249-
pipe.pipeId();
249+
pipe.pipeId(); // field name id
250250
final int entryType = pipe.pipeU1();
251251
pipeStaticField(pipe, entryType);
252252
}
@@ -270,7 +270,7 @@ private void copyHeapDumpClassDump(final Pipe pipe, final long classObjectId) th
270270
}
271271

272272
private boolean shouldTrackClassMetadata() {
273-
return !sanitizeCommand.getExcludeStringFields().isEmpty();
273+
return !sanitizeCommand.getExcludeStringFields().isEmpty() || !sanitizeCommand.isSanitizeByteCharArraysOnly();
274274
}
275275

276276
private boolean isAssignableClassWithExcludeStringField(final long classObjectId) {
@@ -281,7 +281,7 @@ private boolean isAssignableClassWithExcludeStringField(final long classObjectId
281281

282282
private void pipeStaticField(final Pipe pipe, final int entryType) throws IOException {
283283
final int valueSize = BasicType.findValueSize(entryType, pipe.getIdSize());
284-
if (isSanitizeAll()) {
284+
if (shouldSanitizeField(entryType)) {
285285
applySanitization(pipe, valueSize);
286286
} else {
287287
pipe.pipe(valueSize);
@@ -313,12 +313,13 @@ private void copyHeapDumpInstanceDump(final Pipe pipe, final long objectId) thro
313313
copyStringsInstanceFields(pipe, objectId, numBytes);
314314

315315
} else if (isAssignableClassWithExcludeStringField(classObjectId)) {
316-
copyInstanceWithExcludeStringField(pipe, className, numBytes);
316+
copyInstanceAndSanitizeSomeFields(pipe, className, numBytes);
317317

318318
} else {
319-
if (isSanitizeAll()) {
320-
applySanitization(pipe, numBytes);
319+
if (!sanitizeCommand.isSanitizeByteCharArraysOnly()) {
320+
copyInstanceAndSanitizeSomeFields(pipe, className, numBytes);
321321
} else {
322+
// no need to sanitize instance dump. sanitize (byte/char) arrays only, in array dump section
322323
pipe.pipe(numBytes);
323324
}
324325
}
@@ -378,17 +379,21 @@ private Collection<String> getExcludeStringFieldsInClassHierarchy(final String c
378379
.collect(Collectors.toList());
379380
}
380381

381-
private void copyInstanceWithExcludeStringField(final Pipe pipe, final String className, final long numBytes) throws IOException {
382+
private void copyInstanceAndSanitizeSomeFields(final Pipe pipe, final String className, final long numBytes) throws IOException {
382383
final Collection<String> excludeStringFields = getExcludeStringFieldsInClassHierarchy(className);
383384
final ClassObject classObject = classNameToClassObjectsMap.get(className);
384-
final MutableLong numBytesMutable = new MutableLong(numBytes);
385385
Objects.requireNonNull(classObject);
386+
final MutableLong numBytesMutable = new MutableLong(numBytes);
386387
getAllFieldsInClassHierarchy(className).forEach(field -> {
387388
final int fieldSize = field.type.getValueSize(pipe.getIdSize());
388389

389390
if (excludeStringFields.contains(field.name)) {
390391
final long id = Failable.call(pipe::pipeId);
391392
excludeStringObjectIds.add(id);
393+
394+
} else if (shouldSanitizeField(field.type.getU1Code())) {
395+
Failable.run(() -> applySanitization(pipe, fieldSize));
396+
392397
} else {
393398
Failable.run(() -> pipe.pipe(fieldSize));
394399
}
@@ -403,10 +408,13 @@ private String getClassName(final long classObjectId) {
403408
return stringIdToStringMap.getOrDefault(stringId, "");
404409
}
405410

406-
private boolean isSanitizeAll() {
407-
return ENABLE_SANITIZATION &&
408-
!sanitizeCommand.isSanitizeByteCharArraysOnly() &&
409-
!sanitizeCommand.isSanitizeArraysOnly();
411+
private boolean shouldSanitizeField(final int fieldType) {
412+
if (!ENABLE_SANITIZATION) {
413+
return false;
414+
}
415+
416+
final BasicType basicType = BasicType.findByU1Code(fieldType).orElse(BasicType.OBJECT);
417+
return !sanitizeCommand.isSanitizeByteCharArraysOnly() && basicType != BasicType.OBJECT;
410418
}
411419

412420
private void copyHeapDumpObjectArrayDump(final Pipe pipe) throws IOException {

src/main/java/com/paypal/heapdumptool/sanitizer/SanitizeCommandProcessor.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@ public SanitizeCommandProcessor(final SanitizeCommand command, final SanitizeStr
3939

4040
@Override
4141
public void process() throws Exception {
42-
if (command.isSanitizeArraysOnly() && command.isSanitizeByteCharArraysOnly()) {
43-
throw new IllegalArgumentException("sanitizeArraysOnly and sanitizeByteCharArraysOnly cannot be both set to true simultaneously");
44-
}
4542
if (streamFactory.isStdinInput() && !command.getExcludeStringFields().isEmpty()) {
4643
throw new IllegalArgumentException("stdin input and excludeStringFields cannot be both set to true simultaneously");
4744
}

src/main/java/com/paypal/heapdumptool/sanitizer/SanitizeOrCaptureCommandBase.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,6 @@ public abstract class SanitizeOrCaptureCommandBase implements CliCommand {
5454
showDefaultValue = ALWAYS)
5555
private boolean sanitizeByteCharArraysOnly = true;
5656

57-
@Option(names = {"-S", "--sanitize-arrays-only"},
58-
description = "Sanitize arrays only",
59-
arity = "1",
60-
defaultValue = "false",
61-
showDefaultValue = ALWAYS)
62-
private boolean sanitizeArraysOnly;
63-
6457
@Option(names = {"-t", "--text"}, description = "Sanitization text to replace with", defaultValue = "\\0", showDefaultValue = ALWAYS)
6558
private String sanitizationText = "\\0";
6659

@@ -81,7 +74,6 @@ public void copyFrom(final SanitizeOrCaptureCommandBase other) {
8174
this.forceMatchStringCoder = other.forceMatchStringCoder;
8275
this.excludeStringFields = other.excludeStringFields;
8376
this.sanitizationText = other.sanitizationText;
84-
this.sanitizeArraysOnly = other.sanitizeArraysOnly;
8577
this.sanitizeByteCharArraysOnly = other.sanitizeByteCharArraysOnly;
8678
this.tarInput = other.tarInput;
8779
}
@@ -102,14 +94,6 @@ public void setSanitizeByteCharArraysOnly(final boolean sanitizeByteCharArraysOn
10294
this.sanitizeByteCharArraysOnly = sanitizeByteCharArraysOnly;
10395
}
10496

105-
public boolean isSanitizeArraysOnly() {
106-
return sanitizeArraysOnly;
107-
}
108-
109-
public void setSanitizeArraysOnly(final boolean sanitizeArraysOnly) {
110-
this.sanitizeArraysOnly = sanitizeArraysOnly;
111-
}
112-
11397
public boolean isTarInput() {
11498
return tarInput;
11599
}

src/main/java/com/paypal/heapdumptool/sanitizer/SanitizeStreamFactory.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.paypal.heapdumptool.sanitizer;
22

33
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
4-
import org.apache.commons.lang3.StringUtils;
4+
import org.apache.commons.lang3.Strings;
55
import org.apache.commons.lang3.Validate;
66

77
import java.io.BufferedInputStream;
@@ -50,7 +50,7 @@ public OutputStream newOutputStream() throws IOException {
5050
if (command.isZipOutput()) {
5151
final ZipOutputStream zipStream = new ZipOutputStream(output);
5252
final String name = getOutputFileName();
53-
final String entryName = StringUtils.removeEnd(name, ".zip");
53+
final String entryName = Strings.CS.removeEnd(name, ".zip");
5454
zipStream.putNextEntry(new ZipEntry(entryName));
5555
return zipStream;
5656
}
@@ -65,7 +65,7 @@ protected InputStream newInputStream(final Path inputFile) throws IOException {
6565

6666
public boolean isStdinInput() {
6767
final String name = command.getInputFile().getFileName().toString();
68-
return StringUtils.equalsAny(name, "-", "stdin", "0");
68+
return Strings.CS.equalsAny(name, "-", "stdin", "0");
6969
}
7070

7171
private static SanitizeCommand validate(final SanitizeCommand command) {

src/test/java/com/paypal/heapdumptool/ApplicationTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import org.springframework.boot.test.system.OutputCaptureExtension;
1111
import picocli.CommandLine;
1212

13+
import java.util.Objects;
14+
1315
import static com.paypal.heapdumptool.ApplicationTestSupport.runApplication;
1416
import static com.paypal.heapdumptool.ApplicationTestSupport.runApplicationPrivileged;
1517
import static com.paypal.heapdumptool.capture.PrivilegeEscalator.Escalation.REQUIRED_AND_PROMPTED;
@@ -48,6 +50,7 @@ public void testPrivilegeEscalated(final CapturedOutput output) throws Exception
4850
.thenReturn(REQUIRED_AND_PROMPTED);
4951

5052
try (final MockedConstruction<CommandLine> mockedCmd = mockConstruction(CommandLine.class, this::prepare)) {
53+
Objects.requireNonNull(mockedCmd);
5154
final int exitCode = runApplication("capture", "my-container");
5255
assertThat(exitCode).isEqualTo(0);
5356
}

src/test/java/com/paypal/heapdumptool/sanitizer/HeapDumpSanitizerTest.java

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,8 @@ class HeapDumpSanitizerTest {
5050
private static final Logger LOGGER = LoggerFactory.getLogger(HeapDumpSanitizerTest.class);
5151

5252
@TempDir
53-
static Path tempDir2;
5453
static Path tempDir;
5554

56-
static {
57-
try {
58-
tempDir = Files.createTempDirectory("");
59-
} catch (final IOException e) {
60-
throw new RuntimeException(e);
61-
}
62-
}
63-
6455
private final SecretArrays secretArrays = new SecretArrays();
6556

6657
@BeforeEach
@@ -187,21 +178,11 @@ void testSanitizeFieldsOfNonArrayPrimitiveType() throws Exception {
187178
assertThat(countOfSequence(clearHeapDump, nCopiesLongToBytes(cafegirl(), 1)))
188179
.isGreaterThan(500);
189180
}
190-
191-
{
192-
final byte[] clearHeapDump = loadSanitizedHeapDump("--sanitize-byte-char-arrays-only=false", "--sanitize-arrays-only=true");
193-
assertThat(clearHeapDump)
194-
.overridingErrorMessage("sequences do not match") // normal error message would be long and not helpful at all
195-
.containsSequence(nCopiesLongToBytes(deadcow(), 500));
196-
197-
assertThat(countOfSequence(clearHeapDump, nCopiesLongToBytes(cafegirl(), 1)))
198-
.isGreaterThan(500);
199-
}
200181
}
201182

202183
@Test
203184
void testSanitizeArraysOnly() throws Exception {
204-
final byte[] heapDump = loadSanitizedHeapDump("--sanitize-byte-char-arrays-only=false", "--sanitize-arrays-only=true");
185+
final byte[] heapDump = loadSanitizedHeapDump("--sanitize-byte-char-arrays-only=false");
205186
verifyDoesNotContainsSequence(heapDump, secretArrays.getByteArraySequence());
206187
verifyDoesNotContainsSequence(heapDump, secretArrays.getCharArraySequence());
207188
verifyDoesNotContainsSequence(heapDump, secretArrays.getShortArraySequence());
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.paypal.heapdumptool.sanitizer.example;
2+
3+
import java.lang.management.ManagementFactory;
4+
import java.time.Instant;
5+
import java.time.temporal.ChronoUnit;
6+
7+
// for manual testing.
8+
// jcmd 186633 GC.heap_dump /tmp/heap.hprof
9+
// java -jar heap-dump-tool.jar sanitize /tmp/heap.hprof /tmp/sanitize.hprof --sanitize-byte-char-arrays-only=false
10+
// verify 0 primitive values
11+
// verify non-null object refs
12+
public class SimpleClass {
13+
14+
private static final Long simpleStaticLong = System.currentTimeMillis();
15+
private final Long simpleInstanceLong = simpleStaticLong + 1;
16+
private final int simpleInstanceInt = (int) (long) simpleInstanceLong;
17+
18+
private final Inner inner = new Inner();
19+
20+
private static class Inner {
21+
private static final Long staticLong = System.currentTimeMillis();
22+
private final Long instanceLong = staticLong + 1;
23+
private final int instanceInt = (int) (long) instanceLong;
24+
}
25+
26+
public static void main(final String... args) throws InterruptedException {
27+
final SimpleClass simpleClass = new SimpleClass();
28+
final String pid = ManagementFactory.getRuntimeMXBean().getName();
29+
while (!Thread.currentThread().isInterrupted()) {
30+
System.out.println("Running at " + Instant.now().truncatedTo(ChronoUnit.SECONDS) + " " + pid);
31+
32+
if (simpleClass.simpleInstanceLong > 0) {
33+
Thread.sleep(1_000);
34+
}
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)