Commit 0bc09644 authored by Kunshan Wang's avatar Kunshan Wang

More static checkings.

parent 974a9118
package uvm.staticanalysis
import uvm._
import uvm.types._
import uvm.ssavariables._
import org.slf4j.LoggerFactory
import com.typesafe.scalalogging.Logger
object StaticAnalyzer {
val logger: Logger = Logger(LoggerFactory.getLogger(getClass.getName))
}
class StaticAnalyzer {
}
\ No newline at end of file
import StaticAnalyzer._
type MutableSet[T] = collection.mutable.HashSet[T]
val MutableSet = collection.mutable.HashSet
type MutableMap[K, V] = collection.mutable.HashMap[K, V]
val MutableMap = collection.mutable.HashMap
type MutableQueue[T] = collection.mutable.Queue[T]
val MutableQueue = collection.mutable.Queue
type MutableStack[T] = collection.mutable.Stack[T]
val MutableStack = collection.mutable.Stack
def checkBundle(bundle: Bundle, parentBundle: Option[GlobalBundle]): Unit = {
new BundleChecker(bundle, parentBundle).check()
}
class BundleChecker(bundle: Bundle, parentBundle: Option[GlobalBundle]) {
def check(): Unit = {
checkTypes()
checkSigs()
checkConsts()
}
def checkTypes(): Unit = {
val compositeTypes = bundle.typeNs.all.flatMap {
case ty: AbstractCompositeType => Some(ty)
case _ => None
}.toSeq
checkCompositeTypesNotRecursive(compositeTypes)
}
def checkCompositeTypesNotRecursive(compTys: Seq[AbstractCompositeType]): Unit = {
val world = MutableSet(compTys: _*) // All types in this bundle. Assume all other types are valid.
val visited = MutableSet[Type]()
for (rootTy <- world if !visited.contains(rootTy)) {
val inStack = MutableSet[AbstractCompositeType]()
def visit(ty: AbstractCompositeType): Unit = {
logger.debug("Checking composite type %s".format(ty.repr))
visited(ty) = true
inStack(ty) = true
val succs = ty match {
case TypeStruct(fieldTys) => fieldTys
case TypeArray(elemTy, _) => Seq(elemTy)
case TypeVector(elemTy, _) => Seq(elemTy)
case TypeHybrid(fixedTys, varTy) => fixedTys ++ Seq(varTy)
}
succs foreach {
case succ @ TypeHybrid(fixedTys, varTy) =>
throw error("Composite type %s contains hybrid %s".format(ty.repr, succ.repr),
pretty = Seq(ty, succ))
case succ @ TypeVoid() =>
throw error("Composite type %s contains void %s".format(ty.repr, succ.repr),
pretty = Seq(ty, succ))
case succ: AbstractCompositeType => {
if (inStack(succ)) {
throw error("Composite type %s contains itself or its parent %s".format(ty.repr, succ.repr),
pretty = Seq(ty, succ))
} else if (!visited(succ) && world.contains(succ)) {
visit(succ)
} else {
// Ignore visited or out-of-bundle types.
}
}
case _ => // do nothing if it is not composite
}
inStack(ty) = false
}
visit(rootTy)
}
}
def checkValueType(ty: Type): Unit = {
val invalidTypeKind = ty match {
case _: TypeWeakRef => Some("weak reference")
case _: TypeHybrid => Some("hybrid")
case _: TypeVoid => Some("void")
case _ => None
}
invalidTypeKind.foreach { kind =>
throw error("%s %s cannot be the type of an SSA variable".format(kind, ty.repr),
pretty = Seq(ty))
}
}
def checkSigs(): Unit = {
for (sig <- bundle.funcSigNs.all) {
checkSig(sig)
}
}
def checkSig(sig: FuncSig): Unit = {
for (ty <- sig.paramTys ++ sig.retTys) try {
checkValueType(ty)
} catch {
case e: StaticCheckingException => throw error("In function signature %s: %s".format(sig.repr, e.getMessage),
pretty = Seq(sig), cause = e.getCause)
}
}
def checkConsts(): Unit = {
for (c <- bundle.constantNs.all) {
checkScalarConst(c)
}
val compositeConsts = bundle.constantNs.all.flatMap {
case c: ConstSeq => Some(c)
case _ => None
}.toSeq
checkCompositeConstantsNotRecursive(compositeConsts)
}
def checkScalarConst(c: Constant): Unit = {
c match {
case cc @ ConstInt(ty, _) => ty match {
case TypeInt(_) =>
case TypeUPtr(_) =>
case TypeUFuncPtr(_) =>
case _ => {
throw error("Constant %s: int literal is not suitable for type %s".format(c.repr, ty.repr),
pretty = Seq(c, ty))
}
}
case cc @ ConstFloat(ty, _) => ty match {
case TypeFloat() =>
case _ => {
throw error("Constant %s: float literal is not suitable for type %s".format(c.repr, ty.repr),
pretty = Seq(c, ty))
}
}
case cc @ ConstDouble(ty, _) => ty match {
case TypeDouble() =>
case _ => {
throw error("Constant %s: double literal is not suitable for type %s".format(c.repr, ty.repr),
pretty = Seq(c, ty))
}
}
case cc @ ConstNull(ty) => ty match {
case TypeRef(_) =>
case TypeIRef(_) =>
case TypeWeakRef(_) => {
throw error("Constant %s: type %s is a weakref, which cannot have values.".format(c.repr, ty.repr),
pretty = Seq(c, ty))
}
case TypeFuncRef(_) =>
case TypeStackRef() =>
case TypeThreadRef() =>
case TypeFrameCursorRef() =>
case _ => {
throw error("Constant %s: NULL literal is not suitable for type %s".format(c.repr, ty.repr),
pretty = Seq(c, ty))
}
}
case cc @ ConstSeq(ty, elems) => // Ignore for now
}
}
def checkCompositeConstantsNotRecursive(compConsts: Seq[ConstSeq]): Unit = {
val world = MutableSet(compConsts: _*) // All ConstSeq instances in this bundle. Assume all other constants are valid.
val visited = MutableSet[ConstSeq]()
for (rootConst <- world if !visited.contains(rootConst)) {
val inStack = MutableSet[ConstSeq]()
def visit(c: ConstSeq): Unit = {
logger.debug("Checking composite constant %s".format(c.repr))
visited(c) = true
inStack(c) = true
val succs = c match {
case ConstSeq(ty, elems) => {
val expectedArity = ty match {
case t @ TypeStruct(fieldTys) => fieldTys.length
case t @ TypeArray(elemTy, sz) => sz
case t @ TypeVector(elemTy, sz) => sz
case _ => throw error("Constant %s: sequence literal is not suitable for type %s".format(c.repr, ty.repr),
pretty = Seq(c, ty))
}
val actualArity = elems.length
if (actualArity != expectedArity) {
throw error("Constant %s: type %s expects %d elements, but %d found".format(c.repr, ty.repr,
expectedArity, actualArity), pretty = Seq(c, ty))
}
elems
}
}
succs foreach {
case succ: ConstSeq => {
if (inStack(succ)) {
throw error("Constant %s contains itself or its parent %s".format(c.repr, succ.repr),
pretty = Seq(c, succ))
} else if (!visited.contains(succ) && world.contains(succ)) {
visit(succ)
} else {
// Ignore visited or out-of-bundle types.
}
}
case _ => // do nothing if it is not composite
}
inStack(c) = false
}
visit(rootConst)
}
}
def lookupSourceInfo(obj: AnyRef): SourceInfo = {
val si1 = bundle.sourceInfoRepo(obj)
if (si1 == NoSourceInfo && parentBundle.isDefined) {
return parentBundle.get.sourceInfoRepo(obj)
} else {
return si1
}
}
def error(msg: String, pretty: Seq[AnyRef] = Seq(), cause: Throwable = null): StaticCheckingException = {
val prettyMsgs = pretty.map(o => lookupSourceInfo(o).prettyPrint())
new StaticCheckingException("%s\n%s".format(msg, prettyMsgs.mkString("\n")), cause)
}
}
}
class StaticCheckingException(message: String = null, cause: Throwable = null) extends UvmException(message, cause)
......@@ -7,9 +7,8 @@ import uvm.TrantientBundle
import uvm.utils.IDFactory
import org.scalatest.exceptions.TestFailedException
class SourceInfoRepoTest extends FlatSpec with Matchers
with TestingBundlesValidators {
class SourceInfoRepoTest extends FlatSpec with Matchers {
def parseText(globalBundle: GlobalBundle, fac: Option[IDFactory]=None)(uir: String): TrantientBundle = {
val idf = fac.getOrElse(new IDFactory(uvm.refimpl.MicroVM.FIRST_CLIENT_USABLE_ID))
val r = new UIRTextReader(idf)
......@@ -159,4 +158,31 @@ class SourceInfoRepoTest extends FlatSpec with Matchers
.funcsig @i64 = () -> ()
""")
}
it should "produce error when a function is defined twice in one bundle" in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.funcsig @foo = () -> ()
.funcdef @f VERSION %v1 <@foo> {
%entry():
RET ()
}
.funcdef @f VERSION %v2 <@foo> {
%entry():
RET ()
}
""")
}
it should "produce error when a function is declared and defined in one bundle" in {
catchExceptionWhenParsing("""
.typedef @i64 = int<64>
.funcsig @foo = () -> ()
.funcdecl @f <@foo>
.funcdef @f VERSION %v1 <@foo> {
%entry():
RET ()
}
""")
}
}
\ No newline at end of file
package uvm.staticanalysis
import org.scalatest.FlatSpec
import org.scalatest.Matchers
import org.scalatest.exceptions.TestFailedException
import uvm.GlobalBundle
import uvm.TrantientBundle
import uvm.ir.textinput.UIRTextReader
import uvm.utils.IDFactory
class StaticAnalysisTest extends FlatSpec with Matchers {
def parseText(globalBundle: GlobalBundle, fac: Option[IDFactory]=None)(uir: String): TrantientBundle = {
val idf = fac.getOrElse(new IDFactory(uvm.refimpl.MicroVM.FIRST_CLIENT_USABLE_ID))
val r = new UIRTextReader(idf)
val ir = r.read(new java.io.StringReader(uir), globalBundle)
ir
}
behavior of "StaticAnalyzer"
def shouldWorkFineIn(text: String): Unit = {
val gb = new GlobalBundle()
val b = parseText(gb)(text)
new StaticAnalyzer().checkBundle(b, Some(gb))
}
def catchExceptionWhenAnalyzing(text: String): Unit = {
val gb = new GlobalBundle()
val b = parseText(gb)(text)
try {
new StaticAnalyzer().checkBundle(b, Some(gb))
fail()
} catch {
case e: TestFailedException => throw e
case e: Exception => // expected
e.printStackTrace()
}
}
it should "complain if a struct contains itself" in {
catchExceptionWhenAnalyzing("""
.typedef @i64 = int<64>
.typedef @s = struct<@i64 @i64 @s @i64>
""")
}
it should "complain if a struct contains its parent" in {
catchExceptionWhenAnalyzing("""
.typedef @i64 = int<64>
.typedef @s = struct<@i64 @i64 @t @i64>
.typedef @t = struct<@i64 @s @i64>
""")
}
it should "not complain if the type is recursive on refs" in {
shouldWorkFineIn("""
.typedef @i64 = int<64>
.typedef @s = struct<@i64 @i64 @t @i64>
.typedef @t = struct<@i64 @refs @i64>
.typedef @refs = ref<@s>
""")
}
it should "complain if an array type is recursive" in {
catchExceptionWhenAnalyzing("""
.typedef @i64 = int<64>
.typedef @s = struct<@i64 @i64 @a @i64>
.typedef @a = array<@s 10>
""")
}
it should "complain if a vector type is recursive" in {
catchExceptionWhenAnalyzing("""
.typedef @i64 = int<64>
.typedef @v = vector<@a 2>
.typedef @a = array<@v 10>
""")
}
it should "complain if a hybrid is contained in other composite types" in {
catchExceptionWhenAnalyzing("""
.typedef @i64 = int<64>
.typedef @s = struct<@i64 @i64 @h @i64>
.typedef @h = hybrid<@i64 @i64>
""")
}
it should "not complain if a hybrid contains any composite types" in {
shouldWorkFineIn("""
.typedef @i64 = int<64>
.typedef @s = struct<@i64 @i64 @i64>
.typedef @h = hybrid<@s @s>
""")
}
it should "complain if a function returns void" in {
catchExceptionWhenAnalyzing("""
.typedef @void = void
.funcsig @sig = () -> (@void)
""")
}
it should "complain if a hybrid is used for value types" in {
catchExceptionWhenAnalyzing("""
.typedef @i8 = int<8>
.typedef @hybrid = hybrid<@i8>
.funcsig @sig = (@hybrid) -> ()
""")
}
it should "complain if a weak reference is used for value types" in {
catchExceptionWhenAnalyzing("""
.typedef @i8 = int<8>
.typedef @wr = weakref<@i8>
.funcsig @sig = (@wr) -> ()
""")
}
it should "complain if a int literal is used on non-integer or non-pointer types" in {
catchExceptionWhenAnalyzing("""
.typedef @double = double
.const @C <@double> = 100
""")
}
it should "complain if a float literal is used on non-float types" in {
catchExceptionWhenAnalyzing("""
.typedef @double = double
.const @C <@double> = 3.14f
""")
}
it should "complain if a double literal is used on non-double types" in {
catchExceptionWhenAnalyzing("""
.typedef @i64 = int<64>
.const @C <@i64> = 3.14d
""")
}
it should "complain if a NULL literal is used on non-reference types" in {
catchExceptionWhenAnalyzing("""
.typedef @i64 = int<64>
.typedef @ptri64 = uptr<@i64>
.const @C <@ptri64> = NULL
""")
}
it should "complain if a sequence literal is used on non-composite types" in {
catchExceptionWhenAnalyzing("""
.typedef @i64 = int<64>
.const @C1 <@i64> = 10
.const @C <@i64> = { @C1 @C1 @C1 }
""")
}
it should "complain if a sequence literal has the wrong arity for structs" in {
catchExceptionWhenAnalyzing("""
.typedef @i64 = int<64>
.typedef @s = struct<@i64 @i64 @i64>
.const @C1 <@i64> = 10
.const @C <@s> = { @C1 @C1 @C1 @C1}
""")
}
it should "complain if a sequence literal has the wrong arity for arrays" in {
catchExceptionWhenAnalyzing("""
.typedef @i64 = int<64>
.typedef @a = array<@i64 4>
.const @C1 <@i64> = 10
.const @C <@a> = { @C1 @C1 @C1}
""")
}
it should "complain if a sequence literal has the wrong arity for vectors" in {
catchExceptionWhenAnalyzing("""
.typedef @i64 = int<64>
.typedef @v = vector<@i64 4>
.const @C1 <@i64> = 10
.const @C <@v> = { @C1 @C1 @C1}
""")
}
it should "complain if a sequence literal is recursive" in {
catchExceptionWhenAnalyzing("""
.typedef @i64 = int<64>
.typedef @s = struct<@i64 @i64 @i64>
.const @C1 <@i64> = 10
.const @C <@s> = { @C @C @C }
""")
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment