mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-26 22:34:27 +02:00
8275338: Add JFR events for notable serialization situations
Reviewed-by: rriggs, egahlin
This commit is contained in:
parent
4c1a0fc58f
commit
bfd2afe5ad
10 changed files with 781 additions and 0 deletions
|
@ -55,6 +55,8 @@ import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import jdk.internal.event.SerializationMisdeclarationEvent;
|
||||||
import jdk.internal.misc.Unsafe;
|
import jdk.internal.misc.Unsafe;
|
||||||
import jdk.internal.reflect.CallerSensitive;
|
import jdk.internal.reflect.CallerSensitive;
|
||||||
import jdk.internal.reflect.Reflection;
|
import jdk.internal.reflect.Reflection;
|
||||||
|
@ -459,6 +461,10 @@ public final class ObjectStreamClass implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
|
if (SerializationMisdeclarationEvent.enabled() && serializable) {
|
||||||
|
SerializationMisdeclarationChecker.checkMisdeclarations(cl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ import jdk.jfr.events.ExceptionThrownEvent;
|
||||||
import jdk.jfr.events.ProcessStartEvent;
|
import jdk.jfr.events.ProcessStartEvent;
|
||||||
import jdk.jfr.events.SecurityPropertyModificationEvent;
|
import jdk.jfr.events.SecurityPropertyModificationEvent;
|
||||||
import jdk.jfr.events.SecurityProviderServiceEvent;
|
import jdk.jfr.events.SecurityProviderServiceEvent;
|
||||||
|
import jdk.jfr.events.SerializationMisdeclarationEvent;
|
||||||
import jdk.jfr.events.SocketReadEvent;
|
import jdk.jfr.events.SocketReadEvent;
|
||||||
import jdk.jfr.events.SocketWriteEvent;
|
import jdk.jfr.events.SocketWriteEvent;
|
||||||
import jdk.jfr.events.TLSHandshakeEvent;
|
import jdk.jfr.events.TLSHandshakeEvent;
|
||||||
|
@ -52,6 +53,7 @@ public final class MirrorEvents {
|
||||||
ProcessStartEvent.class,
|
ProcessStartEvent.class,
|
||||||
SecurityPropertyModificationEvent.class,
|
SecurityPropertyModificationEvent.class,
|
||||||
SecurityProviderServiceEvent.class,
|
SecurityProviderServiceEvent.class,
|
||||||
|
SerializationMisdeclarationEvent.class,
|
||||||
SocketReadEvent.class,
|
SocketReadEvent.class,
|
||||||
SocketWriteEvent.class,
|
SocketWriteEvent.class,
|
||||||
ThreadSleepEvent.class,
|
ThreadSleepEvent.class,
|
||||||
|
|
|
@ -75,6 +75,7 @@ public final class JDKEvents {
|
||||||
jdk.internal.event.ProcessStartEvent.class,
|
jdk.internal.event.ProcessStartEvent.class,
|
||||||
jdk.internal.event.SecurityPropertyModificationEvent.class,
|
jdk.internal.event.SecurityPropertyModificationEvent.class,
|
||||||
jdk.internal.event.SecurityProviderServiceEvent.class,
|
jdk.internal.event.SecurityProviderServiceEvent.class,
|
||||||
|
jdk.internal.event.SerializationMisdeclarationEvent.class,
|
||||||
jdk.internal.event.SocketReadEvent.class,
|
jdk.internal.event.SocketReadEvent.class,
|
||||||
jdk.internal.event.SocketWriteEvent.class,
|
jdk.internal.event.SocketWriteEvent.class,
|
||||||
jdk.internal.event.ThreadSleepEvent.class,
|
jdk.internal.event.ThreadSleepEvent.class,
|
||||||
|
|
|
@ -756,6 +756,10 @@
|
||||||
<setting name="stackTrace">true</setting>
|
<setting name="stackTrace">true</setting>
|
||||||
</event>
|
</event>
|
||||||
|
|
||||||
|
<event name="jdk.SerializationMisdeclaration">
|
||||||
|
<setting name="enabled">false</setting>
|
||||||
|
</event>
|
||||||
|
|
||||||
<event name="jdk.InitialSecurityProperty">
|
<event name="jdk.InitialSecurityProperty">
|
||||||
<setting name="enabled">true</setting>
|
<setting name="enabled">true</setting>
|
||||||
<setting name="period">beginChunk</setting>
|
<setting name="period">beginChunk</setting>
|
||||||
|
|
|
@ -756,6 +756,10 @@
|
||||||
<setting name="stackTrace">true</setting>
|
<setting name="stackTrace">true</setting>
|
||||||
</event>
|
</event>
|
||||||
|
|
||||||
|
<event name="jdk.SerializationMisdeclaration">
|
||||||
|
<setting name="enabled">true</setting>
|
||||||
|
</event>
|
||||||
|
|
||||||
<event name="jdk.InitialSecurityProperty">
|
<event name="jdk.InitialSecurityProperty">
|
||||||
<setting name="enabled">true</setting>
|
<setting name="enabled">true</setting>
|
||||||
<setting name="period">beginChunk</setting>
|
<setting name="period">beginChunk</setting>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -209,6 +209,7 @@ public class EventNames {
|
||||||
public static final String SecurityProviderService = PREFIX + "SecurityProviderService";
|
public static final String SecurityProviderService = PREFIX + "SecurityProviderService";
|
||||||
public static final String DirectBufferStatistics = PREFIX + "DirectBufferStatistics";
|
public static final String DirectBufferStatistics = PREFIX + "DirectBufferStatistics";
|
||||||
public static final String Deserialization = PREFIX + "Deserialization";
|
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 VirtualThreadStart = PREFIX + "VirtualThreadStart";
|
||||||
public static final String VirtualThreadEnd = PREFIX + "VirtualThreadEnd";
|
public static final String VirtualThreadEnd = PREFIX + "VirtualThreadEnd";
|
||||||
public static final String VirtualThreadPinned = PREFIX + "VirtualThreadPinned";
|
public static final String VirtualThreadPinned = PREFIX + "VirtualThreadPinned";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue