Skip to content

Commit

Permalink
Make string Injections safer. Fix #199
Browse files Browse the repository at this point in the history
  • Loading branch information
oscar-stripe committed Jan 22, 2016
1 parent cac17f1 commit 40f668a
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.twitter.bijection

import java.util.concurrent.atomic.AtomicReference

/**
* This class is for sharing an object between threads
* when allocation of a new one is not that cheap, but
* better than blocking.
*/
private[bijection] class AtomicSharedState[T <: AnyRef](cons: () => T) {
private[this] val ref = new AtomicReference[T](cons())

def get: T = {
val borrow = ref.getAndSet(null.asInstanceOf[T])
if (null == borrow) cons()
else borrow
}
def release(t: T): Unit = {
ref.lazySet(t)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ limitations under the License.

package com.twitter.bijection

import com.twitter.bijection.Inversion.attempt
import java.net.{ URLDecoder, URLEncoder, URL }
import java.util.UUID
import java.nio.charset.{Charset, CodingErrorAction}
import java.nio.ByteBuffer

import java.util.UUID
import scala.annotation.tailrec
import scala.collection.generic.CanBuildFrom

import com.twitter.bijection.Inversion.attempt
import scala.util.Try

trait StringInjections extends NumericInjections {
Expand All @@ -32,9 +33,22 @@ trait StringInjections extends NumericInjections {

def withEncoding(encoding: String): Injection[String, Array[Byte]] =
new AbstractInjection[String, Array[Byte]] {
private[this] val decRef = {
val cs = Charset.forName(encoding)
new AtomicSharedState(() => cs.newDecoder.onUnmappableCharacter(CodingErrorAction.REPORT))
}

def apply(s: String) = s.getBytes(encoding)
override def invert(b: Array[Byte]) =
attempt(b) { new String(_, encoding) }
// toString on ByteBuffer is nicer, so use it
attempt(ByteBuffer.wrap(b)) { bb =>
//these are mutable, so it can't be shared trivially
//avoid GC pressure and (probably) perform better
val dec = decRef.get
val str = dec.decode(bb).toString
decRef.release(dec)
str
}
}

// Some bijections with string from standard java/scala classes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ object StringArbs extends BaseProperties {
class StringBijectionLaws extends CheckProperties with BaseProperties {

property("round trips string -> Array[String]") {
isLooseInjection[String, Array[Byte]]
isInjection[String, Array[Byte]]
}

implicit val symbol = arbitraryViaFn { (s: String) => Symbol(s) }
Expand Down

0 comments on commit 40f668a

Please sign in to comment.