Skip to content

Commit 04dc65f

Browse files
author
EnzeXing
committed
Address comments; add TopWidenedValue
1 parent 61298ad commit 04dc65f

File tree

1 file changed

+82
-45
lines changed

1 file changed

+82
-45
lines changed

Diff for: compiler/src/dotty/tools/dotc/transform/init/Objects.scala

+82-45
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,11 @@ class Objects(using Context @constructorOnly):
9393
* | OfClass(class, vs[outer], ctor, args, env) // instance of a class
9494
* | OfArray(object[owner], regions)
9595
* | Fun(..., env) // value elements that can be contained in ValueSet
96-
* | SafeValue // values on which method calls and fields won't cause warnings. Int, String, etc.
96+
* | SafeValue // values on which method calls and field accesses won't cause warnings. Int, String, etc.
97+
* | UnknownValue
9798
* vs ::= ValueSet(ve) // set of abstract values
9899
* Bottom ::= ValueSet(Empty)
99-
* val ::= ve | UnknownValue | vs | Package // all possible abstract values in domain
100+
* val ::= ve | TopWidenedValue | vs | Package // all possible abstract values in domain
100101
* Ref ::= ObjectRef | OfClass // values that represent a reference to some (global or instance) object
101102
* ThisValue ::= Ref | UnknownValue // possible values for 'this'
102103
*
@@ -190,7 +191,7 @@ class Objects(using Context @constructorOnly):
190191

191192
def show(using Context) =
192193
val valFields = vals.map(_.show + " -> " + _.show)
193-
"OfClass(" + klass.show + ", outer = " + outer + ", args = " + args.map(_.show) + ", vals = " + valFields + ")"
194+
"OfClass(" + klass.show + ", outer = " + outer + ", args = " + args.map(_.show) + " env = " + env.show + ", vals = " + valFields + ")"
194195

