8275338: Add JFR events for notable serialization situations

Reviewed-by: rriggs, egahlin
This commit is contained in:
Raffaello Giulietti 2024-01-18 17:05:35 +00:00
parent 4c1a0fc58f
commit bfd2afe5ad
10 changed files with 781 additions and 0 deletions

View File

@ -55,6 +55,8 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import jdk.internal.event.SerializationMisdeclarationEvent;
import jdk.internal.misc.Unsafe;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
@ -459,6 +461,10 @@ public final class ObjectStreamClass implements Serializable {
}
}
initialized = true;
if (SerializationMisdeclarationEvent.enabled() && serializable) {
SerializationMisdeclarationChecker.checkMisdeclarations(cl);
}
}
/**

View File

@ -0,0 +1,277 @@
/*
* Copyright (c) 2023, 2024, 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 java.io;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import static jdk.internal.event.SerializationMisdeclarationEvent.*;
import static java.lang.reflect.Modifier.*;
final class SerializationMisdeclarationChecker {
private static final String SUID_NAME = "serialVersionUID";
private static final String SERIAL_PERSISTENT_FIELDS_NAME = "serialPersistentFields";
private static final String WRITE_OBJECT_NAME = "writeObject";
private static final String READ_OBJECT_NAME = "readObject";
private static final String READ_OBJECT_NO_DATA_NAME = "readObjectNoData";
private static final String WRITE_REPLACE_NAME = "writeReplace";
private static final String READ_RESOLVE_NAME = "readResolve";
private static final Class<?>[] WRITE_OBJECT_PARAM_TYPES = {ObjectOutputStream.class};
private static final Class<?>[] READ_OBJECT_PARAM_TYPES = {ObjectInputStream.class};
/*
* The sharing of a single Class<?>[] instance here is just to avoid wasting
* space, and should not be considered as a conceptual sharing of types.
*/
private static final Class<?>[] READ_OBJECT_NO_DATA_PARAM_TYPES = {};
private static final Class<?>[] WRITE_REPLACE_PARAM_TYPES = READ_OBJECT_NO_DATA_PARAM_TYPES;
private static final Class<?>[] READ_RESOLVE_PARAM_TYPES = READ_OBJECT_NO_DATA_PARAM_TYPES;
static void checkMisdeclarations(Class<?> cl) {
checkSerialVersionUID(cl);
checkSerialPersistentFields(cl);
checkPrivateMethod(cl, WRITE_OBJECT_NAME,
WRITE_OBJECT_PARAM_TYPES, Void.TYPE);
checkPrivateMethod(cl, READ_OBJECT_NAME,
READ_OBJECT_PARAM_TYPES, Void.TYPE);
checkPrivateMethod(cl, READ_OBJECT_NO_DATA_NAME,
READ_OBJECT_NO_DATA_PARAM_TYPES, Void.TYPE);
checkAccessibleMethod(cl, WRITE_REPLACE_NAME,
WRITE_REPLACE_PARAM_TYPES, Object.class);
checkAccessibleMethod(cl, READ_RESOLVE_NAME,
READ_RESOLVE_PARAM_TYPES, Object.class);
}
private static void checkSerialVersionUID(Class<?> cl) {
Field f = privilegedDeclaredField(cl, SUID_NAME);
if (f == null) {
if (isOrdinaryClass(cl)) {
commitEvent(cl, SUID_NAME + " should be declared explicitly" +
" as a private static final long field");
}
return;
}
if (cl.isEnum()) {
commitEvent(cl, SUID_NAME + " should not be declared in an enum class");
}
if (!isPrivate(f)) {
commitEvent(cl, SUID_NAME + " should be private");
}
if (!isStatic(f)) {
commitEvent(cl, SUID_NAME + " must be static");
}
if (!isFinal(f)) {
commitEvent(cl, SUID_NAME + " must be final");
}
if (f.getType() != Long.TYPE) {
commitEvent(cl, SUID_NAME + " must be of type long");
}
}
private static void checkSerialPersistentFields(Class<?> cl) {
Field f = privilegedDeclaredField(cl, SERIAL_PERSISTENT_FIELDS_NAME);
if (f == null) {
return;
}
if (cl.isRecord()) {
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME +
" should not be declared in a record class");
} else if (cl.isEnum()) {
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME +
" should not be declared in an enum class");
}
if (!isPrivate(f)) {
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME + " must be private");
}
if (!isStatic(f)) {
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME + " must be static");
}
if (!isFinal(f)) {
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME + " must be final");
}
if (f.getType() != ObjectStreamField[].class) {
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME +
" should be of type ObjectStreamField[]");
}
if (!isStatic(f)) {
return;
}
f.setAccessible(true);
Object spf = objectFromStatic(f);
if (spf == null) {
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME + " should be non-null");
return;
}
if (!(spf instanceof ObjectStreamField[])) {
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME +
" must be an instance of ObjectStreamField[]");
}
}
private static void checkPrivateMethod(Class<?> cl,
String name, Class<?>[] paramTypes, Class<?> retType) {
for (Method m : privilegedDeclaredMethods(cl)) {
if (m.getName().equals(name)) {
checkPrivateMethod(cl, m, paramTypes, retType);
}
}
}
private static void checkPrivateMethod(Class<?> cl,
Method m, Class<?>[] paramTypes, Class<?> retType) {
if (cl.isEnum()) {
commitEvent(cl, "method " + m + " should not be declared in an enum class");
} else if (cl.isRecord()) {
commitEvent(cl, "method " + m + " should not be declared in a record class");
}
if (!isPrivate(m)) {
commitEvent(cl, "method " + m + " must be private");
}
if (isStatic(m)) {
commitEvent(cl, "method " + m + " must be non-static");
}
if (m.getReturnType() != retType) {
commitEvent(cl, "method " + m + " must have return type " + retType);
}
if (!Arrays.equals(m.getParameterTypes(), paramTypes)) {
commitEvent(cl, "method " + m + " must have parameter types " + Arrays.toString(paramTypes));
}
}
private static void checkAccessibleMethod(Class<?> cl,
String name, Class<?>[] paramTypes, Class<?> retType) {
for (Class<?> superCl = cl; superCl != null; superCl = superCl.getSuperclass()) {
for (Method m : privilegedDeclaredMethods(superCl)) {
if (m.getName().equals(name)) {
checkAccessibleMethod(cl, superCl, m, paramTypes, retType);
}
}
}
}
private static void checkAccessibleMethod(Class<?> cl,
Class<?> superCl, Method m, Class<?>[] paramTypes, Class<?> retType) {
if (superCl.isEnum()) {
commitEvent(cl, "method " + m + " should not be declared in an enum class");
}
if (isAbstract(m)) {
commitEvent(cl, "method " + m + " must be non-abstract");
}
if (isStatic(m)) {
commitEvent(cl, "method " + m + " must be non-static");
}
if (m.getReturnType() != retType) {
commitEvent(cl, "method " + m + " must have return type " + retType);
}
if (!Arrays.equals(m.getParameterTypes(), paramTypes)) {
commitEvent(cl, "method " + m + " must have parameter types " + Arrays.toString(paramTypes));
}
if (isPrivate(m) && cl != superCl
|| isPackageProtected(m) && !isSamePackage(cl, superCl)) {
commitEvent(cl, "method " + m + " is not accessible");
}
}
private static boolean isSamePackage(Class<?> cl0, Class<?> cl1) {
return cl0.getClassLoader() == cl1.getClassLoader()
&& cl0.getPackageName().equals(cl1.getPackageName());
}
private static boolean isOrdinaryClass(Class<?> cl) {
/* class Enum and class Record are not considered ordinary classes */
return !(cl.isRecord() || cl.isEnum() || cl.isArray()
|| Enum.class == cl || Record.class == cl
|| Proxy.isProxyClass(cl));
}
private static boolean isPrivate(Member m) {
return (m.getModifiers() & PRIVATE) != 0;
}
private static boolean isPackageProtected(Member m) {
return (m.getModifiers() & (PRIVATE | PROTECTED | PUBLIC)) == 0;
}
private static boolean isAbstract(Member m) {
return (m.getModifiers() & ABSTRACT) != 0;
}
private static boolean isFinal(Member m) {
return (m.getModifiers() & FINAL) != 0;
}
private static boolean isStatic(Member m) {
return (m.getModifiers() & STATIC) != 0;
}
@SuppressWarnings("removal")
private static Field privilegedDeclaredField(Class<?> cl, String name) {
if (System.getSecurityManager() == null) {
return declaredField(cl, name);
}
return AccessController.doPrivileged((PrivilegedAction<Field>) () ->
declaredField(cl, name));
}
private static Field declaredField(Class<?> cl, String name) {
try {
return cl.getDeclaredField(name);
} catch (NoSuchFieldException ignored) {
}
return null;
}
@SuppressWarnings("removal")
private static Method[] privilegedDeclaredMethods(Class<?> cl) {
if (System.getSecurityManager() == null) {
return cl.getDeclaredMethods();
}
return AccessController.doPrivileged(
(PrivilegedAction<Method[]>) cl::getDeclaredMethods);
}
private static Object objectFromStatic(Field f) {
try {
return f.get(null);
} catch (IllegalAccessException ignored) {
}
return null;
}
private static void commitEvent(Class<?> cl, String msg) {
commit(timestamp(), cl, msg);
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2023, 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;
/**
* A JFR event for serialization misdeclarations.
* This event is mirrored in {@code jdk.jfr.events.SerializationMisdeclarationEvent}
* where the metadata for the event is provided with annotations.
* Some of the methods are replaced by generated methods when jfr is enabled.
* Note that the order of the arguments of the {@link #commit(long,Class,String)}
* method must be the same as the order of the fields.
*/
public class SerializationMisdeclarationEvent extends Event {
public Class<?> misdeclaredClass;
public String message;
/**
* Commit a serialization misdeclaration event.
* The implementation of this method is generated automatically if jfr is enabled.
* The order of the fields must be the same as the parameters in this method.
* {@code commit(long,Class,String)}
*
* @param start timestamp of the start of the operation
* @param misdeclaredClass the affected class
* @param message the specific event message
*/
public static void commit(long start, Class<?> misdeclaredClass, String message) {
// Generated by JFR
}
/**
* Determine if an event should be emitted. The duration of the operation
* must exceed some threshold in order to commit the event. The implementation
* of this method is generated automatically if jfr is enabled.
*
* @param duration time in nanoseconds to complete the operation
* @return true if the event should be commited
*/
public static boolean shouldCommit(long duration) {
// Generated by JFR
return false;
}
/**
* Determine if this kind of event is enabled. The implementation
* of this method is generated automatically if jfr is enabled.
*
* @return whether serialization misdeclaration events are enabled
*/
public static boolean enabled() {
// Generated by JFR
return false;
}
/**
* Fetch the current timestamp in nanoseconds. This method is used
* to determine the start and end of an operation. The implementation
* of this method is generated automatically if jfr is enabled.
*
* @return the current timestamp value
*/
public static long timestamp() {
// Generated by JFR
return 0L;
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2023, 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;
import jdk.jfr.internal.RemoveFields;
import jdk.jfr.internal.Type;
@Name(Type.EVENT_NAME_PREFIX + "SerializationMisdeclaration")
@Label("Serialization Misdeclaration")
@Category({"Java Development Kit", "Serialization"})
@Description("Methods and fields misdeclarations." +
" The checks are usually performed just once per serializable class," +
" the first time it is used by serialization." +
" Under high memory pressure, a class might be re-checked again.")
@MirrorEvent(className = "jdk.internal.event.SerializationMisdeclarationEvent")
@RemoveFields({"duration", "stackTrace", "eventThread"})
public final class SerializationMisdeclarationEvent extends AbstractJDKEvent {
@Label("Misdeclared Class")
public Class<?> misdeclaredClass;
@Label("Message")
public String message;
}

View File

@ -35,6 +35,7 @@ import jdk.jfr.events.ExceptionThrownEvent;
import jdk.jfr.events.ProcessStartEvent;
import jdk.jfr.events.SecurityPropertyModificationEvent;
import jdk.jfr.events.SecurityProviderServiceEvent;
import jdk.jfr.events.SerializationMisdeclarationEvent;
import jdk.jfr.events.SocketReadEvent;
import jdk.jfr.events.SocketWriteEvent;
import jdk.jfr.events.TLSHandshakeEvent;
@ -52,6 +53,7 @@ public final class MirrorEvents {
ProcessStartEvent.class,
SecurityPropertyModificationEvent.class,
SecurityProviderServiceEvent.class,
SerializationMisdeclarationEvent.class,
SocketReadEvent.class,
SocketWriteEvent.class,
ThreadSleepEvent.class,

View File

@ -75,6 +75,7 @@ public final class JDKEvents {
jdk.internal.event.ProcessStartEvent.class,
jdk.internal.event.SecurityPropertyModificationEvent.class,
jdk.internal.event.SecurityProviderServiceEvent.class,
jdk.internal.event.SerializationMisdeclarationEvent.class,
jdk.internal.event.SocketReadEvent.class,
jdk.internal.event.SocketWriteEvent.class,
jdk.internal.event.ThreadSleepEvent.class,

View File

@ -756,6 +756,10 @@
<setting name="stackTrace">true</setting>
</event>
<event name="jdk.SerializationMisdeclaration">
<setting name="enabled">false</setting>
</event>
<event name="jdk.InitialSecurityProperty">
<setting name="enabled">true</setting>
<setting name="period">beginChunk</setting>

View File

@ -756,6 +756,10 @@
<setting name="stackTrace">true</setting>
</event>
<event name="jdk.SerializationMisdeclaration">
<setting name="enabled">true</setting>
</event>
<event name="jdk.InitialSecurityProperty">
<setting name="enabled">true</setting>
<setting name="period">beginChunk</setting>

View File

@ -0,0 +1,342 @@
/*
* Copyright (c) 2023, 2024 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 jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordingStream;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.ObjectStreamField;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static jdk.test.lib.jfr.EventNames.SerializationMisdeclaration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.params.provider.Arguments.arguments;
/*
* @test
* @bug 8275338
* @summary Check generation of JFR events for misdeclared fields and methods
* relevant to serialization
* @key jfr
* @requires vm.hasJFR
* @library /test/lib
* @run junit/othervm jdk.jfr.event.io.TestSerializationMisdeclarationEvent
*/
public class TestSerializationMisdeclarationEvent {
private static final List<RecordedEvent> events = new ArrayList<>();
@BeforeAll
static void recordEvents() {
try (var rs = new RecordingStream()) {
rs.enable(SerializationMisdeclaration);
rs.onEvent(SerializationMisdeclaration, events::add);
rs.startAsync();
doLookups();
rs.stop();
}
}
static Arguments[] testSingleClassMisdeclarations() {
return new Arguments[] {
arguments(NoSUID.class, new String[] {"serialVersionUID", "should", "explicitly"}),
arguments(NoSUID.class, new String[] {"serialPersistentFields", "should", "non-null"}),
arguments(BadClass.class, new String[] {"serialVersionUID", "should", "private"}),
arguments(BadClass.class, new String[] {"serialVersionUID", "must", "type", "long"}),
arguments(BadClass.class, new String[] {"serialVersionUID", "must", "final"}),
arguments(BadClass.class, new String[] {"serialVersionUID", "must", "static"}),
arguments(BadClass.class, new String[] {"serialPersistentFields", "must", "private"}),
arguments(BadClass.class, new String[] {"serialPersistentFields", "must", "static"}),
arguments(BadClass.class, new String[] {"serialPersistentFields", "must", "final"}),
arguments(BadClass.class, new String[] {"serialPersistentFields", "should", "type", "ObjectStreamField[]"}),
arguments(BadClass.class, new String[] {"method", "writeObject", "must", "private"}),
arguments(BadClass.class, new String[] {"method", "writeObject", "must", "non-static"}),
arguments(BadClass.class, new String[] {"method", "writeObject", "must", "return"}),
arguments(BadClass.class, new String[] {"method", "writeObject", "must", "parameter"}),
arguments(BadClass.class, new String[] {"method", "readObject(", "must", "parameter"}),
arguments(BadClass.class, new String[] {"method", "readObjectNoData", "must", "parameter"}),
arguments(EnumClass.class, new String[] {"serialVersionUID", "enum"}),
arguments(EnumClass.class, new String[] {"serialPersistentFields", "enum"}),
arguments(EnumClass.class, new String[] {"method", "writeObject", "enum"}),
arguments(EnumClass.class, new String[] {"method", "readResolve", "enum"}),
arguments(RecordClass.class, new String[] {"serialPersistentFields", "record"}),
arguments(RecordClass.class, new String[] {"method", "record"}),
arguments(C.class, new String[] {"method", "not", "accessible"}),
arguments(Acc.class, new String[] {"serialPersistentFields", "should", "type", "ObjectStreamField[]"}),
arguments(Acc.class, new String[] {"serialPersistentFields", "must", "instance", "ObjectStreamField[]"}),
arguments(Acc.class, new String[] {"method", "readResolve", "must", "non-abstract"}),
arguments(Acc.class, new String[] {"method", "writeReplace", "must", "non-static"}),
arguments(Acc.class, new String[] {"method", "writeReplace", "must", "return"}),
arguments(Acc.class, new String[] {"method", "writeReplace", "must", "parameter"}),
};
}
static Arguments[] testGoodClass() {
return new Arguments[] {
arguments(A.class),
arguments(B.class),
};
}
@ParameterizedTest
@MethodSource
public void testSingleClassMisdeclarations(Class<?> cls, String[] keywords) {
singleClassEvent(cls, keywords);
}
@ParameterizedTest
@MethodSource
public void testGoodClass(Class<?> cls) {
assertEquals(0, getEventsFor(cls).size(), cls.getName());
}
private static void doLookups() {
ObjectStreamClass.lookup(NoSUID.class);
ObjectStreamClass.lookup(BadClass.class);
ObjectStreamClass.lookup(EnumClass.class);
ObjectStreamClass.lookup(RecordClass.class);
ObjectStreamClass.lookup(Acc.class);
ObjectStreamClass.lookup(A.class);
ObjectStreamClass.lookup(B.class);
ObjectStreamClass.lookup(C.class);
}
private static void singleClassEvent(Class<?> cls, String[] keywords) {
assertEquals(1, getEventsFor(cls, keywords).size(), cls.getName());
}
private static List<RecordedEvent> getEventsFor(Class<?> cls, String[] keywords) {
return events.stream()
.filter(e -> e.getClass("misdeclaredClass").getName().equals(cls.getName())
&& matchesAllKeywords(e.getString("message"), keywords))
.toList();
}
private static boolean matchesAllKeywords(String msg, String[] keywords) {
return Arrays.stream(keywords).allMatch(msg::contains);
}
private static List<RecordedEvent> getEventsFor(Class<?> cls) {
return events.stream()
.filter(e -> e.getClass("misdeclaredClass").getName().equals(cls.getName()))
.toList();
}
private static class A implements Serializable {
@Serial
private static final long serialVersionUID = 0xAAAAL;
@Serial
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
@Serial
private void writeObject(ObjectOutputStream oos) {
}
@Serial
private void readObject(ObjectInputStream ois) {
}
@Serial
private void readObjectNoData() {
}
@Serial
Object writeReplace() {
return null;
}
}
private static class B extends A {
@Serial
private static final long serialVersionUID = 0xBBBBL;
@Serial
private Object readResolve() {
return null;
}
}
private static final class C extends B {
@Serial
private static final long serialVersionUID = 0xCCCCL;
/*
* readResolve() in superclass is not accessible
*/
}
private static final class NoSUID implements Serializable {
/*
* should declare serialVersionUID
*/
/*
* value should be non-null
*/
private static final ObjectStreamField[] serialPersistentFields = null;
}
private static final class BadClass implements Serializable {
/*
* should be private
* must be long
* must be final
*/
Object serialVersionUID = 1.2;
/*
* must be private
* must be static
* must be final
* should be ObjectStreamField[]
*/
Object serialPersistentFields = new String[0];
/*
* must be private
* must be non-static
* must return void
* must accept ObjectOutputStream
*/
static int writeObject(int i) {
return 0;
}
/*
* must accept ObjectInputStream
*/
private void readObject(ObjectOutputStream oos) {
}
/*
* must not accept parameters
*/
private void readObjectNoData(ObjectInputStream ois) {
}
}
private enum EnumClass implements Serializable {
__; // ignored constant
/*
* non-effective on enum
*/
private static final long serialVersionUID = 0xABCDL;
/*
* non-effective on enum
*/
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
/*
* non-effective on enum
*/
private void writeObject(ObjectOutputStream oos) {
}
/*
* non-effective on enum
*/
public Object readResolve() {
return null;
}
}
private record RecordClass() implements Serializable {
/*
* allowed on records
*/
private static final long serialVersionUID = 0x1234L;
/*
* non-effective on records
*/
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
/*
* non-effective on records
*/
static int writeObject(int i) {
return 0;
}
}
private abstract static class Acc implements Serializable {
private static final long serialVersionUID = 0x5678L;
private static final Object serialPersistentFields = new String[0];
/*
* must be non-abstract
*/
abstract Object readResolve();
/*
* must be non-static
*/
static Object writeReplace() {
return null;
}
/*
* must return Object
* must have empty parameter types
*/
String writeReplace(String s) {
return null;
}
}
}

View File

@ -209,6 +209,7 @@ public class EventNames {
public static final String SecurityProviderService = PREFIX + "SecurityProviderService";
public static final String DirectBufferStatistics = PREFIX + "DirectBufferStatistics";
public static final String Deserialization = PREFIX + "Deserialization";
public static final String SerializationMisdeclaration = PREFIX + "SerializationMisdeclaration";
public static final String VirtualThreadStart = PREFIX + "VirtualThreadStart";
public static final String VirtualThreadEnd = PREFIX + "VirtualThreadEnd";
public static final String VirtualThreadPinned = PREFIX + "VirtualThreadPinned";