Skip to content

Commit 2c7245b

Browse files
committed
Improve bytebuddy reliability with improved advice loading
Based on raphw/byte-buddy#218 (comment) this ensures that the advice class can be inlined without being loaded (and thus its dependencies being loaded) from the bytebuddy classloader. In some cases that can cause problems (akka SBT demo) and this resolves those.
1 parent 27c587d commit 2c7245b

14 files changed

+91
-72
lines changed

src/main/kotlin/tech/httptoolkit/javaagent/AgentMain.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ package tech.httptoolkit.javaagent
44

55
import net.bytebuddy.ByteBuddy
66
import net.bytebuddy.agent.builder.AgentBuilder
7+
import net.bytebuddy.asm.Advice
78
import net.bytebuddy.description.type.TypeDescription
9+
import net.bytebuddy.dynamic.ClassFileLocator
810
import net.bytebuddy.dynamic.DynamicType
911
import net.bytebuddy.dynamic.scaffold.TypeValidation
1012
import net.bytebuddy.matcher.ElementMatchers.none
13+
import net.bytebuddy.pool.TypePool
1114
import net.bytebuddy.utility.JavaModule
1215
import java.lang.instrument.Instrumentation
1316
import javax.net.ssl.SSLContext
1417
import java.net.*
1518
import javax.net.ssl.HttpsURLConnection
16-
import javax.net.ssl.SSLSocketFactory
1719
import javax.net.ssl.TrustManagerFactory
1820

1921

@@ -119,7 +121,7 @@ fun interceptAllHttps(config: Config, instrumentation: Instrumentation) {
119121

120122
abstract class MatchingAgentTransformer(private val logger: TransformationLogger) : AgentBuilder.Transformer {
121123
abstract fun register(builder: AgentBuilder): AgentBuilder
122-
abstract fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*>
124+
abstract fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*>
123125

124126
override fun transform(
125127
builder: DynamicType.Builder<*>,
@@ -128,7 +130,18 @@ abstract class MatchingAgentTransformer(private val logger: TransformationLogger
128130
module: JavaModule?
129131
): DynamicType.Builder<*> {
130132
logger.beforeTransformation(typeDescription)
131-
return transform(builder)
133+
134+
return transform(builder) { adviceName ->
135+
val locator = if (classLoader != null) {
136+
ClassFileLocator.Compound(
137+
ClassFileLocator.ForClassLoader.of(classLoader),
138+
ClassFileLocator.ForClassLoader.of(ByteBuddy::class.java.classLoader)
139+
)
140+
} else {
141+
ClassFileLocator.ForClassLoader.of(ByteBuddy::class.java.classLoader)
142+
}
143+
Advice.to(TypePool.Default.of(locator).describe(adviceName).resolve(), locator)
144+
}
132145
}
133146
}
134147

src/main/kotlin/tech/httptoolkit/javaagent/AkkaClientTransformers.kt

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package tech.httptoolkit.javaagent
22

3+
import net.bytebuddy.ByteBuddy
34
import net.bytebuddy.agent.builder.AgentBuilder
45
import net.bytebuddy.asm.Advice
6+
import net.bytebuddy.dynamic.ClassFileLocator
57
import net.bytebuddy.dynamic.DynamicType
68
import net.bytebuddy.matcher.ElementMatchers.*
9+
import net.bytebuddy.pool.TypePool
710
import tech.httptoolkit.javaagent.advice.akka.*
811

912
// First, we hook outgoing connection creation, and ensure that new connections always go via the proxy & trust us:
@@ -14,10 +17,10 @@ class AkkaHttpTransformer(logger: TransformationLogger) : MatchingAgentTransform
1417
.transform(this)
1518
}
1619

17-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
20+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
1821
return builder
1922
.visit(
20-
Advice.to(OverrideHttpSettingsAdvice::class.java)
23+
loadAdvice("tech.httptoolkit.javaagent.advice.akka.OverrideHttpSettingsAdvice")
2124
.on(hasMethodName("_outgoingConnection"))
2225
)
2326
}
@@ -33,10 +36,10 @@ class AkkaPoolSettingsTransformer(logger: TransformationLogger) : MatchingAgentT
3336
.transform(this)
3437
}
3538

