5959import net .bytebuddy .description .type .TypeDescription ;
6060import net .bytebuddy .pool .TypePool ;
6161import org .objectweb .asm .ClassReader ;
62+ import org .objectweb .asm .ClassVisitor ;
6263import org .objectweb .asm .ClassWriter ;
64+ import org .objectweb .asm .MethodVisitor ;
6365import org .objectweb .asm .Opcodes ;
66+ import org .objectweb .asm .commons .JSRInlinerAdapter ;
6467import org .objectweb .asm .tree .ClassNode ;
6568import org .objectweb .asm .tree .MethodNode ;
6669import org .objectweb .asm .tree .analysis .Analyzer ;
7679 * debugger configuration
7780 */
7881public class DebuggerTransformer implements ClassFileTransformer {
79- private static final Logger log = LoggerFactory .getLogger (DebuggerTransformer .class );
82+ private static final Logger LOGGER = LoggerFactory .getLogger (DebuggerTransformer .class );
8083 private static final String CANNOT_FIND_METHOD = "Cannot find method %s::%s%s" ;
8184 private static final String INSTRUMENTATION_FAILS = "Instrumentation fails for %s" ;
8285 private static final String CANNOT_FIND_LINE = "No executable code was found at %s:L%s" ;
@@ -148,7 +151,7 @@ public DebuggerTransformer(
148151 } else if (itwType .equals ("line" )) {
149152 probeCreator = this ::createLineProbes ;
150153 } else {
151- log .warn (
154+ LOGGER .warn (
152155 "Invalid value for 'dd.debugger.instrument-the-world' property: {}. "
153156 + "Valid values are 'method' or 'line'." ,
154157 itwType );
@@ -202,7 +205,7 @@ private void processITWFiles(
202205 for (String fileName : fileNames ) {
203206 Path excludePath = Paths .get (fileName );
204207 if (!Files .exists (excludePath )) {
205- log .warn ("Cannot find exclude file: {}" , excludePath );
208+ LOGGER .warn ("Cannot find exclude file: {}" , excludePath );
206209 continue ;
207210 }
208211 try {
@@ -223,7 +226,7 @@ private void processITWFiles(
223226 classes .add (line );
224227 });
225228 } catch (IOException ex ) {
226- log .warn ("Error reading exclude file '{}' for Instrument-The-World: " , fileName , ex );
229+ LOGGER .warn ("Error reading exclude file '{}' for Instrument-The-World: " , fileName , ex );
227230 }
228231 }
229232 }
@@ -250,7 +253,7 @@ public byte[] transform(
250253 if (definitions .isEmpty ()) {
251254 return null ;
252255 }
253- log .debug ("Matching definitions for class[{}]: {}" , fullyQualifiedClassName , definitions );
256+ LOGGER .debug ("Matching definitions for class[{}]: {}" , fullyQualifiedClassName , definitions );
254257 if (!instrumentationIsAllowed (fullyQualifiedClassName , definitions )) {
255258 return null ;
256259 }
@@ -260,22 +263,22 @@ public byte[] transform(
260263 if (transformed ) {
261264 return writeClassFile (definitions , loader , classFilePath , classNode );
262265 }
263- // This is an info log because in case of SourceFile definition and multiple top-level
266+ // We are logging this because in case of SourceFile definition and multiple top-level
264267 // classes, type may match, but there is one classfile per top-level class so source file
265268 // will match, but not the classfile.
266269 // e.g. Main.java contains Main & TopLevel class, line numbers are in TopLevel class
267- log . info (
270+ LOGGER . debug (
268271 "type {} matched but no transformation for definitions: {}" , classFilePath , definitions );
269272 } catch (Throwable ex ) {
270- log .warn ("Cannot transform: " , ex );
273+ LOGGER .warn ("Cannot transform: " , ex );
271274 reportInstrumentationFails (definitions , fullyQualifiedClassName );
272275 }
273276 return null ;
274277 }
275278
276279 private boolean skipInstrumentation (ClassLoader loader , String classFilePath ) {
277280 if (definitionMatcher .isEmpty ()) {
278- log .debug ("No debugger definitions present." );
281+ LOGGER .debug ("No debugger definitions present." );
279282 return true ;
280283 }
281284 if (classFilePath == null ) {
@@ -311,7 +314,7 @@ private byte[] transformTheWorld(
311314 location = codeSource .getLocation ();
312315 }
313316 }
314- log .debug (
317+ LOGGER .debug (
315318 "Parsing class '{}' {}B loaded from loader='{}' location={}" ,
316319 classFilePath ,
317320 classfileBuffer .length ,
@@ -320,7 +323,7 @@ private byte[] transformTheWorld(
320323 ClassNode classNode = parseClassFile (classFilePath , classfileBuffer );
321324 if (isClassLoaderRelated (classNode )) {
322325 // Skip ClassLoader classes
323- log .debug ("Skipping ClassLoader class: {}" , classFilePath );
326+ LOGGER .debug ("Skipping ClassLoader class: {}" , classFilePath );
324327 excludeClasses .add (classFilePath );
325328 return null ;
326329 }
@@ -337,10 +340,10 @@ private byte[] transformTheWorld(
337340 if (transformed ) {
338341 return writeClassFile (probes , loader , classFilePath , classNode );
339342 } else {
340- log .debug ("Class not transformed: {}" , classFilePath );
343+ LOGGER .debug ("Class not transformed: {}" , classFilePath );
341344 }
342345 } catch (Throwable ex ) {
343- log .warn ("Cannot transform: " , ex );
346+ LOGGER .warn ("Cannot transform: " , ex );
344347 writeToInstrumentationLog (classFilePath );
345348 }
346349 return null ;
@@ -354,7 +357,7 @@ private boolean isMethodIncludedForTransformation(
354357 }
355358 String fqnMethod = classNode .name + "::" + methodNode .name ;
356359 if (excludeMethods .contains (fqnMethod )) {
357- log .debug ("Skipping method: {}" , fqnMethod );
360+ LOGGER .debug ("Skipping method: {}" , fqnMethod );
358361 return false ;
359362 }
360363 return methodNames .add (methodNode .name );
@@ -404,7 +407,7 @@ private synchronized void writeToInstrumentationLog(String classFilePath) {
404407 writer .write (classFilePath );
405408 writer .write ("\n " );
406409 } catch (Exception ex ) {
407- log .warn ("Cannot write to instrumentation.log" , ex );
410+ LOGGER .warn ("Cannot write to instrumentation.log" , ex );
408411 }
409412 }
410413
@@ -443,7 +446,7 @@ private boolean isIncludedForTransformation(String classFilePath) {
443446 private boolean instrumentationIsAllowed (
444447 String fullyQualifiedClassName , List <ProbeDefinition > definitions ) {
445448 if (denyListHelper .isDenied (fullyQualifiedClassName )) {
446- log . info ("Instrumentation denied for {}" , fullyQualifiedClassName );
449+ LOGGER . debug ("Instrumentation denied for {}" , fullyQualifiedClassName );
447450 InstrumentationResult result =
448451 InstrumentationResult .Factory .blocked (
449452 fullyQualifiedClassName ,
@@ -455,7 +458,7 @@ private boolean instrumentationIsAllowed(
455458 return false ;
456459 }
457460 if (!allowListHelper .isAllowAll () && !allowListHelper .isAllowed (fullyQualifiedClassName )) {
458- log . info ("Instrumentation not allowed for {}" , fullyQualifiedClassName );
461+ LOGGER . debug ("Instrumentation not allowed for {}" , fullyQualifiedClassName );
459462 InstrumentationResult result =
460463 InstrumentationResult .Factory .blocked (
461464 fullyQualifiedClassName ,
@@ -487,11 +490,12 @@ private byte[] writeClassFile(
487490 classNode .version = Opcodes .V1_8 ;
488491 }
489492 ClassWriter writer = new SafeClassWriter (loader );
490- log .debug ("Generating bytecode for class: {}" , Strings .getClassName (classFilePath ));
493+ ClassVisitor visitor = new JsrInliningClassVisitor (writer );
494+ LOGGER .debug ("Generating bytecode for class: {}" , Strings .getClassName (classFilePath ));
491495 try {
492- classNode .accept (writer );
496+ classNode .accept (visitor );
493497 } catch (Throwable t ) {
494- log .error ("Cannot write classfile for class: {} Exception: " , classFilePath , t );
498+ LOGGER .error ("Cannot write classfile for class: {} Exception: " , classFilePath , t );
495499 reportInstrumentationFails (definitions , Strings .getClassName (classFilePath ));
496500 return null ;
497501 }
@@ -526,8 +530,8 @@ private void verifyByteCode(String classFilePath, byte[] classFile) {
526530 printWriter .flush ();
527531 String result = stringWriter .toString ();
528532 if (!result .isEmpty ()) {
529- log .warn ("Verification of instrumented class {} failed" , classFilePath );
530- log .debug ("Verify result: {}" , stringWriter );
533+ LOGGER .warn ("Verification of instrumented class {} failed" , classFilePath );
534+ LOGGER .debug ("Verify result: {}" , stringWriter );
531535 throw new RuntimeException ("Generated bytecode is invalid for " + classFilePath );
532536 }
533537 }
@@ -560,9 +564,9 @@ private boolean performInstrumentation(
560564 if (matchingDefs .isEmpty ()) {
561565 continue ;
562566 }
563- if (log .isDebugEnabled ()) {
567+ if (LOGGER .isDebugEnabled ()) {
564568 List <String > probeIds = matchingDefs .stream ().map (ProbeDefinition ::getId ).collect (toList ());
565- log .debug (
569+ LOGGER .debug (
566570 "Instrumenting method: {}.{}{} for probe ids: {}" ,
567571 fullyQualifiedClassName ,
568572 methodNode .name ,
@@ -627,7 +631,7 @@ private void reportErrorForAllProbes(List<ProbeDefinition> definitions, String m
627631 private void addDiagnostics (
628632 ProbeDefinition definition , List <DiagnosticMessage > diagnosticMessages ) {
629633 debuggerSink .addDiagnostics (definition .getProbeId (), diagnosticMessages );
630- log .debug ("Diagnostic messages for definition[{}]: {}" , definition , diagnosticMessages );
634+ LOGGER .debug ("Diagnostic messages for definition[{}]: {}" , definition , diagnosticMessages );
631635 }
632636
633637 private void notifyBlockedDefinitions (
@@ -658,7 +662,7 @@ private InstrumentationResult applyInstrumentation(
658662 status = definition .instrument (methodInfo , probeDiagnostics , toInstrumentInfo .probeIds );
659663 }
660664 } catch (Throwable t ) {
661- log .warn ("Exception during instrumentation: " , t );
665+ LOGGER .warn ("Exception during instrumentation: " , t );
662666 status = InstrumentationResult .Status .ERROR ;
663667 addDiagnosticForAllProbes (
664668 new DiagnosticMessage (DiagnosticMessage .Kind .ERROR , t ), diagnostics );
@@ -694,7 +698,7 @@ private List<ToInstrumentInfo> filterAndSortDefinitions(
694698 // and therefore need to be instrumented once
695699 // note: exception probes are log probes and are handled the same way
696700 if (!Config .get ().isDistributedDebuggerEnabled () && definition instanceof TriggerProbe ) {
697- log .debug (
701+ LOGGER .debug (
698702 "The distributed debugger feature is disabled. Trigger probes will not be installed." );
699703 } else if (isCapturedContextProbe (definition )) {
700704 if (definition .isLineProbe ()) {
@@ -871,7 +875,7 @@ private List<MethodNode> matchMethodDescription(
871875 }
872876 }
873877 } catch (Exception ex ) {
874- log .warn ("Cannot match method: {}" , ex .toString ());
878+ LOGGER .warn ("Cannot match method: {}" , ex .toString ());
875879 }
876880 return result ;
877881 }
@@ -888,22 +892,22 @@ private MethodNode matchSourceFile(
888892 if (matchingMethods != null ) {
889893 matchingMethods .forEach (
890894 methodNode -> {
891- log .debug ("Found lineNode {} method: {}" , matchingLine , methodNode .name );
895+ LOGGER .debug ("Found lineNode {} method: {}" , matchingLine , methodNode .name );
892896 });
893897 // pick the first matching method.
894898 // TODO need a way to disambiguate if multiple methods match the same line
895899 return matchingMethods .isEmpty () ? null : matchingMethods .get (0 );
896900 }
897- log .debug ("Cannot find line: {} in class {}" , matchingLine , classNode .name );
901+ LOGGER .debug ("Cannot find line: {} in class {}" , matchingLine , classNode .name );
898902 return null ;
899903 }
900904
901905 private void dumpInstrumentedClassFile (String className , byte [] data ) {
902906 if (config .isDynamicInstrumentationClassFileDumpEnabled ()) {
903- log .debug ("Generated bytecode len: {}" , data .length );
907+ LOGGER .debug ("Generated bytecode len: {}" , data .length );
904908 Path classFilePath = dumpClassFile (className , data );
905909 if (classFilePath != null ) {
906- log .debug ("Instrumented class saved as: {}" , classFilePath .toString ());
910+ LOGGER .debug ("Instrumented class saved as: {}" , classFilePath .toString ());
907911 }
908912 }
909913 }
@@ -912,7 +916,7 @@ private void dumpOriginalClassFile(String className, byte[] classfileBuffer) {
912916 if (config .isDynamicInstrumentationClassFileDumpEnabled ()) {
913917 Path classFilePath = dumpClassFile (className + "_orig" , classfileBuffer );
914918 if (classFilePath != null ) {
915- log .debug ("Original class saved as: {}" , classFilePath .toString ());
919+ LOGGER .debug ("Original class saved as: {}" , classFilePath .toString ());
916920 }
917921 }
918922 }
@@ -924,11 +928,31 @@ private static Path dumpClassFile(String className, byte[] classfileBuffer) {
924928 Files .write (classFilePath , classfileBuffer , StandardOpenOption .CREATE );
925929 return classFilePath ;
926930 } catch (IOException e ) {
927- log .error ("" , e );
931+ LOGGER .error ("" , e );
928932 return null ;
929933 }
930934 }
931935
936+ /**
937+ * A {@link org.objectweb.asm.ClassVisitor} that uses {@link
938+ * org.objectweb.asm.commons.JSRInlinerAdapter} to remove JSR instructions and inlines the
939+ * referenced subroutines. This allows pre-Java 6 classes with finally blocks to be successfully
940+ * transformed. Without this an IllegalArgumentException for "JSR/RET are not supported with
941+ * computeFrames option" would be thrown when writing the transformed class.
942+ */
943+ static class JsrInliningClassVisitor extends ClassVisitor {
944+ protected JsrInliningClassVisitor (ClassVisitor parent ) {
945+ super (Opcodes .ASM9 , parent );
946+ }
947+
948+ @ Override
949+ public MethodVisitor visitMethod (
950+ int access , String name , String descriptor , String signature , String [] exceptions ) {
951+ MethodVisitor mv = super .visitMethod (access , name , descriptor , signature , exceptions );
952+ return new JSRInlinerAdapter (mv , access , name , descriptor , signature , exceptions );
953+ }
954+ }
955+
932956 static class SafeClassWriter extends ClassWriter {
933957 private final ClassLoader classLoader ;
934958
@@ -981,7 +1005,7 @@ protected String getCommonSuperClass(String type1, String type2) {
9811005 }
9821006 return common .getInternalName ();
9831007 } catch (Exception ex ) {
984- ExceptionHelper .logException (log , ex , "getCommonSuperClass failed: " );
1008+ ExceptionHelper .logException (LOGGER , ex , "getCommonSuperClass failed: " );
9851009 return tpDatadogClassLoader .describe ("java.lang.Object" ).resolve ().getInternalName ();
9861010 }
9871011 }
0 commit comments