77import net .bytebuddy .asm .Advice .ExceptionHandler ;
88import net .bytebuddy .implementation .Implementation ;
99import net .bytebuddy .implementation .bytecode .StackManipulation ;
10+ import net .bytebuddy .implementation .bytecode .constant .TextConstant ;
1011import net .bytebuddy .jar .asm .Label ;
1112import net .bytebuddy .jar .asm .MethodVisitor ;
1213import net .bytebuddy .jar .asm .Opcodes ;
@@ -20,131 +21,155 @@ public class ExceptionHandlers {
2021 // Bootstrap ExceptionHandler.class will always be resolvable, so we'll use it in the log name
2122 private static final String HANDLER_NAME = ExceptionLogger .class .getName ().replace ('.' , '/' );
2223
23- private static final ExceptionHandler EXCEPTION_STACK_HANDLER =
24- new ExceptionHandler .Simple (
25- new StackManipulation () {
26- // Pops one Throwable off the stack. Maxes the stack to at least 3.
27- private final Size size = new StackManipulation .Size (-1 , 3 );
28- private final boolean appSecEnabled =
29- InstrumenterConfig .get ().getAppSecActivation () != ProductActivation .FULLY_DISABLED ;
30- private final boolean detailedErrors =
31- InstrumenterConfig .get ().isDetailedInstrumentationErrors ();
32-
33- @ Override
34- public boolean isValid () {
35- return true ;
36- }
37-
38- @ Override
39- public Size apply (final MethodVisitor mv , final Implementation .Context context ) {
40- final String name = context .getInstrumentedType ().getName ();
41- final boolean exitOnFailure = InstrumenterConfig .get ().isInternalExitOnFailure ();
42- final String logMethod = exitOnFailure ? "error" : "debug" ;
43-
44- // Writes the following bytecode if exitOnFailure is false:
45- //
46- // BlockingExceptionHandler.rethrowIfBlockingException(t);
47- // try {
48- // InstrumentationErrors.recordError();
49- // org.slf4j.LoggerFactory.getLogger((Class)ExceptionLogger.class)
50- // .debug("Failed to handle exception in instrumentation for ...", t);
51- // } catch (Throwable t2) {
52- // }
53- //
54- // And the following bytecode if exitOnFailure is true:
55- //
56- // BlockingExceptionHandler.rethrowIfBlockingException(t);
57- // try {
58- // InstrumentationErrors.recordError();
59- // org.slf4j.LoggerFactory.getLogger((Class)ExceptionLogger.class)
60- // .error("Failed to handle exception in instrumentation for ...", t);
61- // System.exit(1);
62- // } catch (Throwable t2) {
63- // }
64- //
65- final Label logStart = new Label ();
66- final Label logEnd = new Label ();
67- final Label eatException = new Label ();
68- final Label handlerExit = new Label ();
69-
70- // Frames are only meaningful for class files in version 6 or later.
71- final boolean frames =
72- context .getClassFileVersion ().isAtLeast (ClassFileVersion .JAVA_V6 );
73-
74- if (appSecEnabled ) {
75- // rethrow blocking exceptions
76- // stack: (top) throwable
77- mv .visitMethodInsn (
78- Opcodes .INVOKESTATIC ,
79- "datadog/trace/bootstrap/blocking/BlockingExceptionHandler" ,
80- "rethrowIfBlockingException" ,
81- "(Ljava/lang/Throwable;)Ljava/lang/Throwable;" ,
82- false );
83- }
84-
85- mv .visitTryCatchBlock (logStart , logEnd , eatException , "java/lang/Throwable" );
86- mv .visitLabel (logStart );
87- // record instrumentation error
88- if (detailedErrors ) {
89- mv .visitMethodInsn (
90- Opcodes .INVOKESTATIC ,
91- "datadog/trace/bootstrap/InstrumentationErrors" ,
92- "recordError" ,
93- "(Ljava/lang/Throwable;)Ljava/lang/Throwable;" ,
94- false );
95- } else {
96- mv .visitMethodInsn (
97- Opcodes .INVOKESTATIC ,
98- "datadog/trace/bootstrap/InstrumentationErrors" ,
99- "recordError" ,
100- "()V" ,
101- false );
102- }
103- // stack: (top) throwable
104- mv .visitLdcInsn (Type .getType ("L" + HANDLER_NAME + ";" ));
105- mv .visitMethodInsn (
106- Opcodes .INVOKESTATIC ,
107- LOG_FACTORY_NAME ,
108- "getLogger" ,
109- "(Ljava/lang/Class;)L" + LOGGER_NAME + ";" ,
110- false );
111- mv .visitInsn (Opcodes .SWAP ); // stack: (top) throwable,logger
112- mv .visitLdcInsn ("Failed to handle exception in instrumentation for " + name );
113- mv .visitInsn (Opcodes .SWAP ); // stack: (top) throwable,string,logger
114- mv .visitMethodInsn (
115- Opcodes .INVOKEINTERFACE ,
116- LOGGER_NAME ,
117- logMethod ,
118- "(Ljava/lang/String;Ljava/lang/Throwable;)V" ,
119- true );
120-
121- if (exitOnFailure ) {
122- mv .visitInsn (Opcodes .ICONST_1 );
123- mv .visitMethodInsn (Opcodes .INVOKESTATIC , "java/lang/System" , "exit" , "(I)V" , false );
124- }
125- mv .visitLabel (logEnd );
126- mv .visitJumpInsn (Opcodes .GOTO , handlerExit );
127-
128- // if the runtime can't reach our ExceptionHandler or logger,
129- // silently eat the exception
130- mv .visitLabel (eatException );
131- if (frames ) {
132- mv .visitFrame (Opcodes .F_SAME1 , 0 , null , 1 , new Object [] {"java/lang/Throwable" });
133- }
134- mv .visitInsn (Opcodes .POP );
135- // mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
136- // "printStackTrace", "()V", false);
137-
138- mv .visitLabel (handlerExit );
139- if (frames ) {
140- mv .visitFrame (Opcodes .F_SAME , 0 , null , 0 , null );
141- }
142-
143- return size ;
144- }
145- });
146-
147- public static ExceptionHandler defaultExceptionHandler () {
148- return EXCEPTION_STACK_HANDLER ;
24+ // Shared bytecode that turns the [throwable, adviceName] stack left by the TextConstant
25+ // prefix into a logged + optionally swallowed exception.
26+ private static final StackManipulation EXCEPTION_STACK_MANIPULATION =
27+ new StackManipulation () {
28+ // Pops the throwable and the advice-name String off the stack.
29+ // Peak growth above the pair on entry is +2 (during message-building LDCs).
30+ private final Size size = new StackManipulation .Size (-2 , 2 );
31+ private final boolean appSecEnabled =
32+ InstrumenterConfig .get ().getAppSecActivation () != ProductActivation .FULLY_DISABLED ;
33+ private final boolean detailedErrors =
34+ InstrumenterConfig .get ().isDetailedInstrumentationErrors ();
35+
36+ @ Override
37+ public boolean isValid () {
38+ return true ;
39+ }
40+
41+ @ Override
42+ public Size apply (final MethodVisitor mv , final Implementation .Context context ) {
43+ final String instrumentedTypeName = context .getInstrumentedType ().getName ();
44+ final boolean exitOnFailure = InstrumenterConfig .get ().isInternalExitOnFailure ();
45+ final String logMethod = exitOnFailure ? "error" : "debug" ;
46+
47+ // On entry the stack is: (bottom) throwable, adviceName (top)
48+ // — adviceName was pushed by the preceding TextConstant.
49+ //
50+ // Emits the following Java-equivalent code when exitOnFailure is false:
51+ //
52+ // BlockingExceptionHandler.rethrowIfBlockingException(t);
53+ // try {
54+ // InstrumentationErrors.recordError();
55+ // org.slf4j.LoggerFactory.getLogger((Class) ExceptionLogger.class)
56+ // .debug("Failed to handle exception in instrumentation for <type> (" + adviceName +
57+ // ")", t);
58+ // } catch (Throwable t2) {
59+ // }
60+ //
61+ // and the same with .error(...) followed by System.exit(1) when exitOnFailure is true.
62+ final Label logStart = new Label ();
63+ final Label logEnd = new Label ();
64+ final Label eatException = new Label ();
65+ final Label handlerExit = new Label ();
66+
67+ // Frames are only meaningful for class files in version 6 or later.
68+ final boolean frames = context .getClassFileVersion ().isAtLeast (ClassFileVersion .JAVA_V6 );
69+
70+ if (appSecEnabled ) {
71+ // Need throwable on top for rethrowIfBlockingException.
72+ // stack: (top) adviceName, throwable -> top throwable
73+ mv .visitInsn (Opcodes .SWAP );
74+ mv .visitMethodInsn (
75+ Opcodes .INVOKESTATIC ,
76+ "datadog/trace/bootstrap/blocking/BlockingExceptionHandler" ,
77+ "rethrowIfBlockingException" ,
78+ "(Ljava/lang/Throwable;)Ljava/lang/Throwable;" ,
79+ false );
80+ // restore: (top) throwable, adviceName -> top adviceName
81+ mv .visitInsn (Opcodes .SWAP );
82+ }
83+
84+ mv .visitTryCatchBlock (logStart , logEnd , eatException , "java/lang/Throwable" );
85+ mv .visitLabel (logStart );
86+ // record instrumentation error
87+ if (detailedErrors ) {
88+ // recordError(Throwable) needs throwable on top, then we restore.
89+ mv .visitInsn (Opcodes .SWAP );
90+ mv .visitMethodInsn (
91+ Opcodes .INVOKESTATIC ,
92+ "datadog/trace/bootstrap/InstrumentationErrors" ,
93+ "recordError" ,
94+ "(Ljava/lang/Throwable;)Ljava/lang/Throwable;" ,
95+ false );
96+ mv .visitInsn (Opcodes .SWAP );
97+ } else {
98+ mv .visitMethodInsn (
99+ Opcodes .INVOKESTATIC ,
100+ "datadog/trace/bootstrap/InstrumentationErrors" ,
101+ "recordError" ,
102+ "()V" ,
103+ false );
104+ }
105+
106+ // Build the log message:
107+ // "Failed to handle exception in instrumentation for <type> - " + adviceName
108+ // <type> is a generation-time constant baked into the prefix LDC.
109+ // stack: (top) adviceName, throwable
110+ mv .visitLdcInsn (
111+ "Failed to handle exception in instrumentation for " + instrumentedTypeName + " - " );
112+ // stack: prefix (top), adviceName, throwable
113+ mv .visitInsn (Opcodes .SWAP );
114+ // stack: adviceName (top), prefix, throwable
115+ mv .visitMethodInsn (
116+ Opcodes .INVOKEVIRTUAL ,
117+ "java/lang/String" ,
118+ "concat" ,
119+ "(Ljava/lang/String;)Ljava/lang/String;" ,
120+ false );
121+ // stack: message (top), throwable
122+ mv .visitInsn (Opcodes .SWAP );
123+ // stack: throwable(top), message
124+
125+ mv .visitLdcInsn (Type .getType ("L" + HANDLER_NAME + ";" ));
126+ mv .visitMethodInsn (
127+ Opcodes .INVOKESTATIC ,
128+ LOG_FACTORY_NAME ,
129+ "getLogger" ,
130+ "(Ljava/lang/Class;)L" + LOGGER_NAME + ";" ,
131+ false );
132+ // stack: logger (top), throwable, message
133+ mv .visitInsn (Opcodes .DUP_X2 );
134+ // stack: logger (top), throwable, message, logger
135+ mv .visitInsn (Opcodes .POP );
136+ // stack: throwable (top), message, logger
137+ mv .visitMethodInsn (
138+ Opcodes .INVOKEINTERFACE ,
139+ LOGGER_NAME ,
140+ logMethod ,
141+ "(Ljava/lang/String;Ljava/lang/Throwable;)V" ,
142+ true );
143+
144+ if (exitOnFailure ) {
145+ mv .visitInsn (Opcodes .ICONST_1 );
146+ mv .visitMethodInsn (Opcodes .INVOKESTATIC , "java/lang/System" , "exit" , "(I)V" , false );
147+ }
148+ mv .visitLabel (logEnd );
149+ mv .visitJumpInsn (Opcodes .GOTO , handlerExit );
150+
151+ // if the runtime can't reach our ExceptionHandler or logger,
152+ // silently eat the exception
153+ mv .visitLabel (eatException );
154+ if (frames ) {
155+ mv .visitFrame (Opcodes .F_SAME1 , 0 , null , 1 , new Object [] {"java/lang/Throwable" });
156+ }
157+ mv .visitInsn (Opcodes .POP );
158+ // mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
159+ // "printStackTrace", "()V", false);
160+
161+ mv .visitLabel (handlerExit );
162+ if (frames ) {
163+ mv .visitFrame (Opcodes .F_SAME , 0 , null , 0 , null );
164+ }
165+
166+ return size ;
167+ }
168+ };
169+
170+ public static ExceptionHandler exceptionHandlerFor (final String adviceClassName ) {
171+ return new ExceptionHandler .Simple (
172+ new StackManipulation .Compound (
173+ new TextConstant (adviceClassName ), EXCEPTION_STACK_MANIPULATION ));
149174 }
150175}
0 commit comments