3636import static com .oracle .svm .agent .Support .getClassNameOrNull ;
3737import static com .oracle .svm .agent .Support .getDirectCallerClass ;
3838import static com .oracle .svm .agent .Support .getMethodDeclaringClass ;
39+ import static com .oracle .svm .agent .Support .getMethodName ;
3940import static com .oracle .svm .agent .Support .getObjectArgument ;
41+ import static com .oracle .svm .agent .Support .getIntArgument ;
4042import static com .oracle .svm .agent .Support .handles ;
4143import static com .oracle .svm .agent .Support .jniFunctions ;
4244import static com .oracle .svm .agent .Support .jvmtiEnv ;
4345import static com .oracle .svm .agent .Support .jvmtiFunctions ;
4446import static com .oracle .svm .agent .Support .testException ;
4547import static com .oracle .svm .agent .Support .toCString ;
48+ import static com .oracle .svm .agent .Support .callObjectMethodL ;
49+
4650import static com .oracle .svm .agent .jvmti .JvmtiEvent .JVMTI_EVENT_BREAKPOINT ;
4751import static com .oracle .svm .agent .jvmti .JvmtiEvent .JVMTI_EVENT_CLASS_PREPARE ;
4852import static com .oracle .svm .agent .jvmti .JvmtiEvent .JVMTI_EVENT_NATIVE_METHOD_BIND ;
6266import java .util .concurrent .locks .ReentrantLock ;
6367import java .util .function .Supplier ;
6468
69+ import com .oracle .svm .configure .trace .AccessAdvisor ;
70+ import com .oracle .svm .core .jdk .serialize .MethodAccessorNameGenerator ;
6571import org .graalvm .compiler .core .common .NumUtil ;
72+ import org .graalvm .compiler .phases .common .LazyValue ;
6673import org .graalvm .nativeimage .StackValue ;
6774import org .graalvm .nativeimage .UnmanagedMemory ;
6875import org .graalvm .nativeimage .c .function .CEntryPoint ;
94101import com .oracle .svm .configure .config .ConfigurationMethod ;
95102import com .oracle .svm .core .c .function .CEntryPointOptions ;
96103import com .oracle .svm .core .util .VMError ;
104+
97105import com .oracle .svm .jni .nativeapi .JNIEnvironment ;
98106import com .oracle .svm .jni .nativeapi .JNIMethodId ;
99107import com .oracle .svm .jni .nativeapi .JNINativeMethod ;
@@ -129,6 +137,7 @@ final class BreakpointInterceptor {
129137 private static ProxyAccessVerifier proxyVerifier ;
130138 private static ResourceAccessVerifier resourceVerifier ;
131139
140+ private static AccessAdvisor accessAdvisor = new AccessAdvisor ();
132141 private static Map <Long , Breakpoint > installedBreakpoints ;
133142
134143 /**
@@ -160,7 +169,11 @@ final class BreakpointInterceptor {
160169
161170 private static final ThreadLocal <Boolean > recursive = ThreadLocal .withInitial (() -> Boolean .FALSE );
162171
163- private static void traceBreakpoint (JNIEnvironment env , JNIObjectHandle clazz , JNIObjectHandle declaringClass , JNIObjectHandle callerClass , String function , Object result , Object ... args ) {
172+ static {
173+ accessAdvisor .setInLivePhase (true );
174+ }
175+
176+ static void traceBreakpoint (JNIEnvironment env , JNIObjectHandle clazz , JNIObjectHandle declaringClass , JNIObjectHandle callerClass , String function , Object result , Object ... args ) {
164177 if (traceWriter != null ) {
165178 traceWriter .traceCall ("reflect" ,
166179 function ,
@@ -173,6 +186,18 @@ private static void traceBreakpoint(JNIEnvironment env, JNIObjectHandle clazz, J
173186 }
174187 }
175188
189+ static void traceBreakpoint (JNIEnvironment env , JNIObjectHandle clazz , JNIObjectHandle declaringClass ,
190+ JNIObjectHandle callerClass , String function , boolean allowWrite , boolean unsafeAccess , Object result ,
191+ String fieldName ) {
192+ if (traceWriter != null ) {
193+ traceWriter .traceCall ("reflect" , function , getClassNameOr (env , clazz , null , TraceWriter .UNKNOWN_VALUE ),
194+ getClassNameOr (env , declaringClass , null , TraceWriter .UNKNOWN_VALUE ),
195+ getClassNameOr (env , callerClass , null , TraceWriter .UNKNOWN_VALUE ), result , allowWrite , unsafeAccess ,
196+ fieldName );
197+ guarantee (!testException (env ));
198+ }
199+ }
200+
176201 private static boolean forName (JNIEnvironment jni , Breakpoint bp ) {
177202 JNIObjectHandle callerClass = getDirectCallerClass ();
178203 JNIObjectHandle name = getObjectArgument (0 );
@@ -504,6 +529,20 @@ private static boolean newInstance(JNIEnvironment jni, Breakpoint bp) {
504529 JNIMethodId result = nullPointer ();
505530 String name = "<init>" ;
506531 String signature = "()V" ;
532+ /*
533+ * "sun.reflect.MethodAccessorGenerator$1" is added as Include in AccessAdvisor's
534+ * internalsFilter in order to support serialization/deserialization. But newInstance call
535+ * in sun.reflect.MethodAccessorGenerator$1 is invoked in 3 cases: 1. Reflection for method
536+ * invoke 2. Reflection for newInstance invoke 3. Serialization/deserialization The first
537+ * two cases are removed from native-image runtime. The third case is traced by
538+ * com.oracle.svm.agent.SerializationSupport.traceReflects. Therefore don't trace the call
539+ * here to avoid introducing unnecessary items in the reflection configuration file.
540+ */
541+ String callerClassName = getClassNameOrNull (jni , callerClass );
542+ if (callerClassName != null && callerClassName .equals ("sun.reflect.MethodAccessorGenerator$1" )) {
543+ return false ;
544+ }
545+
507546 JNIObjectHandle self = getObjectArgument (0 );
508547 if (self .notEqual (nullHandle ())) {
509548 try (CCharPointerHolder ctorName = toCString (name ); CCharPointerHolder ctorSignature = toCString (signature )) {
@@ -675,6 +714,172 @@ private static boolean handleGetSystemResources(JNIEnvironment jni, Breakpoint b
675714 return allowed ;
676715 }
677716
717+ /**
718+ * Replace the returned value of method sun.reflect.MethodAccessorGenerator.generateName(). The
719+ * returned generated name's postfix is changed from a counter value to the declaring class'
720+ * name. So the generated serialization constructor support class' name shall be fixed name from
721+ * different runs.
722+ */
723+ @ SuppressWarnings ("unused" )
724+ private static boolean generateName (JNIEnvironment jni , Breakpoint bp ) {
725+ JNIObjectHandle callerClass = getDirectCallerClass ();
726+ boolean isConstructor = getIntArgument (0 ) == 0 ? false : true ;
727+ boolean forSerialization = getIntArgument (1 ) == 0 ? false : true ;
728+ // Get declaringClass from generate() method
729+ String generatedClassName = getGeneratedClassName (jni , getObjectArgument (1 , 1 ), isConstructor , forSerialization );
730+ try (CCharPointerHolder name = toCString (generatedClassName )) {
731+ JNIObjectHandle newRet = jniFunctions ().getNewStringUTF ().invoke (jni , name .get ());
732+ if (jvmtiFunctions ().ForceEarlyReturnObject ().invoke (jvmtiEnv (), nullHandle (), newRet ) == JvmtiError .JVMTI_ERROR_NONE ) {
733+ return true ;
734+ }
735+ }
736+ return false ;
737+ }
738+
739+ private static String getGeneratedClassName (JNIEnvironment jni , JNIObjectHandle declaringClass , boolean isConstructor , boolean forSerialization ) {
740+ String declaringClassName = getClassNameOrNull (jni , declaringClass );
741+ if (declaringClassName == null ) {
742+ throw new RuntimeException ("Cannot find class name" );
743+ }
744+ return MethodAccessorNameGenerator .generateClassName (isConstructor , forSerialization , declaringClassName );
745+ }
746+
747+ /**
748+ * java.lang.ClassLoader.postDefineClass is always called in java.lang.ClassLoader.defineClass,
749+ * so intercepting postDefineClass is equivalent to intercepting defineClass but with extra
750+ * benefit of being always able to get defined class' name even if defineClass' classname
751+ * parameter is null.
752+ */
753+ @ SuppressWarnings ("unused" )
754+ private static boolean postDefineClass (JNIEnvironment jni , Breakpoint bp ) {
755+ JNIObjectHandle callerClass = getDirectCallerClass ();
756+ boolean isDynamicallyGenerated = false ;
757+ // Get class name from the argument "name" of
758+ // defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)
759+ // The first argument is implicitly "this", so "name" is the 2nd parameter.
760+ String nameFromDefineClassParam = fromJniString (jni , getObjectArgument (1 , 1 ));
761+ final String definedClassName ;
762+ // 1. Don't have a name for class before defining.
763+ // The class is dynamically generated.
764+ if (nameFromDefineClassParam == null ) {
765+ isDynamicallyGenerated = true ;
766+ definedClassName = getClassNameOrNull (jni , getObjectArgument (1 ));
767+ } else {
768+ definedClassName = nameFromDefineClassParam ;
769+ // Filter out internal classes which are definitely not dynamically generated
770+ if (accessAdvisor .shouldIgnoreCaller (new LazyValue <>(() -> definedClassName ))) {
771+ return false ;
772+ }
773+
774+ // 2. Class with name starts with $ or contains $$ is usually dynamically generated
775+ String className = definedClassName .substring (definedClassName .lastIndexOf ('.' ) + 1 );
776+ if (className .startsWith ("$" ) || className .contains ("$$" )) {
777+ isDynamicallyGenerated = true ;
778+ } else {
779+ // 3. A dynamically defined class always return null
780+ // when call java.lang.ClassLoader.getResource(classname)
781+ // This is the accurate but slow way.
782+ JNIObjectHandle self = getObjectArgument (0 );
783+ String asResourceName = definedClassName .replace ('.' , '/' ) + ".class" ;
784+ try (CCharPointerHolder resourceNameHolder = toCString (asResourceName );) {
785+ JNIObjectHandle resourceNameJString = jniFunctions ().getNewStringUTF ().invoke (jni , resourceNameHolder .get ());
786+ JNIObjectHandle returnValue = callObjectMethodL (jni , self , handles ().javaLangClassLoaderGetResource , resourceNameJString );
787+ isDynamicallyGenerated = returnValue .equal (nullHandle ());
788+ }
789+ }
790+ }
791+ if (isDynamicallyGenerated ) {
792+ DynamicClassGenerationSupport dynamicSupport = DynamicClassGenerationSupport .getDynamicClassGenerationSupport (jni , callerClass ,
793+ definedClassName , traceWriter );
794+ if (!dynamicSupport .dumpDefinedClass ()) {
795+ return false ;
796+ }
797+ return dynamicSupport .traceReflects ();
798+ } else {
799+ return true ;
800+ }
801+ }
802+
803+ /**
804+ * Disable reflection inflation for 2 reasons:
805+ * <li>1. Javassit and Spring use reflection to call java.lang.ClassLoader.defineClass. A fixed
806+ * stacktrace would be easier to track to find out if the defineClass is called from reflection.
807+ * </li>
808+ * <li>2. native-image build time doesn't need any reflection inflation information so it's safe
809+ * to disable it.</li>
810+ */
811+ @ SuppressWarnings ("unused" )
812+ private static boolean inflationThreshold (JNIEnvironment jni , Breakpoint bp ) {
813+ if (jvmtiFunctions ().ForceEarlyReturnInt ().invoke (jvmtiEnv (), nullHandle (), 10000 ) == JvmtiError .JVMTI_ERROR_NONE ) {
814+ return true ;
815+ } else {
816+ return false ;
817+ }
818+ }
819+
820+ /**
821+ * Handle Class<?> sun.reflect.ClassDefiner.defineClass(String name, byte[] bytes, int off, int
822+ * len, ClassLoader parentClassLoader) Dump dynamic defined class to file.
823+ */
824+ @ SuppressWarnings ("unused" )
825+ private static boolean defineClass (JNIEnvironment jni , Breakpoint bp ) {
826+ JNIObjectHandle callerClass = getDirectCallerClass ();
827+ JNIMethodId method = getCallerMethod (4 );
828+ String methodName = getMethodName (method );
829+ JNIObjectHandle declaringClass = getMethodDeclaringClass (method );
830+ String declaringClassName = getClassNameOr (jni , declaringClass , "null" , "exception" );
831+
832+ // Only dump dynamically defined class from sun.reflect.MethodAccessorGenerator.generate
833+ if (methodName .equals ("generate" ) && declaringClassName != null &&
834+ declaringClassName .equals ("sun.reflect.MethodAccessorGenerator" )) {
835+ // isConstructor parameter of generate method
836+ boolean isConstructor = getIntArgument (4 , 7 ) == 0 ? false : true ;
837+ // forSerialization parameter of generate method
838+ boolean forSerialization = getIntArgument (4 , 8 ) == 0 ? false : true ;
839+ // The first method argument is class name
840+ String generatedClassName = fromJniString (jni , getObjectArgument (0 ));
841+ JNIObjectHandle class2Generate = getObjectArgument (4 , 1 );
842+ JNIObjectHandle serializationTargetClass = getObjectArgument (4 , 9 );
843+ DynamicClassGenerationSupport serializationSupport = DynamicClassGenerationSupport .getSerializeSupport (jni , callerClass ,
844+ class2Generate , generatedClassName , serializationTargetClass , traceWriter );
845+
846+ if (isConstructor && !forSerialization ) {
847+ int i = 0 ;
848+ // Walk along the stack trace to find out if current method is
849+ // called from serialization/deserialization
850+ while (true ) {
851+ JNIMethodId m = getCallerMethod (i );
852+ if (m == nullPointer ()) {
853+ break ;
854+ }
855+ String cName = getClassNameOrNull (jni , getMethodDeclaringClass (m ));
856+ String mName = getMethodName (m );
857+ if (cName == null || mName == null ) {
858+ break ;
859+ }
860+ String fullName = cName + "." + mName ;
861+ // Mark is from serialization/deserialization
862+ if (fullName .equals ("java.io.ObjectInputStream.readObject" ) || fullName .equals ("java.io.ObjectOutputStream.writeObject" )) {
863+ forSerialization = true ;
864+ break ;
865+ }
866+ i ++;
867+ }
868+ }
869+ if (isConstructor && forSerialization ) {
870+ if (!serializationSupport .dumpDefinedClass ()) {
871+ return false ;
872+ }
873+ if (serializationSupport .traceReflects ()) {
874+ return true ;
875+ }
876+ } else {
877+ return true ;
878+ }
879+ }
880+ return false ;
881+ }
882+
678883 private static boolean newProxyInstance (JNIEnvironment jni , Breakpoint bp ) {
679884 JNIObjectHandle callerClass = getDirectCallerClass ();
680885 JNIObjectHandle classLoader = getObjectArgument (0 );
@@ -1212,6 +1417,13 @@ private interface BreakpointHandler {
12121417 brk ("java/lang/reflect/Proxy" , "getProxyClass" , "(Ljava/lang/ClassLoader;[Ljava/lang/Class;)Ljava/lang/Class;" , BreakpointInterceptor ::getProxyClass ),
12131418 brk ("java/lang/reflect/Proxy" , "newProxyInstance" ,
12141419 "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;" , BreakpointInterceptor ::newProxyInstance ),
1420+ /*
1421+ * For dumping dynamically generated classes
1422+ */
1423+ brk ("java/lang/ClassLoader" , "postDefineClass" , "(Ljava/lang/Class;Ljava/security/ProtectionDomain;)V" , BreakpointInterceptor ::postDefineClass ),
1424+ brk ("sun/reflect/ClassDefiner" , "defineClass" , "(Ljava/lang/String;[BIILjava/lang/ClassLoader;)Ljava/lang/Class;" , BreakpointInterceptor ::defineClass ),
1425+ brk ("sun/reflect/MethodAccessorGenerator" , "generateName" , "(ZZ)Ljava/lang/String;" , BreakpointInterceptor ::generateName ),
1426+ brk ("sun/reflect/ReflectionFactory" , "inflationThreshold" , "()I" , BreakpointInterceptor ::inflationThreshold ),
12151427
12161428 optionalBrk ("java/util/ResourceBundle" ,
12171429 "getBundleImpl" ,
0 commit comments