Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

package org.apache.pekko

import java.lang.invoke.{ MethodHandles, MethodType }

import org.apache.pekko.actor._

import org.scalatest.matchers.should.Matchers
Expand All @@ -37,6 +39,6 @@ class PekkoExceptionSpec extends AnyWordSpec with Matchers {
}

def verify(clazz: java.lang.Class[?]): Unit = {
clazz.getConstructor(Array(classOf[String]): _*)
MethodHandles.publicLookup().findConstructor(clazz, MethodType.methodType(Void.TYPE, classOf[String]))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ class ByteIteratorSpec extends AnyWordSpec with Matchers {
freshIterator().indexOf(0x10, 3) should be(5)

// There is also an indexOf with another signature, which is hard to invoke :D
val otherIndexOfHandle =
java.lang.invoke.MethodHandles.publicLookup().findVirtual(
classOf[ByteIterator],
"indexOf",
java.lang.invoke.MethodType.methodType(classOf[Int], classOf[Byte], classOf[Int]))
def otherIndexOf(iterator: ByteIterator, byte: Byte, from: Int): Int =
classOf[ByteIterator]
.getMethod("indexOf", classOf[Byte], classOf[Int])
.invoke(iterator, byte.asInstanceOf[Object], from.asInstanceOf[Object])
.asInstanceOf[Int]
otherIndexOfHandle.invoke(iterator, byte, from).asInstanceOf[Int]

otherIndexOf(freshIterator(), 0x20, 1) should be(1)
otherIndexOf(freshIterator(), 0x10, 1) should be(2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ class ByteStringInitializationSpec extends AnyWordSpec with Matchers {
}
}

cleanCl
.loadClass("org.apache.pekko.util.ByteStringInitTest")
.getDeclaredConstructor()
.newInstance()
val clazz = cleanCl.loadClass("org.apache.pekko.util.ByteStringInitTest")
java.lang.invoke.MethodHandles
.privateLookupIn(clazz, java.lang.invoke.MethodHandles.lookup())
.findConstructor(clazz, java.lang.invoke.MethodType.methodType(Void.TYPE))
.invokeWithArguments()
.asInstanceOf[Runnable]
.run()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

package org.apache.pekko.util

import java.util.concurrent.TimeoutException

import scala.annotation.nowarn
import scala.collection.immutable

Expand Down Expand Up @@ -50,7 +52,7 @@ class ReflectSpec extends AnyWordSpec with Matchers {
}
"deal with `null` in 1 matching case" in {
val constructor = Reflect.findConstructor(classOf[One], immutable.Seq(null))
constructor.newInstance(null)
constructor.invoke(null.asInstanceOf[AnyRef])
}
"deal with multiple constructors" in {
Reflect.findConstructor(classOf[MultipleOne], immutable.Seq(new A))
Expand All @@ -62,6 +64,11 @@ class ReflectSpec extends AnyWordSpec with Matchers {
Reflect.findConstructor(classOf[MultipleOne], immutable.Seq(null))
}
}
"use public lookup for public JDK constructors in non-open modules" in {
val constructor = Reflect.findConstructor(classOf[TimeoutException], immutable.Seq("err"))
val instance = Reflect.instantiate[TimeoutException](constructor, immutable.Seq("err"))
instance.getMessage should ===("err")
}
}

"Reflect#getCallerClass" must {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import org.apache.pekko
import pekko.actor.typed.{ ActorSystem, Extension, ExtensionId, Extensions }
import pekko.actor.typed.ExtensionSetup
import pekko.annotation.InternalApi
import pekko.util.Reflect

/**
* INTERNAL API
Expand Down Expand Up @@ -70,8 +71,8 @@ private[pekko] trait ExtensionsImpl extends Extensions { self: ActorSystem[?] wi
dynamicAccess.getClassFor[ExtensionId[Extension]](extensionIdFQCN).flatMap[ExtensionId[Extension]] {
(clazz: Class[?]) =>
Try {
val singletonAccessor = clazz.getDeclaredMethod("getInstance")
singletonAccessor.invoke(null).asInstanceOf[ExtensionId[Extension]]
val handle = Reflect.findStaticNoArgMethod(clazz, "getInstance")
handle.invoke().asInstanceOf[ExtensionId[Extension]]
}
}

Expand Down
11 changes: 6 additions & 5 deletions actor/src/main/java/org/apache/pekko/io/ByteBufferCleaner.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.invoke.VarHandle;
import java.nio.ByteBuffer;

/**
Expand Down Expand Up @@ -50,11 +50,12 @@ private static final class Java9Cleaner implements Cleaner {
private final MethodHandle invokeCleaner;

private Java9Cleaner() throws ReflectiveOperationException {
final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
final Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
final Object theUnsafe = field.get(null);
MethodHandles.Lookup lookup = MethodHandles.lookup();
final Class<?> unsafeClass = lookup.findClass("sun.misc.Unsafe");
final VarHandle unsafeHandle =
MethodHandles.privateLookupIn(unsafeClass, lookup)
.findStaticVarHandle(unsafeClass, "theUnsafe", unsafeClass);
final Object theUnsafe = unsafeHandle.get();
MethodHandle invokeCleaner =
lookup.findVirtual(
unsafeClass, "invokeCleaner", methodType(void.class, ByteBuffer.class));
Expand Down
40 changes: 7 additions & 33 deletions actor/src/main/scala/org/apache/pekko/actor/AbstractProps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@

package org.apache.pekko.actor

import java.lang.reflect.Constructor
import java.lang.reflect.Modifier

import scala.annotation.tailrec
import scala.annotation.varargs

import org.apache.pekko
Expand Down Expand Up @@ -58,42 +56,18 @@ private[pekko] trait AbstractProps {
private def checkCreatorClosingOver(clazz: Class[?]): Unit = {
val enclosingClass = clazz.getEnclosingClass

def hasDeclaredConstructorWithEmptyParams(declaredConstructors: Array[Constructor[?]]): Boolean = {
@tailrec def loop(i: Int): Boolean = {
if (i == declaredConstructors.length) false
else {
if (declaredConstructors(i).getParameterCount == 0)
true
else
loop(i + 1) // recur
}
}
loop(0)
}

def hasDeclaredConstructorWithEnclosingClassParam(declaredConstructors: Array[Constructor[?]]): Boolean = {
@tailrec def loop(i: Int): Boolean = {
if (i == declaredConstructors.length) false
else {
val c = declaredConstructors(i)
if (c.getParameterCount >= 1 && c.getParameterTypes()(0) == enclosingClass)
true
else
loop(i + 1) // recur
}
}
loop(0)
}

def hasValidConstructor: Boolean = {
val constructorsLength = clazz.getConstructors.length
if (constructorsLength > 0)
if (clazz.getConstructors.nonEmpty)
true
else {
val decl = clazz.getDeclaredConstructors
val declaredConstructors = clazz.getDeclaredConstructors
// the hasDeclaredConstructorWithEnclosingClassParam check is for supporting `new Creator<SomeActor> {`
// which was supported in versions before 2.4.5
hasDeclaredConstructorWithEmptyParams(decl) || !hasDeclaredConstructorWithEnclosingClassParam(decl)
declaredConstructors.exists(_.getParameterCount == 0) ||
!((enclosingClass ne null) && declaredConstructors.exists { constructor =>
val parameterTypes = constructor.getParameterTypes
parameterTypes.nonEmpty && parameterTypes(0) == enclosingClass
})
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private[pekko] class ArgsReflectConstructor(clz: Class[? <: Actor], args: immuta
extends IndirectActorProducer {
private val constructor = Reflect.findConstructor(clz, args)
override def actorClass = clz
override def produce() = Reflect.instantiate(constructor, args)
override def produce() = Reflect.instantiate[Actor](constructor, args)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

package org.apache.pekko.actor

import java.lang.reflect.InvocationTargetException
import java.lang.invoke.{ MethodHandle, MethodHandles, MethodType, VarHandle }
import java.util.concurrent.ConcurrentHashMap

import scala.collection.immutable
import scala.reflect.ClassTag
Expand All @@ -26,13 +27,14 @@ import pekko.annotation.DoNotInherit
/**
* This is the default [[pekko.actor.DynamicAccess]] implementation used by [[pekko.actor.ExtendedActorSystem]]
* unless overridden. It uses reflection to turn fully-qualified class names into `Class[_]` objects
* and creates instances from there using `getDeclaredConstructor()` and invoking that. The class loader
* and creates instances from there using constructor method handles. The class loader
* to be used for all this is determined by the actor system’s class loader by default.
*
* Not for user extension or construction
*/
@DoNotInherit
class ReflectiveDynamicAccess(val classLoader: ClassLoader) extends DynamicAccess {
import ReflectiveDynamicAccess._

override def getClassFor[T: ClassTag](fqcn: String): Try[Class[? <: T]] =
Try[Class[? <: T]] {
Expand All @@ -45,13 +47,12 @@ class ReflectiveDynamicAccess(val classLoader: ClassLoader) extends DynamicAcces
Try {
val types = args.map(_._1).toArray
val values = args.map(_._2).toArray
val constructor = clazz.getDeclaredConstructor(types: _*)
constructor.setAccessible(true)
val obj = constructor.newInstance(values: _*)
val handle = constructorHandle(clazz, types)
val obj = handle.invokeWithArguments(values: _*)
val t = implicitly[ClassTag[T]].runtimeClass
if (t.isInstance(obj)) obj.asInstanceOf[T]
else throw new ClassCastException(clazz.getName + " is not a subtype of " + t)
}.recover { case i: InvocationTargetException if i.getTargetException ne null => throw i.getTargetException }
}

override def createInstanceFor[T: ClassTag](fqcn: String, args: immutable.Seq[(Class[?], AnyRef)]): Try[T] =
getClassFor(fqcn).flatMap { c =>
Expand All @@ -60,7 +61,7 @@ class ReflectiveDynamicAccess(val classLoader: ClassLoader) extends DynamicAcces

override def classIsOnClasspath(fqcn: String): Boolean =
getClassFor[Any](fqcn) match {
case Failure(_: ClassNotFoundException | _: NoClassDefFoundError) =>
case Failure(_: ClassNotFoundException | _: NoClassDefFoundError | _: LinkageError) =>
false
case _ =>
true
Expand All @@ -72,17 +73,50 @@ class ReflectiveDynamicAccess(val classLoader: ClassLoader) extends DynamicAcces
else getClassFor(fqcn + "$").recoverWith { case _ => getClassFor(fqcn) }
classTry.flatMap { c =>
Try {
val module = c.getDeclaredField("MODULE$")
module.setAccessible(true)
val moduleHandle = moduleHandleFor(c)
val t = implicitly[ClassTag[T]].runtimeClass
module.get(null) match {
moduleHandle.get() match {
case null => throw new NullPointerException
case x if !t.isInstance(x) => throw new ClassCastException(fqcn + " is not a subtype of " + t)
case x: T => x
case unexpected =>
throw new IllegalArgumentException(s"Unexpected module field: $unexpected") // will not happen, for exhaustiveness check
}
}.recover { case i: InvocationTargetException if i.getTargetException ne null => throw i.getTargetException }
}
}
}
}

private object ReflectiveDynamicAccess {
private val lookup = MethodHandles.lookup()
private val publicLookup = MethodHandles.publicLookup()
private val constructorHandles = new ConcurrentHashMap[ConstructorKey, MethodHandle]
private val moduleHandles = new ConcurrentHashMap[Class[?], VarHandle]
private final case class ConstructorKey(clazz: Class[?], parameterTypes: Vector[Class[?]])

private def constructorHandle(clazz: Class[?], parameterTypes: Array[Class[?]]): MethodHandle = {
val key = ConstructorKey(clazz, parameterTypes.toVector)
val cached = constructorHandles.get(key)
if (cached ne null) cached
else {
val methodType = MethodType.methodType(Void.TYPE, parameterTypes.asInstanceOf[Array[Class[?]]])
val handle = try publicLookup.findConstructor(clazz, methodType)
catch {
case _: IllegalAccessException | _: NoSuchMethodException =>
MethodHandles.privateLookupIn(clazz, lookup).findConstructor(clazz, methodType)
}
val existing = constructorHandles.putIfAbsent(key, handle)
if (existing eq null) handle else existing
}
}

private def moduleHandleFor(clazz: Class[?]): VarHandle = {
val cached = moduleHandles.get(clazz)
if (cached ne null) cached
else {
val handle = MethodHandles.privateLookupIn(clazz, lookup).findStaticVarHandle(clazz, "MODULE$", clazz)
val existing = moduleHandles.putIfAbsent(clazz, handle)
if (existing eq null) handle else existing
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ private[pekko] class Mailboxes(
throw new IllegalArgumentException(s"no wildcard type allowed in RequireMessageQueue argument (was [$x])")
}
case unexpected =>
throw new IllegalArgumentException(s"Unexpected actor class marker: $unexpected") // will not happen, for exhaustiveness check
throw new IllegalArgumentException(s"Unexpected actor class marker: $unexpected")
}

// don’t care if this happens twice
Expand All @@ -148,7 +148,7 @@ private[pekko] class Mailboxes(
s"no wildcard type allowed in ProducesMessageQueue argument (was [$x])")
}
case unexpected =>
throw new IllegalArgumentException(s"Unexpected message queue type marker: $unexpected") // will not happen, for exhaustiveness check
throw new IllegalArgumentException(s"Unexpected message queue type marker: $unexpected")
}
}

Expand Down
Loading
Loading