36-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
39+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
3740
return builder
3841
.visit(
39-
Advice.to(ResetPoolSetupAdvice::class.java)
42+
loadAdvice("tech.httptoolkit.javaagent.advice.akka.ResetPoolSetupAdvice")
4043
.on(isConstructor())
4144
)
4245
}
@@ -51,10 +54,10 @@ class AkkaPoolTransformer(logger: TransformationLogger) : MatchingAgentTransform
5154
.transform(this)
5255
}
5356

54-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
57+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
5558
return builder
5659
.visit(
57-
Advice.to(ResetOldPoolsAdvice::class.java)
60+
loadAdvice("tech.httptoolkit.javaagent.advice.akka.ResetOldPoolsAdvice")
5861
.on(hasMethodName("dispatchRequest"))
5962
)
6063
}
@@ -68,10 +71,10 @@ class AkkaGatewayTransformer(logger: TransformationLogger) : MatchingAgentTransf
6871
.transform(this)
6972
}
7073

71-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
74+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
7275
return builder
7376
.visit(
74-
Advice.to(ResetOldGatewaysAdvice::class.java)
77+
loadAdvice("tech.httptoolkit.javaagent.advice.akka.ResetOldGatewaysAdvice")
7578
.on(hasMethodName("apply"))
7679
)
7780
}

src/main/kotlin/tech/httptoolkit/javaagent/ApacheAsyncClientTransformer.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ class ApacheClientTlsStrategyTransformer(logger: TransformationLogger) : Matchin
3737
).transform(this)
3838
}
3939

40-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
40+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
4141
return builder
4242
.visit(
43-
Advice.to(OverrideSslContextFieldAdvice::class.java)
43+
loadAdvice("tech.httptoolkit.javaagent.advice.OverrideSslContextFieldAdvice")
4444
.on(hasMethodName("upgrade"))
4545
)
4646
}

src/main/kotlin/tech/httptoolkit/javaagent/ApacheClientTransformers.kt

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ class ApacheClientRoutingV4Transformer(logger: TransformationLogger) : MatchingA
1919
).transform(this)
2020
}
2121

22-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
22+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
2323
return builder.visit(
24-
Advice.to(ApacheV4ReturnProxyRouteAdvice::class.java)
24+
loadAdvice("tech.httptoolkit.javaagent.advice.apacheclient.ApacheV4ReturnProxyRouteAdvice")
2525
.on(hasMethodName("determineRoute"))
2626
)
2727
}
@@ -36,9 +36,9 @@ class ApacheClientRoutingV5Transformer(logger: TransformationLogger) : MatchingA
3636
).transform(this)
3737
}
3838

39-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
39+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
4040
return builder.visit(
41-
Advice.to(ApacheV5ReturnProxyRouteAdvice::class.java)
41+
loadAdvice("tech.httptoolkit.javaagent.advice.apacheclient.ApacheV5ReturnProxyRouteAdvice")
4242
.on(hasMethodName("determineRoute"))
4343
)
4444
}
@@ -59,11 +59,11 @@ class ApacheSslSocketFactoryTransformer(logger: TransformationLogger) : Matching
5959
).transform(this)
6060
}
6161

62-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
62+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
6363
return builder
6464
.visit(
65-
Advice.to(ApacheSetSslSocketFactoryAdvice::class.java)
66-
.on(hasMethodName("createLayeredSocket"))
65+
loadAdvice("tech.httptoolkit.javaagent.advice.apacheclient.ApacheSetSslSocketFactoryAdvice")
66+
.on(hasMethodName("createLayeredSocket"))
6767
);
6868
}
6969
}
@@ -80,13 +80,13 @@ class ApacheHostConfigurationTransformer(logger: TransformationLogger) : Matchin
8080
).transform(this)
8181
}
8282

83-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
83+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
8484
return builder
8585
// Override the proxy field value for all new configurations, and for any attempts to call
8686
// setProxy/ProxyHost. We don't no-op these, because we want to call them ourselves later on
8787
// existing configs to reset them - we don't just want to ignore this.
8888
.visit(
89-
Advice.to(ApacheOverrideProxyHostFieldAdvice::class.java)
89+
loadAdvice("tech.httptoolkit.javaagent.advice.apacheclient.ApacheOverrideProxyHostFieldAdvice")
9090
.on(isConstructor<MethodDescription>()
9191
.or(hasMethodName("setProxy"))
9292
.or(hasMethodName("setProxyHost"))
@@ -107,10 +107,10 @@ class ApacheHttpMethodDirectorTransformer(logger: TransformationLogger) : Matchi
107107
).transform(this)
108108
}
109109

