-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Use initialization checker for soundness #11716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
07a2e41
2cefd3e
aaffb11
fe38137
74bcfd7
d28f147
ad1aac0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -107,7 +107,7 @@ object Summarization { | |
|
||
case Typed(expr, tpt) => | ||
if (tpt.tpe.hasAnnotation(defn.UncheckedAnnot)) Summary.empty | ||
else analyze(expr) | ||
else analyze(expr) ++ effectsOfType(tpt.tpe, tpt) | ||
|
||
case NamedArg(name, arg) => | ||
analyze(arg) | ||
|
@@ -140,7 +140,7 @@ object Summarization { | |
val Summary(pots, effs) = analyze(selector) | ||
val init = Summary(Potentials.empty, pots.promote(selector) ++ effs) | ||
cases.foldLeft(init) { (acc, cas) => | ||
acc union analyze(cas.body) | ||
acc + analyze(cas.body) | ||
} | ||
|
||
// case CaseDef(pat, guard, body) => | ||
|
@@ -162,7 +162,7 @@ object Summarization { | |
|
||
case Try(block, cases, finalizer) => | ||
val Summary(pots, effs) = cases.foldLeft(analyze(block)) { (acc, cas) => | ||
acc union analyze(cas.body) | ||
acc + analyze(cas.body) | ||
} | ||
val Summary(_, eff2) = if (finalizer.isEmpty) Summary.empty else analyze(finalizer) | ||
Summary(pots, effs ++ eff2) | ||
|
@@ -199,8 +199,9 @@ object Summarization { | |
Summary(pots.promote(ddef) ++ effs) | ||
} | ||
|
||
case _: TypeDef => | ||
Summary.empty | ||
case tdef: TypeDef => | ||
if tdef.isClassDef then Summary.empty | ||
else Summary(effectsOfType(tdef.symbol.info, tdef.rhs)) | ||
|
||
case _: Import | _: Export => | ||
Summary.empty | ||
|
@@ -213,6 +214,19 @@ object Summarization { | |
else summary | ||
} | ||
|
||
private def effectsOfType(tp: Type, source: Tree)(implicit env: Env): Effects = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this function calculating the effects of constructing an instance of type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's calculating field access effects in types. In Scala we can have types like |
||
var summary = Summary.empty | ||
val traverser = new TypeTraverser { | ||
def traverse(tp: Type): Unit = tp match { | ||
case TermRef(_: SingletonType, _) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we restrict to SingletonType here as a prefix of the TermRef? What could the prefix be other than a SingletonType and why is it OK to skip those cases? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the community build, the project effpi (which use compiler plugin for session protocol checking) generates types like the following:
The type looks dubious, but the compiler seems to be fine with it. For such a type, our code will simply recur on the prefix There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still confused. That type is a TypeRef, right? And we recurse on its prefix below in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't make it clear: it's a display problem, it's a TermRef where the prefix is a TypeRef. If we only have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, sorry, yes, this makes sense now. Then I just wonder why the restriction is for the prefix to be a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the prefix can also be |
||
summary = summary + analyze(tp, source) | ||
case _ => | ||
traverseChildren(tp) | ||
} | ||
} | ||
traverser.traverse(tp) | ||
summary.effs | ||
|
||
def analyze(tp: Type, source: Tree)(implicit env: Env): Summary = | ||
trace("summarizing " + tp.show, init, s => s.asInstanceOf[Summary].show) { | ||
val summary: Summary = tp match { | ||
|
@@ -243,8 +257,12 @@ object Summarization { | |
} | ||
Summary(pots2, effs) | ||
|
||
case _: TermParamRef | _: RecThis => | ||
// possible from checking effects of types | ||
Summary.empty | ||
|
||
case _ => | ||
throw new Exception("unexpected type: " + tp.show) | ||
throw new Exception("unexpected type: " + tp) | ||
} | ||
|
||
if (env.isAlwaysInitialized(tp)) Summary(Potentials.empty, summary.effs) | ||
|
@@ -290,7 +308,7 @@ object Summarization { | |
def parentArgEffsWithInit(stats: List[Tree], ctor: Symbol, source: Tree): Effects = | ||
val init = | ||
if env.canIgnoreMethod(ctor) then Effects.empty | ||
else Effects.empty + MethodCall(ThisRef()(source), ctor)(source) | ||
else Effects.empty :+ MethodCall(ThisRef()(source), ctor)(source) | ||
stats.foldLeft(init) { (acc, stat) => | ||
val summary = Summarization.analyze(stat) | ||
acc ++ summary.effs | ||
|
@@ -320,7 +338,7 @@ object Summarization { | |
if tref.prefix == NoPrefix then Effects.empty | ||
else Summarization.analyze(tref.prefix, ref).effs | ||
|
||
prefixEff + MethodCall(ThisRef()(ref), ctor)(ref) | ||
prefixEff :+ MethodCall(ThisRef()(ref), ctor)(ref) | ||
} | ||
}) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
class A { | ||
trait Cov[+X] { | ||
def get: X | ||
} | ||
trait Bounded { | ||
type T >: Cov[Int] <: Cov[String] | ||
} | ||
val t: Bounded = new Bounded { // error | ||
// Note: using this instead of t produces an error (as expected) | ||
override type T >: t.T <: t.T | ||
} | ||
|
||
val covInt = new Cov[Int] { | ||
override def get: Int = 3 | ||
} | ||
val str: String = ((covInt: t.T): Cov[String]).get // ClassCastException: class Integer cannot be cast to class String | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
object App { | ||
trait A { type L >: Any} | ||
def upcast(a: A, x: Any): a.L = x | ||
val p: A { type L <: Nothing } = p // error | ||
def coerce(x: Any): Int = upcast(p, x) | ||
|
||
def main(args: Array[String]): Unit = { | ||
println(coerce("Uh oh!")) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
object App { | ||
def coerce[U, V](u: U): V = { | ||
trait X { type R >: U } | ||
trait Y { type R = V } | ||
|
||
class T[A <: X](val a: A)(val value: a.R) | ||
|
||
object O { val x : Y & X = x } // error | ||
|
||
val a = new T[Y & X](O.x)(u) | ||
a.value | ||
} | ||
|
||
def main(args: Array[String]): Unit = { | ||
val x: Int = coerce[String, Int]("a") | ||
println(x + 1) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
class C[T] { | ||
val a: T = method | ||
def method = b | ||
val b: T = a // error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
class B { | ||
val a: String = (((1: Any): b.A): Nothing): String | ||
val b: { type A >: Any <: Nothing } = loop() // error | ||
def loop(): Nothing = loop() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
object bar { | ||
trait Sub { | ||
type M | ||
type L <: M | ||
type U >: M | ||
type M2 >: L <: U | ||
} | ||
class Box[V](val v: V) | ||
|
||
class Caster[LL, UU] { | ||
trait S2 { | ||
type L = LL | ||
type U = UU | ||
} | ||
final lazy val nothing: Nothing = nothing | ||
final lazy val sub: S2 & Sub = nothing | ||
final lazy val box : Box[S2 & Sub] = new Box(nothing) | ||
def upcast(t: box.v.M2): box.v.M2 = t // error // error | ||
} | ||
def main(args : Array[String]) : Unit = { | ||
val zero : String = (new Caster[Int,String]()).upcast(0) | ||
println("...") | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the change from Set to Vector related to checking
TypeDef
s, or is it an orthogonal improvement?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's orthogonal. Set is bad for concatenation, and we can afford to have duplicate entries in summaries.