Skip to content

Commit

Permalink
Add JsonProtocol.serializeRecover (#2227)
Browse files Browse the repository at this point in the history
This function will safely wrap any unserializeable annotations in
UnserializeableAnnotations so that they can be safely serialized to JSON
for logging.
  • Loading branch information
jackkoenig committed May 14, 2021
1 parent f33b931 commit c2d72fd
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 15 deletions.
12 changes: 1 addition & 11 deletions src/main/scala/firrtl/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -214,17 +214,7 @@ private[firrtl] object Transform {
val remappedAnnotations = propagateAnnotations(name, logger, before.annotations, after.annotations, after.renames)

logger.trace(s"Annotations:")
logger.trace {
JsonProtocol
.serializeTry(remappedAnnotations)
.recoverWith {
case NonFatal(e) =>
val msg = s"Exception thrown during Annotation serialization:\n " +
e.toString.replaceAll("\n", "\n ")
Try(msg)
}
.get
}
logger.trace(JsonProtocol.serializeRecover(remappedAnnotations))

logger.trace(s"Circuit:\n${after.circuit.serialize}")

Expand Down
29 changes: 26 additions & 3 deletions src/main/scala/firrtl/annotations/JsonProtocol.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package annotations

import firrtl.ir._

import scala.util.{Failure, Try}
import scala.util.{Failure, Success, Try}

import org.json4s._
import org.json4s.native.JsonMethods._
Expand All @@ -19,6 +19,9 @@ trait HasSerializationHints {
def typeHints: Seq[Class[_]]
}

/** Wrapper [[Annotation]] for Annotations that cannot be serialized */
case class UnserializeableAnnotation(error: String, content: String) extends NoTargetAnnotation

object JsonProtocol {
class TransformClassSerializer
extends CustomSerializer[Class[_ <: Transform]](format =>
Expand Down Expand Up @@ -227,14 +230,17 @@ object JsonProtocol {
): Seq[(Annotation, Throwable)] =
annos.map(a => a -> Try(write(a))).collect { case (a, Failure(e)) => (a, e) }

def serializeTry(annos: Seq[Annotation]): Try[String] = {
val tags = annos
private def getTags(annos: Seq[Annotation]): Seq[Class[_]] =
annos
.flatMap({
case anno: HasSerializationHints => anno.getClass +: anno.typeHints
case anno => Seq(anno.getClass)
})
.distinct

def serializeTry(annos: Seq[Annotation]): Try[String] = {
val tags = getTags(annos)

implicit val formats = jsonFormat(tags)
Try(writePretty(annos)).recoverWith {
case e: org.json4s.MappingException =>
Expand All @@ -243,6 +249,23 @@ object JsonProtocol {
}
}

/** Serialize annotations to JSON while wrapping unserializeable ones with [[UnserializeableAnnotation]]
*
* @note this is slower than standard serialization
*/
def serializeRecover(annos: Seq[Annotation]): String = {
val tags = classOf[UnserializeableAnnotation] +: getTags(annos)
implicit val formats = jsonFormat(tags)

val safeAnnos = annos.map { anno =>
Try(write(anno)) match {
case Success(_) => anno
case Failure(e) => UnserializeableAnnotation(e.getMessage, anno.toString)
}
}
writePretty(safeAnnos)
}

def deserialize(in: JsonInput): Seq[Annotation] = deserializeTry(in).get

def deserializeTry(in: JsonInput): Try[Seq[Annotation]] = Try({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
package firrtlTests.annotationTests

import firrtl._
import firrtl.annotations.{JsonProtocol, NoTargetAnnotation, UnserializableAnnotationException}
import firrtl.annotations._
import firrtl.ir._
import firrtl.options.Dependency
import firrtl.transforms.DontTouchAnnotation
import scala.util.Failure
import _root_.logger.{LogLevel, LogLevelAnnotation, Logger}
import org.scalatest.flatspec.AnyFlatSpec
Expand Down Expand Up @@ -77,4 +78,13 @@ class JsonProtocolSpec extends AnyFlatSpec with Matchers {
e.getMessage should include("Classes defined in method bodies are not supported")
}
}
"JsonProtocol.serializeRecover" should "emit even annotations that cannot be serialized" in {
case class MyAnno(x: Int) extends NoTargetAnnotation
val target = CircuitTarget("Top").module("Foo").ref("x")
val annos = MyAnno(3) :: DontTouchAnnotation(target) :: Nil
val res = JsonProtocol.serializeRecover(annos)
res should include(""""class":"firrtl.annotations.UnserializeableAnnotation",""")
res should include(""""error":"Classes defined in method bodies are not supported.",""")
res should include(""""content":"MyAnno(3)"""")
}
}

0 comments on commit c2d72fd

Please sign in to comment.