110-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
110+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
111111
return builder
112112
.visit(
113-
Advice.to(ApacheSetConfigProxyHostAdvice::class.java)
113+
loadAdvice("tech.httptoolkit.javaagent.advice.apacheclient.ApacheSetConfigProxyHostAdvice")
114114
.on(hasMethodName("executeMethod"))
115115
)
116116
}
@@ -127,10 +127,10 @@ class ApacheProtocolTransformer(logger: TransformationLogger) : MatchingAgentTra
127127
).transform(this)
128128
}
129129

130-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
130+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
131131
return builder
132132
.visit(
133-
Advice.to(ApacheReturnCustomSslProtocolSocketFactoryAdvice::class.java)
133+
loadAdvice("tech.httptoolkit.javaagent.advice.apacheclient.ApacheReturnCustomSslProtocolSocketFactoryAdvice")
134134
.on(hasMethodName("getSocketFactory"))
135135
)
136136
}

src/main/kotlin/tech/httptoolkit/javaagent/AsyncHttpClientConfigTransformers.kt

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ class AsyncHttpClientConfigTransformer(logger: TransformationLogger) : MatchingA
2020
).transform(this)
2121
}
2222

23-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
23+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
2424
return builder
25-
.visit(Advice.to(AsyncHttpClientReturnSslContextAdvice::class.java)
26-
.on(hasMethodName("getSslContext")))
27-
.visit(Advice.to(AsyncHttpClientReturnProxySelectorAdvice::class.java)
28-
.on(hasMethodName("getProxyServerSelector")))
25+
.visit(
26+
loadAdvice("tech.httptoolkit.javaagent.advice.asynchttpclient.AsyncHttpClientReturnSslContextAdvice")
27+
.on(hasMethodName("getSslContext")))
28+
.visit(
29+
loadAdvice("tech.httptoolkit.javaagent.advice.asynchttpclient.AsyncHttpClientReturnProxySelectorAdvice")
30+
.on(hasMethodName("getProxyServerSelector")))
2931
}
3032
}
3133

@@ -39,9 +41,10 @@ class AsyncHttpChannelManagerTransformer(logger: TransformationLogger) : Matchin
3941
).transform(this)
4042
}
4143

42-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
44+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
4345
return builder
44-
.visit(Advice.to(AsyncHttpResetSslEngineFactoryAdvice::class.java)
45-
.on(hasMethodName("createSslHandler")))
46+
.visit(
47+
loadAdvice("tech.httptoolkit.javaagent.advice.asynchttpclient.AsyncHttpResetSslEngineFactoryAdvice")
48+
.on(hasMethodName("createSslHandler")))
4649
}
4750
}

src/main/kotlin/tech/httptoolkit/javaagent/HttpsUrlConnectionTransformer.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ class HttpsUrlConnectionTransformer(logger: TransformationLogger): MatchingAgent
1818
).transform(this)
1919
}
2020

21-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
21+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
2222
return builder
23-
.visit(Advice.to(ReturnSslSocketFactoryAdvice::class.java)
23+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.ReturnSslSocketFactoryAdvice")
2424
.on(hasMethodName("getSSLSocketFactory")))
2525
}
2626
}

src/main/kotlin/tech/httptoolkit/javaagent/JavaClientTransformer.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ class JavaClientTransformer(logger: TransformationLogger): MatchingAgentTransfor
1818
).transform(this)
1919
}
2020

21-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
21+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
2222
return builder
23-
.visit(Advice.to(ReturnProxySelectorAdvice::class.java)
23+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.ReturnProxySelectorAdvice")
2424
.on(hasMethodName("proxy")))
25-
.visit(Advice.to(ReturnSslContextAdvice::class.java)
25+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.ReturnSslContextAdvice")
2626
.on(hasMethodName("sslContext")))
27-
.visit(Advice.to(ReturnSslContextAdvice::class.java)
27+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.ReturnSslContextAdvice")
2828
.on(hasMethodName("theSSLContext")))
2929
}
3030
}

src/main/kotlin/tech/httptoolkit/javaagent/JettyClientTransformer.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,19 @@ class JettyClientTransformer(logger: TransformationLogger): MatchingAgentTransfo
3030
).transform(this)
3131
}
3232

