-
Notifications
You must be signed in to change notification settings - Fork 5.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
8275338: Add JFR events for notable serialization situations #17129
Changes from 1 commit
d12fdee
b993e7c
f0b96e9
629e8f2
8534d7a
51507e4
fa04bf4
a6f8025
0cab1c1
7c1facd
94fc01b
3289bee
9cb25a1
91523c6
9ca1f36
ff1b706
b76285d
d0f16b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,86 +52,77 @@ final class SerializationMisdeclarationChecker { | |
private static final Class<?>[] WRITE_REPLACE_PARAM_TYPES = {}; | ||
private static final Class<?>[] READ_RESOLVE_PARAM_TYPES = {}; | ||
|
||
private final Class<?> cl; | ||
|
||
SerializationMisdeclarationChecker(Class<?> cl) { | ||
this.cl = cl; | ||
} | ||
|
||
@SuppressWarnings("removal") | ||
void checkMisdeclarations() { | ||
static void checkMisdeclarations(Class<?> cl) { | ||
AccessController.doPrivileged((PrivilegedAction<Object>) () -> { | ||
privilegedCheckSerialVersionUID(); | ||
privilegedCheckSerialPersistentFields(); | ||
privilegedCheckSerialVersionUID(cl); | ||
privilegedCheckSerialPersistentFields(cl); | ||
|
||
privilegedCheckPrivateMethod(WRITE_OBJECT_NAME, WRITE_OBJECT_PARAM_TYPES, Void.TYPE); | ||
privilegedCheckPrivateMethod(READ_OBJECT_NAME, READ_OBJECT_PARAM_TYPES, Void.TYPE); | ||
privilegedCheckPrivateMethod(READ_OBJECT_NO_DATA_NAME, READ_OBJECT_NO_DATA_PARAM_TYPES, Void.TYPE); | ||
privilegedCheckPrivateMethod(cl, WRITE_OBJECT_NAME, | ||
WRITE_OBJECT_PARAM_TYPES, Void.TYPE); | ||
privilegedCheckPrivateMethod(cl, READ_OBJECT_NAME, | ||
READ_OBJECT_PARAM_TYPES, Void.TYPE); | ||
privilegedCheckPrivateMethod(cl, READ_OBJECT_NO_DATA_NAME, | ||
READ_OBJECT_NO_DATA_PARAM_TYPES, Void.TYPE); | ||
|
||
privilegedCheckAccessibleMethod(WRITE_REPLACE_NAME, WRITE_REPLACE_PARAM_TYPES, Object.class); | ||
privilegedCheckAccessibleMethod(READ_RESOLVE_NAME, READ_RESOLVE_PARAM_TYPES, Object.class); | ||
privilegedCheckAccessibleMethod(cl, WRITE_REPLACE_NAME, | ||
WRITE_REPLACE_PARAM_TYPES, Object.class); | ||
privilegedCheckAccessibleMethod(cl, READ_RESOLVE_NAME, | ||
READ_RESOLVE_PARAM_TYPES, Object.class); | ||
|
||
return null; | ||
}); | ||
} | ||
|
||
private void privilegedCheckSerialVersionUID() { | ||
private static void privilegedCheckSerialVersionUID(Class<?> cl) { | ||
Field f = declaredField(cl, SUID_NAME); | ||
if (f == null) { | ||
if (isOrdinaryClass()) { | ||
commitEvent(SUID_NAME + | ||
" should be declared explicitly as a private static final long field"); | ||
if (isOrdinaryClass(cl)) { | ||
commitEvent(cl, SUID_NAME + " should be declared explicitly" + | ||
" as a private static final long field"); | ||
} | ||
return; | ||
} | ||
if (cl.isEnum()) { | ||
commitEvent(SUID_NAME + | ||
" in an enum class is not effective"); | ||
commitEvent(cl, SUID_NAME + " in an enum class is not effective"); | ||
} | ||
if (!isPrivate(f)) { | ||
commitEvent(SUID_NAME + | ||
" should be private"); | ||
commitEvent(cl, SUID_NAME + " should be private"); | ||
} | ||
if (!isStatic(f)) { | ||
commitEvent(SUID_NAME + | ||
" must be static to be effective"); | ||
commitEvent(cl, SUID_NAME + " must be static"); | ||
} | ||
if (!isFinal(f)) { | ||
commitEvent(SUID_NAME + | ||
" must be final to be effective"); | ||
commitEvent(cl, SUID_NAME + " must be final"); | ||
} | ||
if (f.getType() != Long.TYPE) { | ||
commitEvent(SUID_NAME + | ||
" must be of type long to be effective"); | ||
commitEvent(cl, SUID_NAME + " must be of type long"); | ||
} | ||
} | ||
|
||
private void privilegedCheckSerialPersistentFields() { | ||
private static void privilegedCheckSerialPersistentFields(Class<?> cl) { | ||
Field f = declaredField(cl, SERIAL_PERSISTENT_FIELDS_NAME); | ||
if (f == null) { | ||
return; | ||
} | ||
if (cl.isRecord()) { | ||
commitEvent(SERIAL_PERSISTENT_FIELDS_NAME + | ||
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME + | ||
" in a record class is not effective"); | ||
} else if (cl.isEnum()) { | ||
commitEvent(SERIAL_PERSISTENT_FIELDS_NAME + | ||
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME + | ||
" in an enum class is not effective"); | ||
} | ||
if (!isPrivate(f)) { | ||
commitEvent(SERIAL_PERSISTENT_FIELDS_NAME + | ||
" must be private to be effective"); | ||
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME + " must be private"); | ||
} | ||
if (!isStatic(f)) { | ||
commitEvent(SERIAL_PERSISTENT_FIELDS_NAME + | ||
" must be static to be effective"); | ||
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME + " must be static"); | ||
} | ||
if (!isFinal(f)) { | ||
commitEvent(SERIAL_PERSISTENT_FIELDS_NAME + | ||
" must be final to be effective"); | ||
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME + " must be final"); | ||
} | ||
if (f.getType() != ObjectStreamField[].class) { | ||
commitEvent(SERIAL_PERSISTENT_FIELDS_NAME + | ||
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME + | ||
" should be of type ObjectStreamField[]"); | ||
} | ||
if (!isStatic(f)) { | ||
|
@@ -140,75 +131,76 @@ private void privilegedCheckSerialPersistentFields() { | |
f.setAccessible(true); | ||
Object spf = objectFromStatic(f); | ||
if (spf == null) { | ||
commitEvent(SERIAL_PERSISTENT_FIELDS_NAME + | ||
" must be non-null to be effective"); | ||
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME + " must be non-null"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "must" -> "should". The serialization spec describes the behavior if the field is null. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right |
||
return; | ||
} | ||
if (!(spf instanceof ObjectStreamField[])) { | ||
commitEvent(SERIAL_PERSISTENT_FIELDS_NAME + | ||
" must be an instance of ObjectStreamField[] to be effective"); | ||
commitEvent(cl, SERIAL_PERSISTENT_FIELDS_NAME + | ||
" must be an instance of ObjectStreamField[]"); | ||
} | ||
} | ||
|
||
private void privilegedCheckPrivateMethod(String name, Class<?>[] paramTypes, Class<?> retType) { | ||
private static void privilegedCheckPrivateMethod(Class<?> cl, | ||
String name, Class<?>[] paramTypes, Class<?> retType) { | ||
for (Method m : cl.getDeclaredMethods()) { | ||
if (m.getName().equals(name)) { | ||
privilegedCheckPrivateMethod(m, paramTypes, retType); | ||
privilegedCheckPrivateMethod(cl,m, paramTypes, retType); | ||
} | ||
} | ||
} | ||
|
||
private void privilegedCheckPrivateMethod(Method m, Class<?>[] paramTypes, Class<?> retType) { | ||
private static void privilegedCheckPrivateMethod(Class<?> cl, | ||
Method m, Class<?>[] paramTypes, Class<?> retType) { | ||
if (cl.isEnum()) { | ||
commitEvent("method " + m + " on an enum class is not effective"); | ||
commitEvent(cl, "method " + m + " on an enum class is not effective"); | ||
} else if (cl.isRecord()) { | ||
commitEvent("method " + m + " on an record class is not effective"); | ||
commitEvent(cl, "method " + m + " on a record class is not effective"); | ||
} | ||
if (!isPrivate(m)) { | ||
commitEvent("method " + m + " must be private to be effective"); | ||
commitEvent(cl, "method " + m + " must be private"); | ||
} | ||
if (isStatic(m)) { | ||
commitEvent("method " + m + " must be non-static to be effective"); | ||
commitEvent(cl, "method " + m + " must be non-static"); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should there also be a check to see if this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure that a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The spec is silent about methods being 'native'; it would generally be impractical to implement native methods for these purposes, but a native method can implement the behavior. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @RogerRiggs The checks are agnostic about a method being There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, there's nothing to change; a method declared as native is not mis-declared. |
||
if (m.getReturnType() != retType) { | ||
commitEvent("method " + m + " must have return type " + retType + " to be effective"); | ||
commitEvent(cl, "method " + m + " must have return type " + retType); | ||
} | ||
if (!Arrays.equals(m.getParameterTypes(), paramTypes)) { | ||
commitEvent("method " + m + " must have parameter types " + Arrays.toString(paramTypes) + " to be effective"); | ||
commitEvent(cl, "method " + m + " must have parameter types " + Arrays.toString(paramTypes)); | ||
} | ||
} | ||
|
||
private void privilegedCheckAccessibleMethod(String name, | ||
Class<?>[] paramTypes, Class<?> retType) { | ||
for (Class<?> cls = cl; cls != null; cls = cls.getSuperclass()) { | ||
for (Method m : cls.getDeclaredMethods()) { | ||
private static void privilegedCheckAccessibleMethod(Class<?> cl, | ||
String name, Class<?>[] paramTypes, Class<?> retType) { | ||
for (Class<?> superCl = cl; superCl != null; superCl = superCl.getSuperclass()) { | ||
for (Method m : superCl.getDeclaredMethods()) { | ||
if (m.getName().equals(name)) { | ||
privilegedCheckAccessibleMethod(cls, m, paramTypes, retType); | ||
privilegedCheckAccessibleMethod(cl, superCl, m, paramTypes, retType); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private void privilegedCheckAccessibleMethod(Class<?> cls, Method m, | ||
Class<?>[] paramTypes, Class<?> retType) { | ||
if (cls.isEnum()) { | ||
commitEvent("method " + m + " on an enum class is not effective"); | ||
private static void privilegedCheckAccessibleMethod(Class<?> cl, | ||
Class<?> superCl, Method m, Class<?>[] paramTypes, Class<?> retType) { | ||
if (superCl.isEnum()) { | ||
commitEvent(cl, "method " + m + " on an enum class is not effective"); | ||
} | ||
if (isAbstract(m)) { | ||
commitEvent("method " + m + " must be non-abstract to be effective"); | ||
commitEvent(cl, "method " + m + " must be non-abstract"); | ||
} | ||
if (isStatic(m)) { | ||
commitEvent("method " + m + " must be non-static to be effective"); | ||
commitEvent(cl, "method " + m + " must be non-static"); | ||
} | ||
if (m.getReturnType() != retType) { | ||
commitEvent("method " + m + " must have return type " + retType + " to be effective"); | ||
commitEvent(cl, "method " + m + " must have return type " + retType); | ||
} | ||
if (!Arrays.equals(m.getParameterTypes(), paramTypes)) { | ||
commitEvent("method " + m + " must have parameter types " + Arrays.toString(paramTypes) + " to be effective"); | ||
commitEvent(cl, "method " + m + " must have parameter types " + Arrays.toString(paramTypes)); | ||
} | ||
if (isPrivate(m) && cl != cls | ||
|| isPackageProtected(m) && !isSamePackage(cl, cls)) { | ||
commitEvent("method " + m + " is not accessible"); | ||
if (isPrivate(m) && cl != superCl | ||
|| isPackageProtected(m) && !isSamePackage(cl, superCl)) { | ||
commitEvent(cl, "method " + m + " is not accessible"); | ||
} | ||
} | ||
|
||
|
@@ -217,7 +209,7 @@ private static boolean isSamePackage(Class<?> cl0, Class<?> cl1) { | |
&& cl0.getPackageName().equals(cl1.getPackageName()); | ||
} | ||
|
||
private boolean isOrdinaryClass() { | ||
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 | ||
|
@@ -260,12 +252,8 @@ private static Object objectFromStatic(Field f) { | |
return null; | ||
} | ||
|
||
private void commitEvent(String msg, Object... args) { | ||
commitEvent(cl, msg); | ||
} | ||
|
||
private static void commitEvent(Class<?> cls, String msg) { | ||
commit(timestamp(), cls, msg); | ||
private static void commitEvent(Class<?> cl, String msg) { | ||
commit(timestamp(), cl, msg); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a best practice that can be included in the message? "SUID should not be declared"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's perhaps clearer, will do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typically in other places in the JDK we use
priviledgedXxx
for naming methods that are simple wrappers that callxxx
from within adoPrivileged
. The method is called privileged because it doesn't require the caller to usedoPrivileged
.That is something like:
See for instance:
jdk/src/java.base/share/classes/sun/security/action/GetPropertyAction.java
Line 107 in ee98d26
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah OK, I wasn't aware of this convention.