diff --git a/docs/GUIDE.md b/docs/GUIDE.md index 8606b8ef..fd1bb46f 100644 --- a/docs/GUIDE.md +++ b/docs/GUIDE.md @@ -118,6 +118,13 @@ This option disables detection of messages/events/state based on type of argumen This option disables detection of messages/events/state based on return type of the function given as argument to method. This detection is enabled by default. If you want to disable it, add the following setting:
`Compile / scalacOptions += "-P:serializability-checker-plugin:--disable-detection-higher-order-function"`

+- `--types-explicitly-marked-as-serializable=,,...` + +This option can be used to pass a comma-separated list of fully-qualified names of types that should be considered serializable by the checker, even if they do **not** extend a designated serializability trait. +The list is empty by default. If you want to mark some types as serializable, add the following setting (here shown with sample types):
+ +`Compile / scalacOptions += "-P:serializability-checker-plugin:--types-explicitly-marked-as-serializable=scala.util.Either,scala.collection.immutable.Set"`

+ ### Codec Registration Checker Compiler Plugin Before using this compiler plugin, make sure that you are using both [annotations](#annotations) properly. If so — the plugin can be used right away. This plugin checks whether classes marked with serializability trait are being referenced in a marked serializer, which ensures that codecs will be registered in runtime. diff --git a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPlugin.scala b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPlugin.scala index b84e9cef..39604002 100644 --- a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPlugin.scala +++ b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPlugin.scala @@ -27,6 +27,10 @@ class SerializabilityCheckerCompilerPlugin(override val global: Global) extends pluginOptions.detectFromMethods = !options.contains(disableMethods) pluginOptions.detectFromUntypedMethods = !options.contains(disableMethodsUntyped) pluginOptions.detectFromHigherOrderFunctions = !options.contains(disableHigherOrderFunctions) + options.find(_.startsWith(typesExplicitlyMarkedAsSerializable)).foreach { opt => + pluginOptions.typesExplicitlyMarkedAsSerializable = + opt.stripPrefix(typesExplicitlyMarkedAsSerializable).split(",").toSeq.map(_.strip()) + } true } @@ -37,6 +41,7 @@ class SerializabilityCheckerCompilerPlugin(override val global: Global) extends |$disableMethods - disables detection of messages/events/state based on type of arguments to a method, e.g. akka.actor.typed.ActorRef.tell |$disableMethodsUntyped - disables detection of messages/events/state based on type of arguments to a method that takes Any, used for Akka Classic |$disableHigherOrderFunctions - disables detection of messages/events/state based on return type of the function given as argument to method + |$typesExplicitlyMarkedAsSerializable - comma-separated list of fully-qualified names of types that should be considered serializable by this checker, even if they do NOT extend a designated serializability trait |""".stripMargin) } @@ -49,6 +54,7 @@ object SerializabilityCheckerCompilerPlugin { val disableMethods = "--disable-detection-methods" val disableMethodsUntyped = "--disable-detection-untyped-methods" val disableHigherOrderFunctions = "--disable-detection-higher-order-function" + val typesExplicitlyMarkedAsSerializable = "--types-explicitly-marked-as-serializable=" } val serializabilityTraitType = "org.virtuslab.ash.annotation.SerializabilityTrait" } diff --git a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponent.scala b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponent.scala index 30ee23ad..0b116dd3 100644 --- a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponent.scala +++ b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponent.scala @@ -192,6 +192,8 @@ class SerializabilityCheckerCompilerPluginComponent( Some(tp) else if (akkaSerializabilityTraits.contains(tp.typeSymbol.fullName)) Some(tp) + else if (pluginOptions.typesExplicitlyMarkedAsSerializable.contains(tp.typeSymbol.fullName)) + Some(tp) else if (tp.typeSymbol.isAbstractType) findSuperclassAnnotatedWithSerializabilityTrait(tp.upperBound) else diff --git a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerOptions.scala b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerOptions.scala index b9bef9c5..fe802f30 100644 --- a/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerOptions.scala +++ b/serializability-checker-compiler-plugin/src/main/scala/org/virtuslab/ash/SerializabilityCheckerOptions.scala @@ -10,4 +10,5 @@ class SerializabilityCheckerOptions( var detectFromGenericMethods: Boolean = true, var detectFromMethods: Boolean = true, var detectFromUntypedMethods: Boolean = true, - var detectFromHigherOrderFunctions: Boolean = true) + var detectFromHigherOrderFunctions: Boolean = true, + var typesExplicitlyMarkedAsSerializable: Seq[String] = Seq.empty) diff --git a/serializability-checker-compiler-plugin/src/test/resources/TellEitherSeqSetTest.scala b/serializability-checker-compiler-plugin/src/test/resources/TellEitherSeqSetTest.scala new file mode 100644 index 00000000..8db9b870 --- /dev/null +++ b/serializability-checker-compiler-plugin/src/test/resources/TellEitherSeqSetTest.scala @@ -0,0 +1,11 @@ +package org.random.project + +import akka.actor.ActorRef + +object TellEitherSeqSetTest { + + val ref: ActorRef = ??? + ref.tell(Right("hello"), null) + ref.tell(Seq("hello"), null) + ref.tell(Set("hello"), null) +} diff --git a/serializability-checker-compiler-plugin/src/test/resources/TellEitherTest.scala b/serializability-checker-compiler-plugin/src/test/resources/TellEitherTest.scala new file mode 100644 index 00000000..2e075a40 --- /dev/null +++ b/serializability-checker-compiler-plugin/src/test/resources/TellEitherTest.scala @@ -0,0 +1,9 @@ +package org.random.project + +import akka.actor.ActorRef + +object TellEitherTest { + + val ref: ActorRef = ??? + ref.tell(Right("hello"), null) +} diff --git a/serializability-checker-compiler-plugin/src/test/resources/TellSeqTest.scala b/serializability-checker-compiler-plugin/src/test/resources/TellSeqTest.scala new file mode 100644 index 00000000..06c2ed07 --- /dev/null +++ b/serializability-checker-compiler-plugin/src/test/resources/TellSeqTest.scala @@ -0,0 +1,9 @@ +package org.random.project + +import akka.actor.ActorRef + +object TellSeqTest { + + val ref: ActorRef = ??? + ref.tell(Seq("hello"), null) +} diff --git a/serializability-checker-compiler-plugin/src/test/resources/TellSetTest.scala b/serializability-checker-compiler-plugin/src/test/resources/TellSetTest.scala new file mode 100644 index 00000000..5c09e4e8 --- /dev/null +++ b/serializability-checker-compiler-plugin/src/test/resources/TellSetTest.scala @@ -0,0 +1,9 @@ +package org.random.project + +import akka.actor.ActorRef + +object TellSetTest { + + val ref: ActorRef = ??? + ref.tell(Set("hello"), null) +} diff --git a/serializability-checker-compiler-plugin/src/test/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponentSpec.scala b/serializability-checker-compiler-plugin/src/test/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponentSpec.scala index 150e15fe..9353d815 100644 --- a/serializability-checker-compiler-plugin/src/test/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponentSpec.scala +++ b/serializability-checker-compiler-plugin/src/test/scala/org/virtuslab/ash/SerializabilityCheckerCompilerPluginComponentSpec.scala @@ -180,5 +180,38 @@ class SerializabilityCheckerCompilerPluginComponentSpec extends AnyWordSpecLike val code = getResourceAsString("GenericsTest3.scala") SerializabilityCheckerCompiler.compileCode(List(serNoCode, code)) should be("") } + + "fail on usage of Either as a message" in { + val code = getResourceAsString("TellEitherTest.scala") + SerializabilityCheckerCompiler.compileCode(List(code)) should include( + "Right[Nothing,String] is used as Akka message") + } + + "fail on usage of Seq as a message" in { + val code = getResourceAsString("TellSeqTest.scala") + SerializabilityCheckerCompiler.compileCode(List(code)) should include("Seq[String] is used as Akka message") + } + + "fail on usage of Set as a message" in { + val code = getResourceAsString("TellSetTest.scala") + SerializabilityCheckerCompiler.compileCode(List(code)) should include("Set[String] is used as Akka message") + } + + "not fail on usage of Either as a message when Either is explicitly marked as serializable" in { + val code = getResourceAsString("TellEitherTest.scala") + SerializabilityCheckerCompiler.compileCode( + List(code), + List(typesExplicitlyMarkedAsSerializable + "scala.util.Either")) should be("") + } + + "when multiple types are used as a message, then only fail on the ones that are NOT explicitly marked as serializable" in { + val code = getResourceAsString("TellEitherSeqSetTest.scala") + val output = SerializabilityCheckerCompiler.compileCode( + List(code), + List(typesExplicitlyMarkedAsSerializable + "scala.util.Either,scala.collection.immutable.Set")) + output should include("Seq[String] is used as Akka message") + (output should not).include("Right[Nothing,String] is used as Akka message") + (output should not).include("Set[String] is used as Akka message") + } } }