195196
object OfClass:
196197
def apply(
@@ -229,17 +230,24 @@ class Objects(using Context @constructorOnly):
229230

230231
/**
231232
* Represents common base values like Int, String, etc.
232-
* Assumption: all methods calls on such values should be pure (no side effects)
233+
* Assumption: all methods calls on such values should not trigger initialization of global objects
234+
* or read/write mutable fields
233235
*/
234236
case class SafeValue(tpe: Type) extends ValueElement:
235237
// tpe could be a AppliedType(java.lang.Class, T)
236238
val baseType = if tpe.isInstanceOf[AppliedType] then tpe.asInstanceOf[AppliedType].underlying else tpe
237-
assert(baseType.isInstanceOf[TypeRef] && SafeValue.safeTypes.contains(baseType), "Invalid creation of SafeValue! Type = " + tpe)
238-
val typeref = baseType.asInstanceOf[TypeRef]
239-
def show(using Context): String = "SafeValue of type " + tpe
239+
assert(baseType.isInstanceOf[TypeRef], "Invalid creation of SafeValue! Type = " + tpe)
240+
val typeSymbol = baseType.asInstanceOf[TypeRef].symbol
241+
assert(SafeValue.safeTypeSymbols.contains(typeSymbol), "Invalid creation of SafeValue! Type = " + tpe)
242+
def show(using Context): String = "SafeValue of " + typeSymbol.show
243+
override def equals(that: Any): Boolean =
244+
that.isInstanceOf[SafeValue] && that.asInstanceOf[SafeValue].typeSymbol == typeSymbol
240245

241246
object SafeValue:
242-
val safeTypes = defn.ScalaNumericValueTypeList ++ List(defn.UnitType, defn.BooleanType, defn.StringType, defn.NullType, defn.ClassClass.typeRef)
247+
val safeTypeSymbols =
248+
(defn.ScalaNumericValueTypeList ++
249+
List(defn.UnitType, defn.BooleanType, defn.StringType.asInstanceOf[TypeRef], defn.NullType, defn.ClassClass.typeRef))
250+
.map(_.symbol)
243251

244252
/**
245253
* Represents a set of values
@@ -253,20 +261,26 @@ class Objects(using Context @constructorOnly):
253261
def show(using Context): String = "Package(" + packageSym.show + ")"
254262

255263
/** Represents values unknown to the checker, such as values loaded without source
264+
*/
265+
case object UnknownValue extends ValueElement:
266+
def show(using Context): String = "UnknownValue"
267+
268+
/** Represents values lost due to widening
256269
*
257270
* This is the top of the abstract domain lattice, which should not
258271
* be used during initialization.
259272
*
260-
* UnknownValue is not ValueElement since RefSet containing UnknownValue
261-
* is equivalent to UnknownValue
262-
*/
263-
case object UnknownValue extends Value:
264-
def show(using Context): String = "UnknownValue"
273+
* TopWidenedValue is not ValueElement since RefSet containing TopWidenedValue
274+
* is equivalent to TopWidenedValue
275+
*/
276+
277+
case object TopWidenedValue extends Value:
278+
def show(using Context): String = "TopWidenedValue"
265279

266280
val Bottom = ValueSet(ListSet.empty)
267281

268282
/** Possible types for 'this' */
269-
type ThisValue = Ref | UnknownValue.type
283+
type ThisValue = Ref | TopWidenedValue.type
270284

271285
/** Checking state */
272286
object State:
@@ -623,8 +637,8 @@ class Objects(using Context @constructorOnly):
623637
extension (a: Value)
624638
def join(b: Value): Value =
625639
(a, b) match
626-
case (UnknownValue, _) => UnknownValue
627-
case (_, UnknownValue) => UnknownValue
640+
case (TopWidenedValue, _) => TopWidenedValue
641+
case (_, TopWidenedValue) => TopWidenedValue
628642
case (Package(_), _) => UnknownValue // should not happen
629643
case (_, Package(_)) => UnknownValue
630644
case (Bottom, b) => b
@@ -640,8 +654,8 @@ class Objects(using Context @constructorOnly):
640654
case (a: Ref, b: Ref) if a.equals(b) => Bottom
641655
case _ => a
642656

643-
def widen(height: Int)(using Context): Value =
644-
if height == 0 then UnknownValue
657+
def widen(height: Int)(using Context): Value = log("widening value " + a.show + " down to height " + height, printer, (_: Value).show) {
658+
if height == 0 then TopWidenedValue
645659
else
646660
a match
647661
case Bottom => Bottom
@@ -659,6 +673,7 @@ class Objects(using Context @constructorOnly):
659673
ref.widenedCopy(outer2, args2, env2)
660674

661675
case _ => a
676+
}
662677

663678
def filterType(tpe: Type)(using Context): Value =
664679
tpe match
@@ -671,21 +686,24 @@ class Objects(using Context @constructorOnly):
671686
// Filter the value according to a class symbol, and only leaves the sub-values
672687
// which could represent an object of the given class
673688
def filterClass(sym: Symbol)(using Context): Value =
674-
if !sym.isClass then a
675-
else
676-
val klass = sym.asClass
677-
a match
678-
case UnknownValue => UnknownValue
679-
case Package(_) => a
680-
case SafeValue(_) => a
681-
case ref: Ref => if ref.klass.isSubClass(klass) then ref else Bottom
682-
case ValueSet(values) => values.map(v => v.filterClass(klass)).join
683-
case arr: OfArray => if defn.ArrayClass.isSubClass(klass) then arr else Bottom
684-
case fun: Fun =>
685-
if klass.isOneOf(AbstractOrTrait) && klass.baseClasses.exists(defn.isFunctionClass) then fun else Bottom
686-
687-
extension (value: Ref | UnknownValue.type)
688-
def widenRefOrCold(height : Int)(using Context) : Ref | UnknownValue.type = value.widen(height).asInstanceOf[ThisValue]
689+
if !sym.isClass then a
690+
else
691+
val klass = sym.asClass
692+
a match
693+
case UnknownValue | TopWidenedValue => a
694+
case Package(packageSym) =>
695+
if packageSym.moduleClass.equals(sym) || (klass.denot.isPackageObject && klass.owner.equals(sym)) then a else Bottom
696+
case v: SafeValue => if v.typeSymbol.asClass.isSubClass(klass) then a else Bottom
697+
case ref: Ref => if ref.klass.isSubClass(klass) then ref else Bottom
698+
case ValueSet(values) => values.map(v => v.filterClass(klass)).join
699+
case arr: OfArray => if defn.ArrayClass.isSubClass(klass) then arr else Bottom
700+
case fun: Fun =>
701+
if klass.isOneOf(AbstractOrTrait) && klass.baseClasses.exists(defn.isFunctionClass) then fun else Bottom
702+
703+
extension (value: ThisValue)
704+
def widenRefOrCold(height : Int)(using Context) : ThisValue =
705+
assert(height > 0, "Cannot call widenRefOrCold with height 0!")
706+
value.widen(height).asInstanceOf[ThisValue]
689707

690708
extension (values: Iterable[Value])
691709
def join: Value = if values.isEmpty then Bottom else values.reduce { (v1, v2) => v1.join(v2) }
@@ -708,6 +726,9 @@ class Objects(using Context @constructorOnly):
708726
*/
709727
def call(value: Value, meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, needResolve: Boolean = true): Contextual[Value] = log("call " + meth.show + ", this = " + value.show + ", args = " + args.map(_.value.show), printer, (_: Value).show) {
710728
value.filterClass(meth.owner) match
729+
case TopWidenedValue =>
730+
report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position)
731+
Bottom
711732
case UnknownValue =>
712733
if reportUnknown then
713734
report.warning("Using unknown value. " + Trace.show, Trace.position)
@@ -716,10 +737,12 @@ class Objects(using Context @constructorOnly):
716737
UnknownValue
717738

718739
case Package(packageSym) =>
740+
if meth.equals(defn.throwMethod) then
741+
Bottom
719742
// calls on packages are unexpected. However the typer might mistakenly
720743
// set the receiver to be a package instead of package object.
721744
// See packageObjectStringInterpolator.scala
722-
if !meth.owner.denot.isPackageObject then
745+
else if !meth.owner.denot.isPackageObject then
723746
report.warning("[Internal error] Unexpected call on package = " + value.show + ", meth = " + meth.show + Trace.show, Trace.position)
724747
Bottom
725748
else
@@ -729,13 +752,13 @@ class Objects(using Context @constructorOnly):
729752

730753
case v @ SafeValue(tpe) =>
731754
// Assume such method is pure. Check return type, only try to analyze body if return type is not safe
732-
val target = resolve(v.typeref.symbol.asClass, meth)
755+
val target = resolve(v.typeSymbol.asClass, meth)
733756
if !target.hasSource then
734757
UnknownValue
735758
else
736759
val ddef = target.defTree.asInstanceOf[DefDef]
737760
val returnType = ddef.tpt.tpe
738-
if SafeValue.safeTypes.contains(returnType) then
761+
if SafeValue.safeTypeSymbols.contains(returnType.typeSymbol) then
739762
// since method is pure and return type is safe, no need to analyze method body
740763
SafeValue(returnType)
741764
else
@@ -800,7 +823,7 @@ class Objects(using Context @constructorOnly):
800823
if meth.owner.isClass then
801824
(ref, Env.NoEnv)
802825
else
803-
Env.resolveEnv(meth.owner.enclosingMethod, ref, summon[Env.Data]).getOrElse(UnknownValue -> Env.NoEnv)
826+
Env.resolveEnv(meth.owner.enclosingMethod, ref, summon[Env.Data]).getOrElse(TopWidenedValue -> Env.NoEnv)
804827

805828
val env2 = Env.ofDefDef(ddef, args.map(_.value), outerEnv)
806829
extendTrace(ddef) {
@@ -898,6 +921,9 @@ class Objects(using Context @constructorOnly):
898921
*/
899922
def select(value: Value, field: Symbol, receiver: Type, needResolve: Boolean = true): Contextual[Value] = log("select " + field.show + ", this = " + value.show, printer, (_: Value).show) {
900923
value.filterClass(field.owner) match
924+
case TopWidenedValue =>
925+
report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position)
926+
Bottom
901927
case UnknownValue =>
902928
if reportUnknown then
903929
report.warning("Using unknown value. " + Trace.show, Trace.position)
@@ -942,15 +968,15 @@ class Objects(using Context @constructorOnly):
942968
Bottom
943969
else
944970
// initialization error, reported by the initialization checker
945-
UnknownValue
971+
Bottom
946972
else if ref.hasVal(target) then
947973
ref.valValue(target)
948974
else if ref.isObjectRef && ref.klass.hasSource then
949975
report.warning("Access uninitialized field " + field.show + ". " + Trace.show, Trace.position)
950976
Bottom
951977
else
952978
// initialization error, reported by the initialization checker
953-
UnknownValue
979+
Bottom
954980

955981
else
956982
if ref.klass.isSubClass(receiver.widenSingleton.classSymbol) then
@@ -984,15 +1010,21 @@ class Objects(using Context @constructorOnly):
9841010
*/
9851011
def assign(lhs: Value, field: Symbol, rhs: Value, rhsTyp: Type): Contextual[Value] = log("Assign" + field.show + " of " + lhs.show + ", rhs = " + rhs.show, printer, (_: Value).show) {
9861012
lhs.filterClass(field.owner) match
1013+
case TopWidenedValue =>
1014+
report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position)
1015+
case UnknownValue =>
1016+
if reportUnknown then
1017+
report.warning("Assigning to unknown value. " + Trace.show, Trace.position)
1018+
end if
9871019
case p: Package =>
9881020
report.warning("[Internal error] unexpected tree in assignment, package = " + p.packageSym.show + Trace.show, Trace.position)
9891021
case fun: Fun =>
9901022
report.warning("[Internal error] unexpected tree in assignment, fun = " + fun.code.show + Trace.show, Trace.position)
9911023
case arr: OfArray =>
9921024
report.warning("[Internal error] unexpected tree in assignment, array = " + arr.show + " field = " + field + Trace.show, Trace.position)
9931025

994-
case SafeValue(_) | UnknownValue =>
995-
report.warning("Assigning to base or unknown value is forbidden. " + Trace.show, Trace.position)
1026+
case SafeValue(_) =>
1027+
report.warning("Assigning to base value is forbidden. " + Trace.show, Trace.position)
9961028

9971029
case ValueSet(values) =>
9981030
values.foreach(ref => assign(ref, field, rhs, rhsTyp))
@@ -1028,9 +1060,13 @@ class Objects(using Context @constructorOnly):
10281060
Bottom
10291061

10301062
case UnknownValue =>
1031-
UnknownValue
1063+
if reportUnknown then
1064+
report.warning("Instantiating when outer is unknown. " + Trace.show, Trace.position)
1065+
Bottom
1066+
else
1067+
UnknownValue
10321068

1033-
case outer: (Ref | UnknownValue.type | Package) =>
1069+
case outer: (Ref | TopWidenedValue.type | Package) =>
10341070
if klass == defn.ArrayClass then
10351071
args.head.tree.tpe match
10361072
case ConstantType(Constants.Constant(0)) =>
@@ -1046,7 +1082,7 @@ class Objects(using Context @constructorOnly):
10461082
outer match
10471083
case Package(_) => // For top-level classes
10481084
(outer, Env.NoEnv)
1049-
case thisV : (Ref | UnknownValue.type) =>
1085+
case thisV : ThisValue =>
10501086
if klass.owner.isClass then
10511087
if klass.owner.is(Flags.Package) then
10521088
report.warning("[Internal error] top-level class should have `Package` as outer, class = " + klass.show + ", outer = " + outer.show + ", " + Trace.show, Trace.position)
@@ -1115,7 +1151,7 @@ class Objects(using Context @constructorOnly):
11151151
case fun: Fun =>
11161152
given Env.Data = Env.ofByName(sym, fun.env)
11171153
eval(fun.code, fun.thisV, fun.klass)
1118-
case UnknownValue =>
1154+
case UnknownValue | TopWidenedValue =>
11191155
report.warning("Calling on unknown value. " + Trace.show, Trace.position)
11201156
Bottom
11211157
case _: ValueSet | _: Ref | _: OfArray | _: Package | SafeValue(_) =>
@@ -1891,6 +1927,7 @@ class Objects(using Context @constructorOnly):
18911927
thisV match
18921928
case Bottom => Bottom
18931929
case UnknownValue => UnknownValue
1930+
case TopWidenedValue => TopWidenedValue
18941931
case ref: Ref =>
18951932
val outerCls = klass.owner.lexicallyEnclosingClass.asClass
18961933
if !ref.hasOuter(klass) then

0 commit comments

Comments
 (0)