Skip to content

[Scala] Class deserialization returns null until serialize is called #2249

Open
@creddy

Description

@creddy

Search before asking

  • I had searched in the issues and found no similar issues.

Version

➜ fury-test sw_vers
ProductName: macOS
ProductVersion: 15.4.1
BuildVersion: 24E263

➜ fury-test java -version
openjdk version "21.0.4.0.101" 2024-10-15 LTS
OpenJDK Runtime Environment Zulu21.37+12-SA (build 21.0.4.0.101+1-LTS)
OpenJDK 64-Bit Server VM Zulu21.37+12-SA (build 21.0.4.0.101+1-LTS, mixed mode)
Salesforce OneJDK 21.0.4.0.101+34 2024-11-20

➜ fury-test sbt -version
sbt version in this project: 1.10.11
sbt runner version: 1.10.11

➜ fury-test cat build.sbt| grep scalaVersion
scalaVersion := "2.13.15"

➜ fury-test cat build.sbt| grep org.apache.fury
libraryDependencies += "org.apache.fury" % "fury-scala_2.13" % "0.10.2"

Component(s)

Other, Java

Minimal reproduce step

Main.scala

import org.apache.fury.{ Fury, ThreadSafeFury }
import org.apache.fury.serializer.scala.ScalaSerializers
import java.nio.file.{Paths, Files}
import java.nio.charset.StandardCharsets

object Main extends App {
  val threadSafeFury: ThreadSafeFury = Fury
    .builder()
    .withScalaOptimizationEnabled(true)
    .requireClassRegistration(false)
    .withRefTracking(true)
    .buildThreadSafeFury()

  ScalaSerializers.registerSerializers(threadSafeFury)

  case class ThisReturnsNullUntilSerializeIsCalled(param: String)

  // Uncomment this line to generate the test file. Comment it out to reproduce the issue after generating the file
  // Files.write(Paths.get("test.txt"), threadSafeFury.serialize(ThisReturnsNullUntilSerializeIsCalled))

  // Registering the class resolves the issue
  //threadSafeFury.register(Main.ThisReturnsNullUntilSerializeIsCalled.getClass)

  // Stored the serialized bytes in a previous run
  val byteArray = Files.readAllBytes(Paths.get("test.txt"))

  // This should print `ThisReturnsNullUntilSerializeIsCalled` but prints `null`
  println(threadSafeFury.deserialize(byteArray))

  // After this call deserialization works
  val bytes = threadSafeFury.serialize(ThisReturnsNullUntilSerializeIsCalled)

  // Prints `ThisReturnsNullUntilSerializeIsCalled`
  println(threadSafeFury.deserialize(byteArray))

  // Write out again to ensure that the file is up to date
  Files.write(Paths.get("test.txt"), bytes)
}

build.sbt

scalaVersion := "2.13.15"

name := "hello-world"
organization := "ch.epfl.scala"
version := "1.0"
libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "2.3.0"
libraryDependencies += "org.apache.fury" % "fury-scala_2.13" % "0.10.2"

What did you expect to see?

sbt:hello-world> run
[info] compiling 1 Scala source to /Users/chethan.reddy/tmp/fury-test/fury-test/target/scala-2.13/classes ...
[info] running Main 
2025-05-20 09:29:16 WARN  FuryBuilder:427 [sbt-bg-threads-9] - Class registration isn't forced, unknown classes can be deserialized. If the environment isn't secure, please enable class registration by `FuryBuilder#requireClassRegistration(true)` or configure ClassChecker by `ClassResolver#setClassChecker`
2025-05-20 09:29:16 INFO  Fury:159 [sbt-bg-threads-9] - Created new fury org.apache.fury.Fury@39db8b6
ThisReturnsNullUntilSerializeIsCalled
ThisReturnsNullUntilSerializeIsCalled
[success] Total time: 0 s, completed May 20, 2025, 9:29:16 AM

What did you see instead?

sbt:hello-world> run
[info] running Main 
2025-05-20 09:28:45 WARN  FuryBuilder:427 [sbt-bg-threads-7] - Class registration isn't forced, unknown classes can be deserialized. If the environment isn't secure, please enable class registration by `FuryBuilder#requireClassRegistration(true)` or configure ClassChecker by `ClassResolver#setClassChecker`
2025-05-20 09:28:45 INFO  Fury:159 [sbt-bg-threads-7] - Created new fury org.apache.fury.Fury@784533a7
null
ThisReturnsNullUntilSerializeIsCalled
[success] Total time: 0 s, completed May 20, 2025, 9:28:45 AM

Anything Else?

We're using the class as a kind of Enum. Here's a different example:

import org.apache.fury.{ Fury, ThreadSafeFury }
import org.apache.fury.serializer.scala.ScalaSerializers
import java.nio.file.{Paths, Files}
import java.nio.charset.StandardCharsets

object Main extends App {
  val threadSafeFury: ThreadSafeFury = Fury
    .builder()
    .withScalaOptimizationEnabled(true)
    .requireClassRegistration(false)
    .withRefTracking(true)
    .buildThreadSafeFury()

  ScalaSerializers.registerSerializers(threadSafeFury)

  sealed trait VehicleType { def name: String; def id: Int }

  object VehicleType {
    case object Bus extends VehicleType { val name = "Bus"; val id = 0 }
    case object Car extends VehicleType { val name = "Car"; val id = 1 }

  }

  object Transportation {
    case class Default(
        id: Long,
        tpe: VehicleType
    )
  }

  val travelMethod = Transportation.Default(
        id = 10L,
        tpe = VehicleType.Car)

  if (!Files.exists(Paths.get("car.txt"))) {
    Files.write(Paths.get("car.txt"), threadSafeFury.serialize(travelMethod))
  }

  val byteArray = Files.readAllBytes(Paths.get("car.txt"))

  println(threadSafeFury.deserialize(byteArray))
}

When we first run the program, the file car.txt is created and the program outputs:

Default(10,Car)

Then, if we modify travelMethod to

  val travelMethod = Transportation.Default(
        id = 10L,
        tpe = VehicleType.Bus)

Running the program again without any other changes outputs:

Default(10,null)

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions