Skip to content

Commit

Permalink
Use TreeMap instead of HashMap for JsObject key/value pairs, fixes sp…
Browse files Browse the repository at this point in the history
…ray#277

The problem is that with String's hashCode implementation it is too simple to
create synthetic collisions. This allows an attacker to create an object with
keys that all collide which leads to a performance drop for the HashMap
just for creating the map in the first place. See scala/bug#11203
for more information about the underlying HashMap issue.

For the time being, it seems safer to use a TreeMap which uses String ordering.
Benchmarks suggest that using a TreeMap is only ~6% slower for reasonably sized JSON objects
up to 100 keys.

Benchmark                         (_size)  (parser)   Mode  Cnt        Score       Error  Units
ExtractFieldsBenchmark.readSpray        1   HashMap  thrpt    5  1195832.262 ± 64366.605  ops/s
ExtractFieldsBenchmark.readSpray        1   TreeMap  thrpt    5  1342009.641 ± 17307.555  ops/s
ExtractFieldsBenchmark.readSpray       10   HashMap  thrpt    5   237173.327 ± 70341.742  ops/s
ExtractFieldsBenchmark.readSpray       10   TreeMap  thrpt    5   233510.618 ± 69638.750  ops/s
ExtractFieldsBenchmark.readSpray      100   HashMap  thrpt    5    23202.016 ±  1514.763  ops/s
ExtractFieldsBenchmark.readSpray      100   TreeMap  thrpt    5    21899.072 ±   823.225  ops/s
ExtractFieldsBenchmark.readSpray     1000   HashMap  thrpt    5     2073.754 ±    66.093  ops/s
ExtractFieldsBenchmark.readSpray     1000   TreeMap  thrpt    5     1793.329 ±    43.603  ops/s
ExtractFieldsBenchmark.readSpray    10000   HashMap  thrpt    5      208.160 ±     7.466  ops/s
ExtractFieldsBenchmark.readSpray    10000   TreeMap  thrpt    5      160.349 ±     5.809  ops/s
  • Loading branch information
jrudolph committed Oct 30, 2018
1 parent caee7a0 commit f89bf5b
Show file tree
Hide file tree
Showing 2 changed files with 8 additions and 6 deletions.
7 changes: 4 additions & 3 deletions src/main/scala/spray/json/JsValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package spray.json

import collection.immutable
import scala.collection.immutable.TreeMap

/**
* The general type of a JSON AST node.
Expand Down Expand Up @@ -53,10 +54,10 @@ case class JsObject(fields: Map[String, JsValue]) extends JsValue {
def getFields(fieldNames: String*): immutable.Seq[JsValue] = fieldNames.toIterator.flatMap(fields.get).toList
}
object JsObject {
val empty = JsObject(Map.empty[String, JsValue])
def apply(members: JsField*) = new JsObject(Map(members: _*))
val empty = JsObject(TreeMap.empty[String, JsValue])
def apply(members: JsField*) = new JsObject(TreeMap(members: _*))
@deprecated("Use JsObject(JsValue*) instead", "1.3.0")
def apply(members: List[JsField]) = new JsObject(Map(members: _*))
def apply(members: List[JsField]) = apply(members: _*)
}

/**
Expand Down
7 changes: 4 additions & 3 deletions src/main/scala/spray/json/JsonParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ package spray.json

import scala.annotation.{switch, tailrec}
import java.lang.{StringBuilder => JStringBuilder}
import java.nio.{CharBuffer, ByteBuffer}
import java.nio.{ByteBuffer, CharBuffer}
import java.nio.charset.Charset

import scala.collection.immutable.TreeMap

/**
* Fast, no-dependency parser for JSON as defined by http://tools.ietf.org/html/rfc4627.
*/
Expand Down Expand Up @@ -86,8 +88,7 @@ class JsonParser(input: ParserInput) {
val nextMap = map.updated(key, jsValue)
if (ws(',')) members(nextMap) else nextMap
}
var map = Map.empty[String, JsValue]
map = members(map)
val map = members(TreeMap.empty[String, JsValue])
require('}')
JsObject(map)
} else {
Expand Down

0 comments on commit f89bf5b

Please sign in to comment.