33-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
33+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
3434
return builder
35-
.visit(Advice.to(JettyReturnProxyConfigurationAdvice::class.java)
35+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.jettyclient.JettyReturnProxyConfigurationAdvice")
3636
.on(hasMethodName("getProxyConfiguration")))
37-
.visit(Advice.to(JettyReturnSslContextFactoryV10Advice::class.java)
37+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.jettyclient.JettyReturnSslContextFactoryV10Advice")
3838
.on(hasMethodName<MethodDescription>("getSslContextFactory").and(
3939
returns(SslContextFactory.Client::class.java)
4040
)))
41-
.visit(Advice.to(JettyReturnSslContextFactoryV9Advice::class.java)
41+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.jettyclient.JettyReturnSslContextFactoryV9Advice")
4242
.on(hasMethodName<MethodDescription>("getSslContextFactory").and(
4343
returns(SslContextFactory::class.java)
4444
)))
45-
.visit(Advice.to(JettyResetDestinationsAdvice::class.java)
45+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.jettyclient.JettyResetDestinationsAdvice")
4646
.on(hasMethodName("resolveDestination")))
4747
}
4848
}

src/main/kotlin/tech/httptoolkit/javaagent/KtorCioTransformers.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ class KtorClientTlsTransformer(logger: TransformationLogger) : MatchingAgentTran
2020
).transform(this)
2121
}
2222

23-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
23+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
2424
return builder
25-
.visit(Advice.to(KtorResetTlsClientTrustAdvice::class.java)
25+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.ktor.KtorResetTlsClientTrustAdvice")
2626
.on(hasMethodName<MethodDescription>("openTLSSession")
2727
.and(takesArgument(3, named("io.ktor.network.tls.TLSConfig")))))
2828
}
@@ -38,9 +38,9 @@ class KtorClientEngineConfigTransformer(logger: TransformationLogger) : Matching
3838
).transform(this)
3939
}
4040

41-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
41+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
4242
return builder
43-
.visit(Advice.to(ReturnProxyAdvice::class.java)
43+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.ReturnProxyAdvice")
4444
.on(hasMethodName("getProxy")))
4545
}
4646
}
@@ -57,9 +57,9 @@ class KtorCioEngineTransformer(logger: TransformationLogger) : MatchingAgentTran
5757
).transform(this)
5858
}
5959

60-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
60+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
6161
return builder
62-
.visit(Advice.to(KtorResetProxyFieldAdvice::class.java)
62+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.ktor.KtorResetProxyFieldAdvice")
6363
.on(hasMethodName("execute")))
6464
}
6565
}

src/main/kotlin/tech/httptoolkit/javaagent/OkHttpClientTransformers.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ class OkHttpClientV3Transformer(logger: TransformationLogger): MatchingAgentTran
2525
).transform(this)
2626
}
2727

28-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
28+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
2929
return builder
3030
// v3 uses proxy() functions, while v4 uses Kotlin getters that compile to the same thing
31-
.visit(Advice.to(ReturnProxyAdvice::class.java)
31+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.ReturnProxyAdvice")
3232
.on(hasMethodName("proxy")))
3333
// This means we ignore client certs, but that's fine: we can't pass them through the proxy anyway. That
3434
// needs to be configured separately in the proxy's configuration.
35-
.visit(Advice.to(ReturnSslSocketFactoryAdvice::class.java)
35+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.ReturnSslSocketFactoryAdvice")
3636
.on(hasMethodName("sslSocketFactory")))
3737
}
3838
}
@@ -55,12 +55,12 @@ class OkHttpClientV2Transformer(logger: TransformationLogger): MatchingAgentTran
5555
).transform(this)
5656
}
5757

58-
override fun transform(builder: DynamicType.Builder<*>): DynamicType.Builder<*> {
58+
override fun transform(builder: DynamicType.Builder<*>, loadAdvice: (String) -> Advice): DynamicType.Builder<*> {
5959
return builder
6060
// v2 uses getX methods:
61-
.visit(Advice.to(ReturnProxyAdvice::class.java)
61+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.ReturnProxyAdvice")
6262
.on(hasMethodName("getProxy")))
63-
.visit(Advice.to(ReturnSslSocketFactoryAdvice::class.java)
63+
.visit(loadAdvice("tech.httptoolkit.javaagent.advice.ReturnSslSocketFactoryAdvice")
6464
.on(hasMethodName("getSslSocketFactory")))
6565
}
6666
}

0 commit comments

Comments
 (0)