mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
8261160: Add a deserialization JFR event
Co-authored-by: Sean Coffey <coffeys@openjdk.org> Co-authored-by: Chris Hegarty <chegar@openjdk.org> Reviewed-by: coffeys, rriggs, dfuchs, egahlin
This commit is contained in:
parent
a305743cfa
commit
3dc6f52a89
12 changed files with 615 additions and 22 deletions
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -48,6 +48,7 @@ import java.util.concurrent.ConcurrentMap;
|
||||||
import static java.io.ObjectStreamClass.processQueue;
|
import static java.io.ObjectStreamClass.processQueue;
|
||||||
|
|
||||||
import jdk.internal.access.SharedSecrets;
|
import jdk.internal.access.SharedSecrets;
|
||||||
|
import jdk.internal.event.DeserializationEvent;
|
||||||
import jdk.internal.misc.Unsafe;
|
import jdk.internal.misc.Unsafe;
|
||||||
import sun.reflect.misc.ReflectUtil;
|
import sun.reflect.misc.ReflectUtil;
|
||||||
import sun.security.action.GetBooleanAction;
|
import sun.security.action.GetBooleanAction;
|
||||||
|
@ -1323,9 +1324,12 @@ public class ObjectInputStream
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoke the serialization filter if non-null.
|
* Invokes the serialization filter if non-null.
|
||||||
|
*
|
||||||
* If the filter rejects or an exception is thrown, throws InvalidClassException.
|
* If the filter rejects or an exception is thrown, throws InvalidClassException.
|
||||||
*
|
*
|
||||||
|
* Logs and/or commits a {@code DeserializationEvent}, if configured.
|
||||||
|
*
|
||||||
* @param clazz the class; may be null
|
* @param clazz the class; may be null
|
||||||
* @param arrayLength the array length requested; use {@code -1} if not creating an array
|
* @param arrayLength the array length requested; use {@code -1} if not creating an array
|
||||||
* @throws InvalidClassException if it rejected by the filter or
|
* @throws InvalidClassException if it rejected by the filter or
|
||||||
|
@ -1333,11 +1337,12 @@ public class ObjectInputStream
|
||||||
*/
|
*/
|
||||||
private void filterCheck(Class<?> clazz, int arrayLength)
|
private void filterCheck(Class<?> clazz, int arrayLength)
|
||||||
throws InvalidClassException {
|
throws InvalidClassException {
|
||||||
|
// Info about the stream is not available if overridden by subclass, return 0
|
||||||
|
long bytesRead = (bin == null) ? 0 : bin.getBytesRead();
|
||||||
|
RuntimeException ex = null;
|
||||||
|
ObjectInputFilter.Status status = null;
|
||||||
|
|
||||||
if (serialFilter != null) {
|
if (serialFilter != null) {
|
||||||
RuntimeException ex = null;
|
|
||||||
ObjectInputFilter.Status status;
|
|
||||||
// Info about the stream is not available if overridden by subclass, return 0
|
|
||||||
long bytesRead = (bin == null) ? 0 : bin.getBytesRead();
|
|
||||||
try {
|
try {
|
||||||
status = serialFilter.checkInput(new FilterValues(clazz, arrayLength,
|
status = serialFilter.checkInput(new FilterValues(clazz, arrayLength,
|
||||||
totalObjectRefs, depth, bytesRead));
|
totalObjectRefs, depth, bytesRead));
|
||||||
|
@ -1355,12 +1360,24 @@ public class ObjectInputStream
|
||||||
status, clazz, arrayLength, totalObjectRefs, depth, bytesRead,
|
status, clazz, arrayLength, totalObjectRefs, depth, bytesRead,
|
||||||
Objects.toString(ex, "n/a"));
|
Objects.toString(ex, "n/a"));
|
||||||
}
|
}
|
||||||
if (status == null ||
|
}
|
||||||
status == ObjectInputFilter.Status.REJECTED) {
|
DeserializationEvent event = new DeserializationEvent();
|
||||||
InvalidClassException ice = new InvalidClassException("filter status: " + status);
|
if (event.shouldCommit()) {
|
||||||
ice.initCause(ex);
|
event.filterConfigured = serialFilter != null;
|
||||||
throw ice;
|
event.filterStatus = status != null ? status.name() : null;
|
||||||
}
|
event.type = clazz;
|
||||||
|
event.arrayLength = arrayLength;
|
||||||
|
event.objectReferences = totalObjectRefs;
|
||||||
|
event.depth = depth;
|
||||||
|
event.bytesRead = bytesRead;
|
||||||
|
event.exceptionType = ex != null ? ex.getClass() : null;
|
||||||
|
event.exceptionMessage = ex != null ? ex.getMessage() : null;
|
||||||
|
event.commit();
|
||||||
|
}
|
||||||
|
if (serialFilter != null && (status == null || status == ObjectInputFilter.Status.REJECTED)) {
|
||||||
|
InvalidClassException ice = new InvalidClassException("filter status: " + status);
|
||||||
|
ice.initCause(ex);
|
||||||
|
throw ice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdk.internal.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event details relating to deserialization.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public final class DeserializationEvent extends Event {
|
||||||
|
public boolean filterConfigured;
|
||||||
|
public String filterStatus;
|
||||||
|
public Class<?> type;
|
||||||
|
public int arrayLength;
|
||||||
|
public long objectReferences;
|
||||||
|
public long depth;
|
||||||
|
public long bytesRead;
|
||||||
|
public Class<?> exceptionType;
|
||||||
|
public String exceptionMessage;
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdk.jfr.events;
|
||||||
|
|
||||||
|
import jdk.jfr.Category;
|
||||||
|
import jdk.jfr.Description;
|
||||||
|
import jdk.jfr.Label;
|
||||||
|
import jdk.jfr.Name;
|
||||||
|
import jdk.jfr.internal.MirrorEvent;
|
||||||
|
|
||||||
|
@Category({"Java Development Kit", "Serialization"})
|
||||||
|
@Label("Deserialization")
|
||||||
|
@Name("jdk.Deserialization")
|
||||||
|
@Description("Results of deserialiation and ObjectInputFilter checks")
|
||||||
|
@MirrorEvent(className = "jdk.internal.event.DeserializationEvent")
|
||||||
|
public final class DeserializationEvent extends AbstractJDKEvent {
|
||||||
|
|
||||||
|
@Label("Filter Configured")
|
||||||
|
public boolean filterConfigured;
|
||||||
|
|
||||||
|
@Label("Filter Status")
|
||||||
|
public String filterStatus;
|
||||||
|
|
||||||
|
@Label ("Type")
|
||||||
|
public Class<?> type;
|
||||||
|
|
||||||
|
@Label ("Array Length")
|
||||||
|
public int arrayLength;
|
||||||
|
|
||||||
|
@Label ("Object References")
|
||||||
|
public long objectReferences;
|
||||||
|
|
||||||
|
@Label ("Depth")
|
||||||
|
public long depth;
|
||||||
|
|
||||||
|
@Label ("Bytes Read")
|
||||||
|
public long bytesRead;
|
||||||
|
|
||||||
|
@Label ("Exception Type")
|
||||||
|
public Class<?> exceptionType;
|
||||||
|
|
||||||
|
@Label ("Exception Message")
|
||||||
|
public String exceptionMessage;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -38,6 +38,7 @@ import jdk.jfr.events.ExceptionThrownEvent;
|
||||||
import jdk.jfr.events.FileForceEvent;
|
import jdk.jfr.events.FileForceEvent;
|
||||||
import jdk.jfr.events.FileReadEvent;
|
import jdk.jfr.events.FileReadEvent;
|
||||||
import jdk.jfr.events.FileWriteEvent;
|
import jdk.jfr.events.FileWriteEvent;
|
||||||
|
import jdk.jfr.events.DeserializationEvent;
|
||||||
import jdk.jfr.events.ProcessStartEvent;
|
import jdk.jfr.events.ProcessStartEvent;
|
||||||
import jdk.jfr.events.SecurityPropertyModificationEvent;
|
import jdk.jfr.events.SecurityPropertyModificationEvent;
|
||||||
import jdk.jfr.events.SocketReadEvent;
|
import jdk.jfr.events.SocketReadEvent;
|
||||||
|
@ -55,11 +56,12 @@ import jdk.jfr.internal.SecuritySupport;
|
||||||
public final class JDKEvents {
|
public final class JDKEvents {
|
||||||
|
|
||||||
private static final Class<?>[] mirrorEventClasses = {
|
private static final Class<?>[] mirrorEventClasses = {
|
||||||
|
DeserializationEvent.class,
|
||||||
|
ProcessStartEvent.class,
|
||||||
SecurityPropertyModificationEvent.class,
|
SecurityPropertyModificationEvent.class,
|
||||||
TLSHandshakeEvent.class,
|
TLSHandshakeEvent.class,
|
||||||
X509CertificateEvent.class,
|
X509CertificateEvent.class,
|
||||||
X509ValidationEvent.class,
|
X509ValidationEvent.class
|
||||||
ProcessStartEvent.class
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final Class<?>[] eventClasses = {
|
private static final Class<?>[] eventClasses = {
|
||||||
|
@ -73,11 +75,13 @@ public final class JDKEvents {
|
||||||
ErrorThrownEvent.class,
|
ErrorThrownEvent.class,
|
||||||
ActiveSettingEvent.class,
|
ActiveSettingEvent.class,
|
||||||
ActiveRecordingEvent.class,
|
ActiveRecordingEvent.class,
|
||||||
|
jdk.internal.event.DeserializationEvent.class,
|
||||||
|
jdk.internal.event.ProcessStartEvent.class,
|
||||||
jdk.internal.event.SecurityPropertyModificationEvent.class,
|
jdk.internal.event.SecurityPropertyModificationEvent.class,
|
||||||
jdk.internal.event.TLSHandshakeEvent.class,
|
jdk.internal.event.TLSHandshakeEvent.class,
|
||||||
jdk.internal.event.X509CertificateEvent.class,
|
jdk.internal.event.X509CertificateEvent.class,
|
||||||
jdk.internal.event.X509ValidationEvent.class,
|
jdk.internal.event.X509ValidationEvent.class,
|
||||||
jdk.internal.event.ProcessStartEvent.class,
|
|
||||||
DirectBufferStatisticsEvent.class
|
DirectBufferStatisticsEvent.class
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -665,6 +665,11 @@
|
||||||
<setting name="threshold" control="socket-io-threshold">20 ms</setting>
|
<setting name="threshold" control="socket-io-threshold">20 ms</setting>
|
||||||
</event>
|
</event>
|
||||||
|
|
||||||
|
<event name="jdk.Deserialization">
|
||||||
|
<setting name="enabled">false</setting>
|
||||||
|
<setting name="stackTrace">true</setting>
|
||||||
|
</event>
|
||||||
|
|
||||||
<event name="jdk.SecurityPropertyModification">
|
<event name="jdk.SecurityPropertyModification">
|
||||||
<setting name="enabled">false</setting>
|
<setting name="enabled">false</setting>
|
||||||
<setting name="stackTrace">true</setting>
|
<setting name="stackTrace">true</setting>
|
||||||
|
|
|
@ -665,6 +665,11 @@
|
||||||
<setting name="threshold" control="socket-io-threshold">10 ms</setting>
|
<setting name="threshold" control="socket-io-threshold">10 ms</setting>
|
||||||
</event>
|
</event>
|
||||||
|
|
||||||
|
<event name="jdk.Deserialization">
|
||||||
|
<setting name="enabled">false</setting>
|
||||||
|
<setting name="stackTrace">true</setting>
|
||||||
|
</event>
|
||||||
|
|
||||||
<event name="jdk.SecurityPropertyModification">
|
<event name="jdk.SecurityPropertyModification">
|
||||||
<setting name="enabled">false</setting>
|
<setting name="enabled">false</setting>
|
||||||
<setting name="stackTrace">true</setting>
|
<setting name="stackTrace">true</setting>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -53,6 +53,18 @@ import org.testng.annotations.DataProvider;
|
||||||
*
|
*
|
||||||
* @summary Test Global Filters
|
* @summary Test Global Filters
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* @test
|
||||||
|
* @bug 8261160
|
||||||
|
* @summary Add a deserialization JFR event
|
||||||
|
* @build GlobalFilterTest SerialFilterTest
|
||||||
|
* @requires vm.hasJFR
|
||||||
|
* @run testng/othervm/policy=security.policy
|
||||||
|
* -XX:StartFlightRecording=name=DeserializationEvent,dumponexit=true
|
||||||
|
* -Djava.security.properties=${test.src}/java.security-extra1
|
||||||
|
* -Djava.security.debug=properties GlobalFilterTest
|
||||||
|
*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public class GlobalFilterTest {
|
public class GlobalFilterTest {
|
||||||
private static final String serialPropName = "jdk.serialFilter";
|
private static final String serialPropName = "jdk.serialFilter";
|
||||||
|
|
437
test/jdk/jdk/jfr/event/io/TestDeserializationEvent.java
Normal file
437
test/jdk/jdk/jfr/event/io/TestDeserializationEvent.java
Normal file
|
@ -0,0 +1,437 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdk.jfr.event.io;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InvalidClassException;
|
||||||
|
import java.io.ObjectInputFilter.Status;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import jdk.jfr.Recording;
|
||||||
|
import jdk.jfr.consumer.RecordedClass;
|
||||||
|
import jdk.jfr.consumer.RecordedEvent;
|
||||||
|
import jdk.test.lib.jfr.EventNames;
|
||||||
|
import jdk.test.lib.jfr.Events;
|
||||||
|
import jdk.test.lib.serial.SerialObjectBuilder;
|
||||||
|
import org.testng.annotations.DataProvider;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
import static java.lang.System.out;
|
||||||
|
import static org.testng.Assert.*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8261160
|
||||||
|
* @summary Add a deserialization JFR event
|
||||||
|
* @key jfr
|
||||||
|
* @requires vm.hasJFR
|
||||||
|
* @library /test/lib
|
||||||
|
* @run testng/othervm jdk.jfr.event.io.TestDeserializationEvent
|
||||||
|
*/
|
||||||
|
public class TestDeserializationEvent {
|
||||||
|
|
||||||
|
public record R() implements Serializable { }
|
||||||
|
|
||||||
|
@DataProvider(name = "scenarios")
|
||||||
|
public Object[][] scenarios() throws Exception {
|
||||||
|
byte[] ba1 = serialize(new R());
|
||||||
|
byte[] ba2 = serialize(new int[] { 56, 67, 58, 59, 60 });
|
||||||
|
byte[] ba3 = serialize(new R[] { new R(), new R() });
|
||||||
|
byte[] ba4 = serialize(new char[][] { new char[] {'a', 'b'}, new char[] {'c'} });
|
||||||
|
|
||||||
|
// data provider columns- 1:id, 2:deserialize-operation, 3:expected-event-checkers
|
||||||
|
return new Object[][] {
|
||||||
|
{ 1, // single stream object, R
|
||||||
|
(Runnable)() -> deserialize(ba1),
|
||||||
|
List.of(
|
||||||
|
Set.of(
|
||||||
|
assertFilterStatus(null),
|
||||||
|
assertType(R.class),
|
||||||
|
assertArrayLength(-1),
|
||||||
|
assertObjectReferences(1),
|
||||||
|
assertDepth(1),
|
||||||
|
assertHasBytesRead(),
|
||||||
|
assertExceptionType(null),
|
||||||
|
assertExceptionMessage(null))) },
|
||||||
|
{ 2, // primitive int array
|
||||||
|
(Runnable)() -> deserialize(ba2),
|
||||||
|
List.of(
|
||||||
|
Set.of( // TC_CLASS, for array class int[]
|
||||||
|
assertType(int[].class),
|
||||||
|
assertArrayLength(-1)),
|
||||||
|
Set.of( // TC_ARRAY, actual array
|
||||||
|
assertType(int[].class),
|
||||||
|
assertArrayLength(5))) },
|
||||||
|
{ 3, // reference array, R
|
||||||
|
(Runnable)() -> deserialize(ba3),
|
||||||
|
List.of(
|
||||||
|
Set.of( // TC_CLASS, for array class R[]
|
||||||
|
assertType(R[].class),
|
||||||
|
assertArrayLength(-1)),
|
||||||
|
Set.of( // TC_ARRAY, actual array
|
||||||
|
assertType(R[].class),
|
||||||
|
assertArrayLength(2)),
|
||||||
|
Set.of( // TC_CLASS, for R
|
||||||
|
assertType(R.class),
|
||||||
|
assertArrayLength(-1)),
|
||||||
|
Set.of( // TC_REFERENCE, for TC_CLASS relating second stream obj
|
||||||
|
assertType(null),
|
||||||
|
assertArrayLength(-1))) },
|
||||||
|
{ 4, // multi-dimensional prim char array
|
||||||
|
(Runnable)() -> deserialize(ba4),
|
||||||
|
List.of(
|
||||||
|
Set.of( // TC_CLASS, for array class char[][]
|
||||||
|
assertType(char[][].class),
|
||||||
|
assertArrayLength(-1),
|
||||||
|
assertDepth(1)),
|
||||||
|
Set.of( // TC_ARRAY, actual char[][] array
|
||||||
|
assertType(char[][].class),
|
||||||
|
assertArrayLength(2),
|
||||||
|
assertDepth(1)),
|
||||||
|
Set.of( // TC_CLASS, for array class char[]
|
||||||
|
assertType(char[].class),
|
||||||
|
assertArrayLength(-1),
|
||||||
|
assertDepth(2)),
|
||||||
|
Set.of( // TC_ARRAY, first char[] array
|
||||||
|
assertType(char[].class),
|
||||||
|
assertArrayLength(2),
|
||||||
|
assertDepth(2)),
|
||||||
|
Set.of( // TC_REFERENCE, for TC_CLASS relating to second stream array
|
||||||
|
assertType(null),
|
||||||
|
assertArrayLength(-1),
|
||||||
|
assertDepth(2)),
|
||||||
|
Set.of( // TC_ARRAY, second char[] array
|
||||||
|
assertType(char[].class),
|
||||||
|
assertArrayLength(1),
|
||||||
|
assertDepth(2))) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "scenarios")
|
||||||
|
public void test(int id,
|
||||||
|
Runnable thunk,
|
||||||
|
List<Set<Consumer<RecordedEvent>>> expectedValuesChecker)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
try (Recording recording = new Recording()) {
|
||||||
|
recording.enable(EventNames.Deserialization);
|
||||||
|
recording.start();
|
||||||
|
thunk.run();
|
||||||
|
recording.stop();
|
||||||
|
List<RecordedEvent> events = Events.fromRecording(recording);
|
||||||
|
assertEquals(events.size(), expectedValuesChecker.size());
|
||||||
|
assertEventList(events, expectedValuesChecker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Class<InvalidClassException> ICE = InvalidClassException.class;
|
||||||
|
|
||||||
|
@DataProvider(name = "filterDisallowedValues")
|
||||||
|
public Object[][] filterDisallowedValues() {
|
||||||
|
return new Object[][] {
|
||||||
|
{ Status.REJECTED, "REJECTED" },
|
||||||
|
{ null, null }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "filterDisallowedValues")
|
||||||
|
public void testFilterDisallow(Status filterStatus,
|
||||||
|
String expectedValue)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
try (Recording recording = new Recording();
|
||||||
|
var bais = new ByteArrayInputStream(serialize(new R()));
|
||||||
|
var ois = new ObjectInputStream(bais)) {
|
||||||
|
ois.setObjectInputFilter(fv -> filterStatus);
|
||||||
|
recording.enable(EventNames.Deserialization);
|
||||||
|
recording.start();
|
||||||
|
assertThrows(ICE, () -> ois.readObject());
|
||||||
|
recording.stop();
|
||||||
|
List<RecordedEvent> events = Events.fromRecording(recording);
|
||||||
|
assertEquals(events.size(), 1);
|
||||||
|
assertEquals(events.get(0).getEventType().getName(), "jdk.Deserialization");
|
||||||
|
assertFilterConfigured(true).accept(events.get(0));
|
||||||
|
assertFilterStatus(expectedValue).accept(events.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DataProvider(name = "filterAllowedValues")
|
||||||
|
public Object[][] filterAllowedValues() {
|
||||||
|
return new Object[][] {
|
||||||
|
{ Status.ALLOWED, "ALLOWED" },
|
||||||
|
{ Status.UNDECIDED, "UNDECIDED" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "filterAllowedValues")
|
||||||
|
public void testFilterAllowed(Status filterStatus,
|
||||||
|
String expectedValue) throws Exception {
|
||||||
|
try (Recording recording = new Recording();
|
||||||
|
var bais = new ByteArrayInputStream(serialize(new R()));
|
||||||
|
var ois = new ObjectInputStream(bais)) {
|
||||||
|
ois.setObjectInputFilter(fv -> filterStatus);
|
||||||
|
recording.enable(EventNames.Deserialization);
|
||||||
|
recording.start();
|
||||||
|
ois.readObject();
|
||||||
|
recording.stop();
|
||||||
|
List<RecordedEvent> events = Events.fromRecording(recording);
|
||||||
|
assertEquals(events.size(), 1);
|
||||||
|
assertEquals(events.get(0).getEventType().getName(), "jdk.Deserialization");
|
||||||
|
assertFilterConfigured(true).accept(events.get(0));
|
||||||
|
assertFilterStatus(expectedValue).accept(events.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class XYZException extends RuntimeException {
|
||||||
|
XYZException(String msg) { super(msg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testException() throws Exception {
|
||||||
|
try (Recording recording = new Recording();
|
||||||
|
var bais = new ByteArrayInputStream(serialize(new R()));
|
||||||
|
var ois = new ObjectInputStream(bais)) {
|
||||||
|
ois.setObjectInputFilter(fv -> { throw new XYZException("I am a bad filter!!!"); });
|
||||||
|
recording.enable(EventNames.Deserialization);
|
||||||
|
recording.start();
|
||||||
|
assertThrows(ICE, () -> ois.readObject());
|
||||||
|
recording.stop();
|
||||||
|
List<RecordedEvent> events = Events.fromRecording(recording);
|
||||||
|
assertEquals(events.size(), 1);
|
||||||
|
assertEquals(events.get(0).getEventType().getName(), "jdk.Deserialization");
|
||||||
|
assertFilterConfigured(true).accept(events.get(0));
|
||||||
|
assertExceptionType(XYZException.class).accept(events.get(0));
|
||||||
|
assertExceptionMessage("I am a bad filter!!!").accept(events.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertEventList(List<RecordedEvent> actualEvents,
|
||||||
|
List<Set<Consumer<RecordedEvent>>> expectedValuesChecker) {
|
||||||
|
int found = 0;
|
||||||
|
for (RecordedEvent recordedEvent : actualEvents) {
|
||||||
|
assertEquals(recordedEvent.getEventType().getName(), "jdk.Deserialization");
|
||||||
|
out.println("Checking recorded event:" + recordedEvent);
|
||||||
|
Set<Consumer<RecordedEvent>> checkers = expectedValuesChecker.get(found);
|
||||||
|
for (Consumer<RecordedEvent> checker : checkers) {
|
||||||
|
out.println(" checking:" + checker);
|
||||||
|
checker.accept(recordedEvent);
|
||||||
|
}
|
||||||
|
assertFilterConfigured(false).accept(recordedEvent); // no filter expected
|
||||||
|
assertExceptionType(null).accept(recordedEvent); // no exception type expected
|
||||||
|
assertExceptionMessage(null).accept(recordedEvent); // no exception message expected
|
||||||
|
found++;
|
||||||
|
}
|
||||||
|
assertEquals(found, expectedValuesChecker.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
static Consumer<RecordedEvent> assertFilterConfigured(boolean expectedValue) {
|
||||||
|
return new Consumer<>() {
|
||||||
|
@Override public void accept(RecordedEvent recordedEvent) {
|
||||||
|
assertTrue(recordedEvent.hasField("filterConfigured"));
|
||||||
|
assertEquals((boolean)recordedEvent.getValue("filterConfigured"), expectedValue);
|
||||||
|
}
|
||||||
|
@Override public String toString() {
|
||||||
|
return "assertFilterConfigured, expectedValue=" + expectedValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Consumer<RecordedEvent> assertFilterStatus(String expectedValue) {
|
||||||
|
return new Consumer<>() {
|
||||||
|
@Override public void accept(RecordedEvent recordedEvent) {
|
||||||
|
assertTrue(recordedEvent.hasField("filterStatus"));
|
||||||
|
assertEquals(recordedEvent.getValue("filterStatus"), expectedValue);
|
||||||
|
}
|
||||||
|
@Override public String toString() {
|
||||||
|
return "assertFilterStatus, expectedValue=" + expectedValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Consumer<RecordedEvent> assertType(Class<?> expectedValue) {
|
||||||
|
return new Consumer<>() {
|
||||||
|
@Override public void accept(RecordedEvent recordedEvent) {
|
||||||
|
assertTrue(recordedEvent.hasField("type"));
|
||||||
|
assertClassOrNull(expectedValue, recordedEvent, "type");
|
||||||
|
}
|
||||||
|
@Override public String toString() {
|
||||||
|
return "assertType, expectedValue=" + expectedValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Consumer<RecordedEvent> assertArrayLength(int expectedValue) {
|
||||||
|
return new Consumer<>() {
|
||||||
|
@Override public void accept(RecordedEvent recordedEvent) {
|
||||||
|
assertTrue(recordedEvent.hasField("arrayLength"));
|
||||||
|
assertEquals((int)recordedEvent.getValue("arrayLength"), expectedValue);
|
||||||
|
}
|
||||||
|
@Override public String toString() {
|
||||||
|
return "assertArrayLength, expectedValue=" + expectedValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Consumer<RecordedEvent> assertObjectReferences(long expectedValue) {
|
||||||
|
return new Consumer<>() {
|
||||||
|
@Override public void accept(RecordedEvent recordedEvent) {
|
||||||
|
assertTrue(recordedEvent.hasField("objectReferences"));
|
||||||
|
assertEquals((long)recordedEvent.getValue("objectReferences"), expectedValue);
|
||||||
|
}
|
||||||
|
@Override public String toString() {
|
||||||
|
return "assertObjectReferences, expectedValue=" + expectedValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Consumer<RecordedEvent> assertDepth(long expectedValue) {
|
||||||
|
return new Consumer<>() {
|
||||||
|
@Override public void accept(RecordedEvent recordedEvent) {
|
||||||
|
assertTrue(recordedEvent.hasField("depth"));
|
||||||
|
assertEquals((long)recordedEvent.getValue("depth"), expectedValue);
|
||||||
|
}
|
||||||
|
@Override public String toString() {
|
||||||
|
return "assertDepth, expectedValue=" + expectedValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Consumer<RecordedEvent> assertHasBytesRead() {
|
||||||
|
return new Consumer<>() {
|
||||||
|
@Override public void accept(RecordedEvent recordedEvent) {
|
||||||
|
assertTrue(recordedEvent.hasField("bytesRead"));
|
||||||
|
}
|
||||||
|
@Override public String toString() {
|
||||||
|
return "assertHasBytesRead,";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Consumer<RecordedEvent> assertBytesRead(long expectedValue) {
|
||||||
|
return new Consumer<>() {
|
||||||
|
@Override public void accept(RecordedEvent recordedEvent) {
|
||||||
|
assertHasBytesRead().accept(recordedEvent);
|
||||||
|
assertEquals((long)recordedEvent.getValue("bytesRead"), expectedValue);
|
||||||
|
}
|
||||||
|
@Override public String toString() {
|
||||||
|
return "assertBytesRead, expectedValue=" + expectedValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Consumer<RecordedEvent> assertExceptionType(Class<?> expectedValue) {
|
||||||
|
return new Consumer<>() {
|
||||||
|
@Override public void accept(RecordedEvent recordedEvent) {
|
||||||
|
assertTrue(recordedEvent.hasField("exceptionType"));
|
||||||
|
assertClassOrNull(expectedValue, recordedEvent, "exceptionType");
|
||||||
|
}
|
||||||
|
@Override public String toString() {
|
||||||
|
return "assertExceptionType, expectedValue=" + expectedValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Consumer<RecordedEvent> assertExceptionMessage(String expectedValue) {
|
||||||
|
return new Consumer<>() {
|
||||||
|
@Override public void accept(RecordedEvent recordedEvent) {
|
||||||
|
assertTrue(recordedEvent.hasField("exceptionMessage"));
|
||||||
|
assertEquals(recordedEvent.getValue("exceptionMessage"), expectedValue);
|
||||||
|
}
|
||||||
|
@Override public String toString() {
|
||||||
|
return "assertExceptionMessage, expectedValue=" + expectedValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertClassOrNull(Class<?> expectedValue,
|
||||||
|
RecordedEvent recordedEvent,
|
||||||
|
String valueName) {
|
||||||
|
if (expectedValue == null && recordedEvent.getValue(valueName) == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (recordedEvent.getValue(valueName) instanceof RecordedClass recordedClass)
|
||||||
|
assertEquals(recordedClass.getName(), expectedValue.getName());
|
||||||
|
else
|
||||||
|
fail("Expected RecordedClass, got:" + recordedEvent.getValue(valueName).getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> byte[] serialize(T obj) throws IOException {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
ObjectOutputStream oos = new ObjectOutputStream(baos);
|
||||||
|
oos.writeObject(obj);
|
||||||
|
oos.close();
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static <T> T deserialize(byte[] streamBytes) {
|
||||||
|
try {
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes);
|
||||||
|
ObjectInputStream ois = new ObjectInputStream(bais);
|
||||||
|
return (T) ois.readObject();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
static volatile boolean initializedFoo; // false
|
||||||
|
// Do not inadvertently initialize this class, Foo.
|
||||||
|
static class Foo implements Serializable {
|
||||||
|
static { TestDeserializationEvent.initializedFoo = true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the creation and recording of the Deserialization event does
|
||||||
|
* not inadvertently trigger initialization of the class of the stream
|
||||||
|
* object, when deserialization is rejected by the filter.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRejectedClassNotInitialized() throws Exception {
|
||||||
|
byte[] bytes = SerialObjectBuilder.newBuilder("Foo").build();
|
||||||
|
assertFalse(initializedFoo); // sanity
|
||||||
|
|
||||||
|
try (Recording recording = new Recording();
|
||||||
|
var bais = new ByteArrayInputStream(bytes);
|
||||||
|
var ois = new ObjectInputStream(bais)) {
|
||||||
|
ois.setObjectInputFilter(fv -> Status.REJECTED);
|
||||||
|
recording.enable(EventNames.Deserialization);
|
||||||
|
recording.start();
|
||||||
|
assertThrows(ICE, () -> ois.readObject());
|
||||||
|
recording.stop();
|
||||||
|
List<RecordedEvent> events = Events.fromRecording(recording);
|
||||||
|
assertEquals(events.size(), 1);
|
||||||
|
assertEquals(events.get(0).getEventType().getName(), "jdk.Deserialization");
|
||||||
|
assertFilterConfigured(true).accept(events.get(0));
|
||||||
|
assertFilterStatus("REJECTED").accept(events.get(0));
|
||||||
|
assertFalse(initializedFoo);
|
||||||
|
assertType(Foo.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -178,6 +178,7 @@ public class TestDefaultConfigurations {
|
||||||
insertSetting(doc, EventNames.X509Certificate, "threshold", "0 ns");
|
insertSetting(doc, EventNames.X509Certificate, "threshold", "0 ns");
|
||||||
insertSetting(doc, EventNames.X509Validation, "threshold", "0 ns");
|
insertSetting(doc, EventNames.X509Validation, "threshold", "0 ns");
|
||||||
insertSetting(doc, EventNames.ProcessStart, "threshold", "0 ns");
|
insertSetting(doc, EventNames.ProcessStart, "threshold", "0 ns");
|
||||||
|
insertSetting(doc, EventNames.Deserialization, "threshold", "0 ns");
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -212,9 +212,9 @@ public class TestEventMetadata {
|
||||||
String lowerCased = name.toLowerCase();
|
String lowerCased = name.toLowerCase();
|
||||||
Asserts.assertFalse(lowerCased.contains("info") && !lowerCased.contains("information"), "Use 'information' instead 'info' in name");
|
Asserts.assertFalse(lowerCased.contains("info") && !lowerCased.contains("information"), "Use 'information' instead 'info' in name");
|
||||||
Asserts.assertFalse(lowerCased.contains("alloc") && !lowerCased.contains("alloca"), "Use 'allocation' instead 'alloc' in name");
|
Asserts.assertFalse(lowerCased.contains("alloc") && !lowerCased.contains("alloca"), "Use 'allocation' instead 'alloc' in name");
|
||||||
Asserts.assertFalse(lowerCased.contains("config") && !lowerCased.contains("configuration"), "Use 'configuration' instead of 'config' in name");
|
Asserts.assertFalse(lowerCased.contains("config") && !(lowerCased.contains("configuration") || lowerCased.contains("filterconfigured")), "Use 'configuration' instead of 'config' in name");
|
||||||
Asserts.assertFalse(lowerCased.contains("evac") && !lowerCased.contains("evacu"), "Use 'evacuation' instead of 'evac' in name");
|
Asserts.assertFalse(lowerCased.contains("evac") && !lowerCased.contains("evacu"), "Use 'evacuation' instead of 'evac' in name");
|
||||||
Asserts.assertFalse(lowerCased.contains("stat") && !(lowerCased.contains("state") ||lowerCased.contains("statistic")) , "Use 'statistics' instead of 'stat' in name");
|
Asserts.assertFalse(lowerCased.contains("stat") && !(lowerCased.contains("state") ||lowerCased.contains("statistic") ||lowerCased.contains("filterstatus")) , "Use 'statistics' instead of 'stat' in name");
|
||||||
Asserts.assertFalse(name.contains("ID") , "Use 'id' or 'Id' instead of 'ID' in name");
|
Asserts.assertFalse(name.contains("ID") , "Use 'id' or 'Id' instead of 'ID' in name");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -206,6 +206,7 @@ public final class TestActiveSettingEvent {
|
||||||
settingValues.put(EventNames.X509Certificate + "#threshold", "0 ns");
|
settingValues.put(EventNames.X509Certificate + "#threshold", "0 ns");
|
||||||
settingValues.put(EventNames.X509Validation + "#threshold", "0 ns");
|
settingValues.put(EventNames.X509Validation + "#threshold", "0 ns");
|
||||||
settingValues.put(EventNames.ProcessStart + "#threshold", "0 ns");
|
settingValues.put(EventNames.ProcessStart + "#threshold", "0 ns");
|
||||||
|
settingValues.put(EventNames.Deserialization + "#threshold", "0 ns");
|
||||||
|
|
||||||
try (Recording recording = new Recording(c)) {
|
try (Recording recording = new Recording(c)) {
|
||||||
Map<Long, EventType> eventTypes = new HashMap<>();
|
Map<Long, EventType> eventTypes = new HashMap<>();
|
||||||
|
|
|
@ -199,6 +199,8 @@ public class EventNames {
|
||||||
public final static String X509Validation = PREFIX + "X509Validation";
|
public final static String X509Validation = PREFIX + "X509Validation";
|
||||||
public final static String SecurityProperty = PREFIX + "SecurityPropertyModification";
|
public final static String SecurityProperty = PREFIX + "SecurityPropertyModification";
|
||||||
public final static String DirectBufferStatistics = PREFIX + "DirectBufferStatistics";
|
public final static String DirectBufferStatistics = PREFIX + "DirectBufferStatistics";
|
||||||
|
public final static String Deserialization = PREFIX + "Deserialization";
|
||||||
|
|
||||||
|
|
||||||
// Flight Recorder
|
// Flight Recorder
|
||||||
public final static String DumpReason = PREFIX + "DumpReason";
|
public final static String DumpReason = PREFIX + "DumpReason";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue