Repository: 7mind/izumi
Pull request #1737 (2022-05-31 17:05:20)

Bytebuddy cogen (java 17)
All tests pass on Java 17.


Patch:
diff --git a/.jvmopts b/.jvmopts
index 78bfdd705..69d24bc5b 100644
--- a/.jvmopts
+++ b/.jvmopts
@@ -1,4 +1,4 @@
--Xmx3G
+-Xmx12G
 -XX:ReservedCodeCacheSize=256M
 -XX:MaxMetaspaceSize=2560M
 
diff --git a/build.sbt b/build.sbt
index 118c72c0d..6e1a88819 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1374,7 +1374,7 @@ lazy val `distage-core-api` = project.in(file("distage/distage-core-api"))
   )
   .disablePlugins(AssemblyPlugin)
 
-lazy val `distage-core-proxy-cglib` = project.in(file("distage/distage-core-proxy-cglib"))
+lazy val `distage-core-proxy-bytebuddy` = project.in(file("distage/distage-core-proxy-bytebuddy"))
   .dependsOn(
     `distage-core-api` % "test->compile;compile->compile"
   )
@@ -1383,7 +1383,7 @@ lazy val `distage-core-proxy-cglib` = project.in(file("distage/distage-core-prox
       compilerPlugin("org.typelevel" % "kind-projector" % V.kind_projector cross CrossVersion.full),
       "org.scala-lang.modules" %% "scala-collection-compat" % V.collection_compat,
       "org.scalatest" %% "scalatest" % V.scalatest % Test,
-      "cglib" % "cglib-nodep" % V.cglib_nodep
+      "net.bytebuddy" % "byte-buddy" % V.bytebuddy
     )
   )
   .settings(
@@ -1506,7 +1506,7 @@ lazy val `distage-core-proxy-cglib` = project.in(file("distage/distage-core-prox
 lazy val `distage-core` = project.in(file("distage/distage-core"))
   .dependsOn(
     `distage-core-api` % "test->compile;compile->compile",
-    `distage-core-proxy-cglib` % "test->compile;compile->compile"
+    `distage-core-proxy-bytebuddy` % "test->compile"
   )
   .settings(
     libraryDependencies ++= Seq(
@@ -3289,7 +3289,7 @@ lazy val `microsite` = project.in(file("doc/microsite"))
     `fundamentals-orphans` % "test->compile;compile->compile",
     `fundamentals-literals` % "test->compile;compile->compile",
     `distage-core-api` % "test->compile;compile->compile",
-    `distage-core-proxy-cglib` % "test->compile;compile->compile",
+    `distage-core-proxy-bytebuddy` % "test->compile;compile->compile",
     `distage-core` % "test->compile;compile->compile",
     `distage-extension-config` % "test->compile;compile->compile",
     `distage-extension-plugins` % "test->compile;compile->compile",
@@ -3681,7 +3681,7 @@ lazy val `distage` = (project in file(".agg/distage-distage"))
   .disablePlugins(AssemblyPlugin)
   .aggregate(
     `distage-core-api`,
-    `distage-core-proxy-cglib`,
+    `distage-core-proxy-bytebuddy`,
     `distage-core`,
     `distage-extension-config`,
     `distage-extension-plugins`,
@@ -3705,7 +3705,7 @@ lazy val `distage-jvm` = (project in file(".agg/distage-distage-jvm"))
   .disablePlugins(AssemblyPlugin)
   .aggregate(
     `distage-core-api`,
-    `distage-core-proxy-cglib`,
+    `distage-core-proxy-bytebuddy`,
     `distage-core`,
     `distage-extension-config`,
     `distage-extension-plugins`,
diff --git a/distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/exceptions/CgLibInstantiationOpException.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/exceptions/interpretation/ProxyInstantiationException.scala
similarity index 50%
rename from distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/exceptions/CgLibInstantiationOpException.scala
rename to distage/distage-core-api/src/main/scala/izumi/distage/model/exceptions/interpretation/ProxyInstantiationException.scala
index 45461a57f..7abdccdf5 100644
--- a/distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/exceptions/CgLibInstantiationOpException.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/exceptions/interpretation/ProxyInstantiationException.scala
@@ -1,8 +1,8 @@
-package izumi.distage.provisioning.strategies.cglib.exceptions
+package izumi.distage.model.exceptions.interpretation
 
 import izumi.distage.model.exceptions.DIException
 import izumi.distage.model.plan.ExecutableOp
 import izumi.distage.model.provisioning.proxies.ProxyProvider.ProxyParams
 
-class CgLibInstantiationOpException(message: String, val target: Class[?], val params: ProxyParams, val op: ExecutableOp, cause: Throwable)
+class ProxyInstantiationException(message: String, val target: Class[?], val params: ProxyParams, val op: ExecutableOp, cause: Throwable)
   extends DIException(message, cause)
diff --git a/distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/DistageProxy.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/proxies/DistageProxy.scala
similarity index 53%
rename from distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/DistageProxy.scala
rename to distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/proxies/DistageProxy.scala
index b3cfb6bf4..f0a00c1c5 100644
--- a/distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/DistageProxy.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/proxies/DistageProxy.scala
@@ -1,4 +1,4 @@
-package izumi.distage.provisioning.strategies.cglib
+package izumi.distage.model.provisioning.proxies
 
 trait DistageProxy {
   def _distageProxyReference: AnyRef
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/proxies/ProxyProvider.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/proxies/ProxyProvider.scala
index a8bb5a44c..99841284d 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/proxies/ProxyProvider.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/provisioning/proxies/ProxyProvider.scala
@@ -10,7 +10,7 @@ trait ProxyProvider {
 }
 
 object ProxyProvider {
-  class ProxyProviderFailingImpl extends ProxyProvider {
+  object ProxyProviderFailingImpl extends ProxyProvider {
     override def makeCycleProxy(deferredKey: DIKey, proxyContext: ProxyContext): DeferredInit = {
       throw new ProxyProviderFailingImplCalledException(s"ProxyProviderFailingImpl can't create cycle-breaking proxies, failed op: ${proxyContext.op}", this)
     }
diff --git a/distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/CglibAtomicRefDispatcher.scala b/distage/distage-core-proxy-bytebuddy/src/main/scala/izumi/distage/provisioning/strategies/bytebuddyproxy/ByteBuddyAtomicRefDispatcher.scala
similarity index 52%
rename from distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/CglibAtomicRefDispatcher.scala
rename to distage/distage-core-proxy-bytebuddy/src/main/scala/izumi/distage/provisioning/strategies/bytebuddyproxy/ByteBuddyAtomicRefDispatcher.scala
index d5d57be54..2b2936855 100644
--- a/distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/CglibAtomicRefDispatcher.scala
+++ b/distage/distage-core-proxy-bytebuddy/src/main/scala/izumi/distage/provisioning/strategies/bytebuddyproxy/ByteBuddyAtomicRefDispatcher.scala
@@ -1,34 +1,33 @@
-package izumi.distage.provisioning.strategies.cglib
-
-import java.lang.reflect.Method
+package izumi.distage.provisioning.strategies.bytebuddyproxy
 
+import izumi.distage.model.provisioning.proxies.DistageProxy
 import izumi.distage.model.provisioning.proxies.ProxyDispatcher.AtomicProxyDispatcher
-import net.sf.cglib.proxy.{MethodInterceptor, MethodProxy}
+
+import java.lang.reflect.{InvocationHandler, Method}
 
 // dynamic dispatching is not optimal, uhu
-private[distage] class CglibAtomicRefDispatcher(
+private[distage] class ByteBuddyAtomicRefDispatcher(
   nullProxy: AnyRef
 ) extends AtomicProxyDispatcher
-  with MethodInterceptor {
-
-  override def intercept(o: scala.Any, method: Method, objects: Array[AnyRef], methodProxy: MethodProxy): AnyRef = {
+  with InvocationHandler {
+  override def invoke(o: scala.Any, method: Method, objects: Array[AnyRef]): AnyRef = {
     val methodName = method.getName
     if (methodName == "equals" && (method.getParameterTypes sameElements Array(classOf[AnyRef]))) {
       objects.headOption match {
         case Some(r: DistageProxy) =>
-          Boolean.box(getRef() == r._distageProxyReference)
+          Boolean.box(getRef == r._distageProxyReference)
 
         case _ =>
-          method.invoke(getRef(), objects: _*)
+          method.invoke(getRef, objects: _*)
       }
     } else if (methodName == "_distageProxyReference" && method.getParameterCount == 0) {
-      getRef()
+      getRef
     } else {
-      method.invoke(getRef(), objects: _*)
+      method.invoke(getRef, objects: _*)
     }
   }
 
-  @inline private[this] final def getRef(): AnyRef = {
+  @inline private[this] final def getRef: AnyRef = {
     val value = reference.get()
     if (value ne null) value else nullProxy
   }
diff --git a/distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/CglibNullMethodInterceptor.scala b/distage/distage-core-proxy-bytebuddy/src/main/scala/izumi/distage/provisioning/strategies/bytebuddyproxy/ByteBuddyNullMethodInterceptor.scala
similarity index 56%
rename from distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/CglibNullMethodInterceptor.scala
rename to distage/distage-core-proxy-bytebuddy/src/main/scala/izumi/distage/provisioning/strategies/bytebuddyproxy/ByteBuddyNullMethodInterceptor.scala
index da1c6b657..58faffa65 100644
--- a/distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/CglibNullMethodInterceptor.scala
+++ b/distage/distage-core-proxy-bytebuddy/src/main/scala/izumi/distage/provisioning/strategies/bytebuddyproxy/ByteBuddyNullMethodInterceptor.scala
@@ -1,16 +1,15 @@
-package izumi.distage.provisioning.strategies.cglib
+package izumi.distage.provisioning.strategies.bytebuddyproxy
 
 import izumi.distage.model.exceptions.interpretation.MissingRefException
-
-import java.lang.reflect.Method
 import izumi.distage.model.reflection.DIKey
-import net.sf.cglib.proxy.{MethodInterceptor, MethodProxy}
+
+import java.lang.reflect.{InvocationHandler, Method}
 
 // we use this to be able to display something for uninitialized proxies
-private[distage] class CglibNullMethodInterceptor(
+private[distage] class ByteBuddyNullMethodInterceptor(
   key: DIKey
-) extends MethodInterceptor {
-  override def intercept(o: Any, method: Method, objects: Array[AnyRef], methodProxy: MethodProxy): AnyRef = {
+) extends InvocationHandler {
+  override def invoke(o: Any, method: Method, objects: Array[AnyRef]): AnyRef = {
     if (method.getName == "toString" && method.getParameterCount == 0) {
       s"__UninitializedProxy__:$key"
     } else {
diff --git a/distage/distage-core-proxy-bytebuddy/src/main/scala/izumi/distage/provisioning/strategies/dynamicproxy/DynamicProxyProvider.scala b/distage/distage-core-proxy-bytebuddy/src/main/scala/izumi/distage/provisioning/strategies/dynamicproxy/DynamicProxyProvider.scala
new file mode 100644
index 000000000..1b416809d
--- /dev/null
+++ b/distage/distage-core-proxy-bytebuddy/src/main/scala/izumi/distage/provisioning/strategies/dynamicproxy/DynamicProxyProvider.scala
@@ -0,0 +1,63 @@
+package izumi.distage.provisioning.strategies.dynamicproxy
+
+import izumi.distage.model.exceptions.interpretation.ProxyInstantiationException
+import izumi.distage.model.provisioning.proxies.ProxyProvider.ProxyParams.{Empty, Params}
+import izumi.distage.model.provisioning.proxies.ProxyProvider.{DeferredInit, ProxyContext}
+import izumi.distage.model.provisioning.proxies.{DistageProxy, ProxyProvider}
+import izumi.distage.model.reflection.DIKey
+import izumi.distage.provisioning.strategies.bytebuddyproxy.{ByteBuddyAtomicRefDispatcher, ByteBuddyNullMethodInterceptor}
+import net.bytebuddy.ByteBuddy
+import net.bytebuddy.dynamic.scaffold.TypeValidation
+import net.bytebuddy.implementation.InvocationHandlerAdapter
+import net.bytebuddy.matcher.{ElementMatcher, ElementMatchers}
+import izumi.fundamentals.platform.exceptions.IzThrowable.*
+import net.bytebuddy.description.method.MethodDescription
+
+import java.lang.reflect.InvocationHandler
+
+object DynamicProxyProvider extends ProxyProvider {
+
+  override def makeCycleProxy(deferredKey: DIKey, proxyContext: ProxyContext): DeferredInit = {
+    val nullDispatcher = new ByteBuddyNullMethodInterceptor(deferredKey)
+    val nullProxy = mkDynamic(nullDispatcher, proxyContext)
+
+    val realDispatcher = new ByteBuddyAtomicRefDispatcher(nullProxy)
+    val realProxy = mkDynamic(realDispatcher, proxyContext)
+
+    DeferredInit(realDispatcher, realProxy)
+  }
+
+  private def mkDynamic(dispatcher: InvocationHandler, proxyContext: ProxyContext): AnyRef = {
+    val clazz = proxyContext.runtimeClass.asInstanceOf[Class[AnyRef]]
+
+    val constructedProxyClass: Class[AnyRef] = new ByteBuddy()
+      .`with`(TypeValidation.DISABLED)
+      .subclass(clazz)
+      .method(ElementMatchers.isMethod.asInstanceOf[ElementMatcher[MethodDescription]])
+      .intercept(InvocationHandlerAdapter.of(dispatcher))
+      .implement(classOf[DistageProxy])
+      .make()
+      .load(clazz.getClassLoader)
+      .getLoaded.asInstanceOf[Class[AnyRef]]
+
+    try {
+      proxyContext.params match {
+        case Empty =>
+          constructedProxyClass.getDeclaredConstructor().newInstance()
+        case Params(types, values) =>
+          val c = constructedProxyClass.getDeclaredConstructor(types: _*)
+          c.newInstance(values.map(_.asInstanceOf[AnyRef]): _*)
+      }
+    } catch {
+      case f: Throwable =>
+        throw new ProxyInstantiationException(
+          s"Failed to instantiate class with CGLib, make sure you don't dereference proxied parameters in constructors: " +
+          s"class=${proxyContext.runtimeClass}, params=${proxyContext.params}, exception=${f.stackTrace}",
+          clazz,
+          proxyContext.params,
+          proxyContext.op,
+          f,
+        )
+    }
+  }
+}
diff --git a/distage/distage-core-proxy-cglib/src/test/scala/izumi/.keep b/distage/distage-core-proxy-bytebuddy/src/test/scala/izumi/.keep
similarity index 100%
rename from distage/distage-core-proxy-cglib/src/test/scala/izumi/.keep
rename to distage/distage-core-proxy-bytebuddy/src/test/scala/izumi/.keep
diff --git a/distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/CglibProxyProvider.scala b/distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/CglibProxyProvider.scala
deleted file mode 100644
index 304eec8ad..000000000
--- a/distage/distage-core-proxy-cglib/src/main/scala/izumi/distage/provisioning/strategies/cglib/CglibProxyProvider.scala
+++ /dev/null
@@ -1,59 +0,0 @@
-package izumi.distage.provisioning.strategies.cglib
-
-import izumi.distage.model.provisioning.proxies.ProxyProvider
-import izumi.distage.model.provisioning.proxies.ProxyProvider.ProxyParams.{Empty, Params}
-import izumi.distage.model.provisioning.proxies.ProxyProvider.{DeferredInit, ProxyContext}
-import izumi.distage.model.reflection.DIKey
-import izumi.distage.provisioning.strategies.cglib.exceptions.CgLibInstantiationOpException
-import izumi.fundamentals.platform.exceptions.IzThrowable._
-import net.sf.cglib.proxy.{Callback, Enhancer}
-
-class CglibProxyProvider extends ProxyProvider {
-
-  override def makeCycleProxy(deferredKey: DIKey, proxyContext: ProxyContext): DeferredInit = {
-    val nullDispatcher = new CglibNullMethodInterceptor(deferredKey)
-    val nullProxy = mkDynamic(nullDispatcher, proxyContext)
-
-    val realDispatcher = new CglibAtomicRefDispatcher(nullProxy)
-    val realProxy = mkDynamic(realDispatcher, proxyContext)
-
-    DeferredInit(realDispatcher, realProxy)
-  }
-
-  private def mkDynamic(dispatcher: Callback, proxyContext: ProxyContext): AnyRef = {
-    val clazz = proxyContext.runtimeClass
-
-    // Enhancer.setSuperclass is side-effectful, so we had to copypaste
-    val enhancer = new Enhancer()
-
-    if (clazz.isInterface) {
-      enhancer.setInterfaces(Array[Class[?]](clazz, classOf[DistageProxy]))
-    } else if (clazz == classOf[Any]) {
-      enhancer.setInterfaces(Array(classOf[DistageProxy]))
-    } else {
-      enhancer.setSuperclass(clazz)
-      enhancer.setInterfaces(Array(classOf[DistageProxy]))
-    }
-
-    enhancer.setCallback(dispatcher)
-
-    try {
-      proxyContext.params match {
-        case Empty =>
-          enhancer.create()
-        case Params(types, values) =>
-          enhancer.create(types, values.asInstanceOf[Array[Object]])
-      }
-    } catch {
-      case f: Throwable =>
-        throw new CgLibInstantiationOpException(
-          s"Failed to instantiate class with CGLib, make sure you don't dereference proxied parameters in constructors: " +
-          s"class=${proxyContext.runtimeClass}, params=${proxyContext.params}, exception=${f.stackTrace}",
-          clazz,
-          proxyContext.params,
-          proxyContext.op,
-          f,
-        )
-    }
-  }
-}
diff --git a/distage/distage-core/.js/src/main/scala/izumi/distage/bootstrap/CglibBootstrap.scala b/distage/distage-core/.js/src/main/scala/izumi/distage/bootstrap/CglibBootstrap.scala
deleted file mode 100644
index cc187d278..000000000
--- a/distage/distage-core/.js/src/main/scala/izumi/distage/bootstrap/CglibBootstrap.scala
+++ /dev/null
@@ -1,7 +0,0 @@
-package izumi.distage.bootstrap
-
-import izumi.distage.model.provisioning.proxies.ProxyProvider.ProxyProviderFailingImpl
-
-object CglibBootstrap {
-  type CglibProxyProvider = ProxyProviderFailingImpl
-}
diff --git a/distage/distage-core/.js/src/main/scala/izumi/distage/bootstrap/DynamicProxyBootstrap.scala b/distage/distage-core/.js/src/main/scala/izumi/distage/bootstrap/DynamicProxyBootstrap.scala
new file mode 100644
index 000000000..c7915f3d4
--- /dev/null
+++ b/distage/distage-core/.js/src/main/scala/izumi/distage/bootstrap/DynamicProxyBootstrap.scala
@@ -0,0 +1,8 @@
+package izumi.distage.bootstrap
+
+import izumi.distage.model.provisioning.proxies.ProxyProvider
+import izumi.distage.model.provisioning.proxies.ProxyProvider.ProxyProviderFailingImpl
+
+object DynamicProxyBootstrap {
+  val DynamicProxyProvider: ProxyProvider = ProxyProviderFailingImpl
+}
diff --git a/distage/distage-core/.jvm/src/main/scala/izumi/distage/bootstrap/CglibBootstrap.scala b/distage/distage-core/.jvm/src/main/scala/izumi/distage/bootstrap/CglibBootstrap.scala
deleted file mode 100644
index ed346b27d..000000000
--- a/distage/distage-core/.jvm/src/main/scala/izumi/distage/bootstrap/CglibBootstrap.scala
+++ /dev/null
@@ -1,7 +0,0 @@
-package izumi.distage.bootstrap
-
-import izumi.distage.provisioning.strategies.cglib
-
-object CglibBootstrap {
-  type CglibProxyProvider = cglib.CglibProxyProvider
-}
diff --git a/distage/distage-core/.jvm/src/main/scala/izumi/distage/bootstrap/DynamicProxyBootstrap.scala b/distage/distage-core/.jvm/src/main/scala/izumi/distage/bootstrap/DynamicProxyBootstrap.scala
new file mode 100644
index 000000000..99183c093
--- /dev/null
+++ b/distage/distage-core/.jvm/src/main/scala/izumi/distage/bootstrap/DynamicProxyBootstrap.scala
@@ -0,0 +1,20 @@
+package izumi.distage.bootstrap
+
+import izumi.distage.model.provisioning.proxies.ProxyProvider
+import izumi.fundamentals.reflection.TypeUtil
+
+import scala.util.Try
+
+object DynamicProxyBootstrap {
+  val dynProxyProviderName = "izumi.distage.provisioning.strategies.dynamicproxy.DynamicProxyProvider$"
+
+  val DynamicProxyProvider: ProxyProvider =
+    Try(TypeUtil.instantiateObject[ProxyProvider](Class.forName(dynProxyProviderName))).toOption match {
+      case Some(value) =>
+        value
+      case None =>
+        import izumi.distage.model.provisioning.proxies.ProxyProvider.ProxyProviderFailingImpl
+        ProxyProviderFailingImpl
+    }
+
+}
diff --git a/distage/distage-core/src/main/scala/izumi/distage/bootstrap/BootstrapLocator.scala b/distage/distage-core/src/main/scala/izumi/distage/bootstrap/BootstrapLocator.scala
index 35a8a27eb..c9556d5a3 100644
--- a/distage/distage-core/src/main/scala/izumi/distage/bootstrap/BootstrapLocator.scala
+++ b/distage/distage-core/src/main/scala/izumi/distage/bootstrap/BootstrapLocator.scala
@@ -1,6 +1,6 @@
 package izumi.distage.bootstrap
 
-import izumi.distage.bootstrap.CglibBootstrap.CglibProxyProvider
+import izumi.distage.bootstrap.DynamicProxyBootstrap.DynamicProxyProvider
 import izumi.distage.model.*
 import izumi.distage.model.definition.*
 import izumi.distage.model.plan.ExecutableOp.InstantiationOp
@@ -138,8 +138,8 @@ object BootstrapLocator {
 
     make[BindingTranslator].from[BindingTranslator.Impl]
 
-    make[ProxyProvider].tagged(Cycles.Proxy).from[CglibProxyProvider]
-    make[ProxyProvider].from[ProxyProviderFailingImpl]
+    make[ProxyProvider].tagged(Cycles.Proxy).fromValue(DynamicProxyProvider)
+    make[ProxyProvider].fromValue(ProxyProviderFailingImpl)
 
     make[ProxyStrategy].tagged(Cycles.Disable).from[ProxyStrategyFailingImpl]
     make[ProxyStrategy].from[ProxyStrategyDefaultImpl]
diff --git a/project/Deps.sc b/project/Deps.sc
index c134b71e7..aba682d89 100644
--- a/project/Deps.sc
+++ b/project/Deps.sc
@@ -28,7 +28,7 @@ object Izumi {
     val classgraph = Version.VExpr("V.classgraph")
     val slf4j = Version.VExpr("V.slf4j")
     val typesafe_config = Version.VExpr("V.typesafe_config")
-    val cglib_nodep = Version.VExpr("V.cglib_nodep")
+    val bytebuddy = Version.VExpr("V.bytebuddy")
     val scala_java_time = Version.VExpr("V.scala_java_time")
     val scalamock = Version.VExpr("V.scalamock")
     val docker_java = Version.VExpr("V.docker_java")
@@ -101,7 +101,7 @@ object Izumi {
     final val scala_library = Library("org.scala-lang", "scala-library", Version.VExpr("scalaVersion.value"), LibraryType.Invariant)
     final val scala_reflect = Library("org.scala-lang", "scala-reflect", Version.VExpr("scalaVersion.value"), LibraryType.Invariant)
 
-    final val cglib_nodep = Library("cglib", "cglib-nodep", V.cglib_nodep, LibraryType.Invariant) in Scope.Compile.jvm
+    final val bytebuddy = Library("net.bytebuddy", "byte-buddy", V.bytebuddy, LibraryType.Invariant) in Scope.Compile.jvm
 
     final val projector = Library("org.typelevel", "kind-projector", V.kind_projector, LibraryType.Invariant)
       .more(LibSetting.Raw("cross CrossVersion.full"))
@@ -291,7 +291,7 @@ object Izumi {
       final val basePath = Seq("distage")
 
       final lazy val coreApi = ArtifactId("distage-core-api")
-      final lazy val proxyCglib = ArtifactId("distage-core-proxy-cglib")
+      final lazy val proxyBytebuddy = ArtifactId("distage-core-proxy-bytebuddy")
       final lazy val core = ArtifactId("distage-core")
       final lazy val config = ArtifactId("distage-extension-config")
       final lazy val plugins = ArtifactId("distage-extension-plugins")
@@ -453,8 +453,8 @@ object Izumi {
         depends = Seq(Projects.fundamentals.reflection, Projects.fundamentals.bio).map(_ in Scope.Compile.all),
       ),
       Artifact(
-        name = Projects.distage.proxyCglib,
-        libs = Seq(cglib_nodep),
+        name = Projects.distage.proxyBytebuddy,
+        libs = Seq(bytebuddy),
         depends = Seq(Projects.distage.coreApi).map(_ in Scope.Compile.all),
         platforms = Targets.jvm,
       ),
@@ -466,7 +466,7 @@ object Izumi {
           scala_java_time in Scope.Test.js,
           javaXInject in Scope.Test.all,
         ),
-        depends = Seq(Projects.distage.coreApi in Scope.Compile.all, Projects.distage.proxyCglib in Scope.Compile.jvm),
+        depends = Seq(Projects.distage.coreApi in Scope.Compile.all, Projects.distage.proxyBytebuddy in Scope.Test.jvm),
       ),
       Artifact(
         name = Projects.distage.config,
diff --git a/project/Versions.scala b/project/Versions.scala
index 96d0f9814..0d59b1715 100644
--- a/project/Versions.scala
+++ b/project/Versions.scala
@@ -36,7 +36,7 @@ object V {
   val typesafe_config = "1.4.0"
 
   // good to drop - java
-  val cglib_nodep = "3.3.0"
+  val bytebuddy = "1.12.10"
   val docker_java = "3.2.13"
 
   // microsite-only


Test patch:
diff --git a/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcBasicTestsJvm.scala b/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcBasicTestsJvm.scala
index 2c0476667..a7e8a9a06 100644
--- a/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcBasicTestsJvm.scala
+++ b/distage/distage-core/.jvm/src/test/scala/izumi/distage/gc/GcBasicTestsJvm.scala
@@ -4,6 +4,7 @@ import distage.DIKey
 import izumi.distage.model.PlannerInput
 import izumi.distage.model.definition.{Activation, ModuleDef}
 import izumi.distage.model.plan.Roots
+import izumi.distage.model.provisioning.proxies.DistageProxy
 import org.scalatest.wordspec.AnyWordSpec
 
 import scala.collection.immutable
@@ -30,10 +31,20 @@ class GcBasicTestsJvm extends AnyWordSpec with MkGcInjector {
 
       val result = injector.produce(plan).unsafeGet()
       assert(result.find[Trash].isEmpty)
-      assert(result.get[Circular1].c2 != null)
-      assert(result.get[Circular2].c1 != null)
-      assert(result.get[Circular1].c2.isInstanceOf[Circular2])
-      assert(result.get[Circular2].c1.isInstanceOf[Circular1])
+      val c1 = result.get[Circular1]
+      val c2 = result.get[Circular2]
+
+      assert(c1.c2 != null)
+      assert(c2.c1 != null)
+
+      assert(c2.test == 1)
+
+      assert(c1.c2.isInstanceOf[Circular2])
+      assert(c2.c1.isInstanceOf[Circular1])
+
+      assert(c2.isInstanceOf[DistageProxy] || c1.isInstanceOf[DistageProxy])
+      val p = List(c1, c2).collect { case p: DistageProxy => p }
+      assert(p.forall(_._distageProxyReference != null))
     }
 
     "keep by-name loops alive" in {
diff --git a/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/CglibProxiesTestJvm.scala b/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/CglibProxiesTestJvm.scala
index 742aee9d4..1e6dedfab 100644
--- a/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/CglibProxiesTestJvm.scala
+++ b/distage/distage-core/.jvm/src/test/scala/izumi/distage/injector/CglibProxiesTestJvm.scala
@@ -5,11 +5,9 @@ import izumi.distage.fixtures.CircularCases.*
 import izumi.distage.fixtures.InnerClassCases.{InnerClassStablePathsCase, InnerClassUnstablePathsCase}
 import izumi.distage.fixtures.ResourceCases.{CircularResourceCase, Ref, Suspend2}
 import izumi.distage.injector.ResourceEffectBindingsTest.Fn
-import izumi.distage.model.exceptions.interpretation.ProvisioningException
+import izumi.distage.model.exceptions.interpretation.{ProvisioningException, ProxyInstantiationException}
 import izumi.distage.model.plan.Roots
-import izumi.distage.provisioning.strategies.cglib.exceptions.CgLibInstantiationOpException
 import izumi.fundamentals.platform.functional.Identity
-import net.sf.cglib.core.CodeGenerationException
 import org.scalatest.wordspec.AnyWordSpec
 
 import scala.collection.immutable.Queue
@@ -19,7 +17,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
   "CircularDependenciesTest" should {
 
     "support circular dependencies" in {
-      import CircularCase1._
+      import CircularCase1.*
 
       val definition = PlannerInput.everything(new ModuleDef {
         make[Circular2]
@@ -36,7 +34,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     }
 
     "support circular dependencies with final class implementations" in {
-      import CircularCase1._
+      import CircularCase1.*
 
       val definition = PlannerInput.everything(new ModuleDef {
         make[Circular2].from[Circular2Impl]
@@ -53,7 +51,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     }
 
     "support circular dependencies in providers" in {
-      import CircularCase1._
+      import CircularCase1.*
 
       val definition = PlannerInput.everything(new ModuleDef {
         make[Circular2].from {
@@ -78,7 +76,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     }
 
     "Supports self-referencing circulars" in {
-      import CircularCase3._
+      import CircularCase3.*
 
       val definition = PlannerInput.everything(new ModuleDef {
         make[SelfReference]
@@ -94,7 +92,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     }
 
     "Support self-referencing provider" in {
-      import CircularCase3._
+      import CircularCase3.*
 
       val definition = PlannerInput.everything(new ModuleDef {
         make[SelfReference].from {
@@ -113,7 +111,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     }
 
     "support proxy circular dependencies involving a primitive type" in {
-      import CircularCase8._
+      import CircularCase8.*
 
       val definition = PlannerInput.everything(new ModuleDef {
         make[Circular2]
@@ -137,7 +135,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     }
 
     "support circular dependencies that use another object in their constructor that isn't involved in a cycle" in {
-      import CircularCase9._
+      import CircularCase9.*
 
       val definition = PlannerInput.everything(new ModuleDef {
         make[Circular2]
@@ -162,7 +160,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     }
 
     "support fully generic circular dependencies" in {
-      import CircularCase5._
+      import CircularCase5.*
 
       val definition = PlannerInput.everything(new ModuleDef {
         make[GenericCircular[Dependency]]
@@ -180,7 +178,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     }
 
     "support named circular dependencies" in {
-      import CircularCase4._
+      import CircularCase4.*
 
       val definition = PlannerInput.everything(new ModuleDef {
         make[IdTypeCircular]
@@ -201,7 +199,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     }
 
     "support type refinements in circular dependencies" in {
-      import CircularCase6._
+      import CircularCase6.*
 
       val definition = PlannerInput.everything(new ModuleDef {
         make[Dependency { def dep: RefinedCircular }].from[RealDependency]
@@ -219,7 +217,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     }
 
     "support simple by-name forward ref when there are non-by-name references" in {
-      import CircularCase10._
+      import CircularCase10.*
 
       val definition = PlannerInput(
         new ModuleDef {
@@ -242,7 +240,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     }
 
     "Regression test 1: isolated cycles causing spooky action at a distance" in {
-      import CircularCase7._
+      import CircularCase7.*
 
       val definition = PlannerInput.everything(new ModuleDef {
         // cycle
@@ -290,8 +288,8 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
 
     "progression test: can't find proper constructor for circular dependencies inside stable objects that contain inner classes from inherited traits that depend on types defined inside trait" in {
       val res = intercept[ProvisioningException] {
-        import InnerClassStablePathsCase._
-        import StableObjectInheritingTrait._
+        import InnerClassStablePathsCase.*
+        import StableObjectInheritingTrait.*
 
         val definition = PlannerInput.everything(new ModuleDef {
           make[Circular1]
@@ -309,7 +307,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     "progression test: cglib proxies can't resolve circular path-dependent dependencies (we don't take prefix type into account when calling constructor for generated lambdas and end up choosing the wrong constructor...)" in {
       // the value prefix probably has to be stored inside the Provider to fix this
       val exc = intercept[ProvisioningException] {
-        import InnerClassUnstablePathsCase._
+        import InnerClassUnstablePathsCase.*
         val testProviderModule = new TestModule
 
         val definition = PlannerInput.everything(new ModuleDef {
@@ -322,9 +320,9 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
 
         assert(context.get[testProviderModule.TestFactory].mk(testProviderModule.TestDependency()) == testProviderModule.TestClass(testProviderModule.TestDependency()))
       }
-      assert(exc.getSuppressed.head.isInstanceOf[CgLibInstantiationOpException])
-      assert(exc.getSuppressed.head.getCause.isInstanceOf[CodeGenerationException])
-      assert(exc.getSuppressed.head.getCause.getCause.isInstanceOf[NoSuchMethodException])
+      assert(exc.getSuppressed.head.isInstanceOf[ProxyInstantiationException])
+//      assert(exc.getSuppressed.head.getCause.isInstanceOf[CodeGenerationException])
+//      assert(exc.getSuppressed.head.getCause.getCause.isInstanceOf[NoSuchMethodException])
     }
 
   }
@@ -332,7 +330,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
   "ResourceEffectBindingsTest" should {
 
     "Support self-referencing circular effects" in {
-      import izumi.distage.fixtures.CircularCases.CircularCase3._
+      import izumi.distage.fixtures.CircularCases.CircularCase3.*
 
       val definition = PlannerInput.everything(new ModuleDef {
         make[Ref[Fn, Boolean]].fromEffect(Ref[Fn](false))
@@ -353,7 +351,7 @@ class CglibProxiesTestJvm extends AnyWordSpec with MkInjector {
     }
 
     "Support mutually-referent circular resources" in {
-      import CircularResourceCase._
+      import CircularResourceCase.*
       import ResourceEffectBindingsTest.Fn
 
       val definition = PlannerInput(
diff --git a/distage/distage-core/src/test/scala/izumi/distage/gc/GcBasicTests.scala b/distage/distage-core/src/test/scala/izumi/distage/gc/GcBasicTests.scala
index 15fae10a7..a575a999e 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/gc/GcBasicTests.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/gc/GcBasicTests.scala
@@ -34,7 +34,7 @@ class GcBasicTests extends AnyWordSpec with MkGcInjector {
     "handle by-name circular dependencies with sets through refs/2" in {
       import GcCases.InjectorCase13._
 
-      val injector = mkNoCglibInjector()
+      val injector = mkNoProxiesInjector()
       val plan = injector.plan(
         PlannerInput(
           new ModuleDef {
diff --git a/distage/distage-core/src/test/scala/izumi/distage/gc/GcCases.scala b/distage/distage-core/src/test/scala/izumi/distage/gc/GcCases.scala
index 05240b3bb..821263b30 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/gc/GcCases.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/gc/GcCases.scala
@@ -11,7 +11,9 @@ object GcCases {
 
     class Circular1(val c2: Circular2)
 
-    class Circular2(val c1: Circular1, val c4: Circular4)
+    class Circular2(val c1: Circular1, val c4: Circular4) {
+      def test: Int = 1
+    }
 
     class Circular3(val c4: Circular4)
 
diff --git a/distage/distage-core/src/test/scala/izumi/distage/gc/MkGcInjector.scala b/distage/distage-core/src/test/scala/izumi/distage/gc/MkGcInjector.scala
index b29bcfd08..e3ae9cc58 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/gc/MkGcInjector.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/gc/MkGcInjector.scala
@@ -16,7 +16,7 @@ trait MkGcInjector {
     Injector(Seq(AutoSetModule().register[AutoCloseable]) ++ more: _*)
   }
 
-  def mkNoCglibInjector(): Injector[Identity] = {
+  def mkNoProxiesInjector(): Injector[Identity] = {
     Injector.NoProxies(AutoSetModule().register[AutoCloseable])
   }
 }
diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/CircularDependenciesTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/CircularDependenciesTest.scala
index d12cc822b..26e653fd1 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/injector/CircularDependenciesTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/injector/CircularDependenciesTest.scala
@@ -68,7 +68,7 @@ class CircularDependenciesTest extends AnyWordSpec with MkInjector {
       make[ByNameSelfReference]
     })
 
-    val injector = mkNoCglibInjector()
+    val injector = mkNoProxiesInjector()
     val plan = injector.plan(definition)
     val context = injector.produce(plan).unsafeGet()
 
@@ -84,7 +84,7 @@ class CircularDependenciesTest extends AnyWordSpec with MkInjector {
       make[TraitSelfReference]
     })
 
-    val injector = mkNoCglibInjector()
+    val injector = mkNoProxiesInjector()
     val plan = injector.plan(definition)
     val context = injector.produce(plan).unsafeGet()
 
@@ -103,7 +103,7 @@ class CircularDependenciesTest extends AnyWordSpec with MkInjector {
       make[FactorySelfReference]
     })
 
-    val injector = mkNoCglibInjector()
+    val injector = mkNoProxiesInjector()
     val plan = injector.plan(definition)
     val context = injector.produce(plan).unsafeGet()
 
@@ -170,7 +170,7 @@ class CircularDependenciesTest extends AnyWordSpec with MkInjector {
       make[Int].from(1)
     })
 
-    val injector = mkNoCglibInjector()
+    val injector = mkNoProxiesInjector()
     val plan = injector.plan(definition)
     val context = injector.produce(plan).unsafeGet()
 
diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/InnerClassesTest.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/InnerClassesTest.scala
index 114fedea7..dd168b794 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/injector/InnerClassesTest.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/injector/InnerClassesTest.scala
@@ -154,7 +154,7 @@ class InnerClassesTest extends AnyWordSpec with MkInjector {
       make[ByNameCircular2]
     })
 
-    val context = mkNoCglibInjector().produce(definition).unsafeGet()
+    val context = mkNoProxiesInjector().produce(definition).unsafeGet()
 
     assert(context.get[ByNameCircular1] != null)
     assert(context.get[ByNameCircular1].circular2 eq context.get[ByNameCircular2])
diff --git a/distage/distage-core/src/test/scala/izumi/distage/injector/MkInjector.scala b/distage/distage-core/src/test/scala/izumi/distage/injector/MkInjector.scala
index eb3064c80..d2a9dee97 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/injector/MkInjector.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/injector/MkInjector.scala
@@ -5,6 +5,6 @@ import izumi.fundamentals.platform.functional.Identity
 
 trait MkInjector {
   def mkInjector(): Injector[Identity] = Injector.Standard()
-  def mkNoCglibInjector(): Injector[Identity] = Injector.NoProxies()
+  def mkNoProxiesInjector(): Injector[Identity] = Injector.NoProxies()
   def mkNoCyclesInjector(): Injector[Identity] = Injector.NoCycles()
 }
diff --git a/distage/distage-extension-config/src/test/scala/izumi/distage/impl/OptionalDependencyTest.scala b/distage/distage-extension-config/src/test/scala/izumi/distage/impl/OptionalDependencyTest.scala
index e70ec9f75..32ea9aa4c 100644
--- a/distage/distage-extension-config/src/test/scala/izumi/distage/impl/OptionalDependencyTest.scala
+++ b/distage/distage-extension-config/src/test/scala/izumi/distage/impl/OptionalDependencyTest.scala
@@ -137,8 +137,12 @@ class OptionalDependencyTest extends AnyWordSpec with GivenWhenThen {
       """)
 
     And("Methods that use `No More Orphans` trick can be called with nulls, but will error")
-    intercept[NoClassDefFoundError] {
+    intercept[Throwable] {
       QuasiIO.fromCats[Option, Lifecycle[_[_], Int]](null, null)
+    } match {
+      case _: NoClassDefFoundError =>
+      case _: NullPointerException =>
+        fail("NPE has been thrown, seems like cats are in the classpath (running under IDEA?)")
     }
 
     And("Methods that mention cats types only in generics will error on call")