@@ -6,14 +6,15 @@ package play.api.libs.json
66
77import org .openjdk .jol .info .GraphLayout
88import org .scalatest .freespec .AnyFreeSpec
9+ import scala .util .Properties
910import scala .util .chaining ._
1011
1112class JsonMemoryFootprintSpec extends AnyFreeSpec {
1213
1314 " Json.parse" - {
1415 " obj0" in assertSizes(""" {}""" , 16 , 16 )
15- " obj1" in assertSizes(""" {"1":true}""" , 152 , 168 )
16- " obj4" in assertSizes(""" {"1":true,"2":true,"3":true,"4":true}""" , 296 , 312 )
16+ " obj1" in assertSizes(""" {"1":true}""" , 152 , 168 , expectedJdk21 = Some ( 160 ), hashedJdk21 = Some ( 184 ) )
17+ " obj4" in assertSizes(""" {"1":true,"2":true,"3":true,"4":true}""" , 296 , 312 , expectedJdk21 = Some ( 304 ), hashedJdk21 = Some ( 328 ) )
1718
1819 " arr0" in assertSizes(""" []""" , 40 , 40 )
1920 " arr1" in assertSizes(""" [true]""" , 120 , 120 )
@@ -33,38 +34,39 @@ class JsonMemoryFootprintSpec extends AnyFreeSpec {
3334 " JsObject" - {
3435 def obj (json : String ) = Json .parse(json).as[JsObject ]
3536 " obj0 ++ obj0" in assertSize(obj(" {}" ) ++ obj(" {}" ), 16 )
36- " obj0 ++ obj1" in assertSize(obj(" {}" ) ++ obj(""" {"1":true}""" ), 152 )
37- " obj1 ++ obj0" in assertSize(obj(""" {"1":true}""" ) ++ obj(""" {}""" ), 152 )
37+ " obj0 ++ obj1" in assertSize(obj(" {}" ) ++ obj(""" {"1":true}""" ), 152 , expectedJdk21 = Some ( 160 ) )
38+ " obj1 ++ obj0" in assertSize(obj(""" {"1":true}""" ) ++ obj(""" {}""" ), 152 , expectedJdk21 = Some ( 160 ) )
3839
39- " obj1.value" in assertSize(obj(""" {"1":true}""" ).tap(_.value), 152 )
40+ " obj1.value" in assertSize(obj(""" {"1":true}""" ).tap(_.value), 152 , expectedJdk21 = Some ( 160 ) )
4041 }
4142
4243 " malicious" - {
4344 // if we pack data into ~1KB of input, how much memory amplification can we achieve?
4445 def arr1KB (elem : String , targetSize : Int = 1000 ): String =
4546 Iterator .continually(elem).take(targetSize / (elem.length + 1 )).mkString(" [" , " ," , " ]" )
4647 " obj0" in assertSizes(arr1KB(" {}" ), 7432 , 7432 )
47- " obj1" in assertSizes(arr1KB(""" {"a":6}""" ), 29568 , 31568 )
48+ " obj1" in assertSizes(arr1KB(""" {"a":6}""" ), 29568 , 31568 , expectedJdk21 = Some ( 30568 ), hashedJdk21 = Some ( 33568 ) )
4849 " nums" in assertSizes(arr1KB(" 6" ), 42104 , 42104 )
4950 " arr0" in assertSizes(arr1KB(" []" ), 15424 , 15424 )
5051 " arr1" in assertSizes(arr1KB(" [6]" ), 51080 , 51080 )
5152 }
5253
53- private def assertSizes (input : String , expected : Long , hashed : Long ) = {
54- assertSize(Json .parse(input), expected)
54+ private def assertSizes (input : String , expected : Long , hashed : Long , expectedJdk21 : Option [ Long ] = None , hashedJdk21 : Option [ Long ] = None ) = {
55+ assertSize(Json .parse(input), expected, expectedJdk21 )
5556 withClue(" After hashCode():" )(
5657 assertSize(
5758 {
5859 val t = Json .parse(input)
5960 t.hashCode()
6061 t
6162 },
62- hashed
63+ hashed,
64+ hashedJdk21
6365 )
6466 )
6567 }
6668
67- private def assertSize (a : => JsValue , expected : Long ) = {
69+ private def assertSize (a : => JsValue , expected : Long , expectedJdk21 : Option [ Long ] = None ) = {
6870 val layout1 = GraphLayout .parseInstance(a)
6971 val layout2 = GraphLayout .parseInstance(a)
7072 val distinct = layout1.subtract(layout2) // shared singletons don't count.
@@ -74,7 +76,7 @@ class JsonMemoryFootprintSpec extends AnyFreeSpec {
7476 | ${distinct.toPrintable}""" .stripMargin
7577
7678 withClue(clue) {
77- assert(distinct.totalSize() === expected)
79+ assert(distinct.totalSize() === expectedJdk21.filter(_ => Properties .isJavaAtLeast( " 21 " )).getOrElse( expected) )
7880 }
7981 }
8082}
0 commit comments