Skip to content

Commit 6b5d6a4

Browse files
author
EnzeXing
committed
Address comments; add TopWidenedValue
1 parent 7ee0726 commit 6b5d6a4

File tree

1 file changed

+82
-45
lines changed

1 file changed

+82
-45
lines changed

compiler/src/dotty/tools/dotc/transform/init/Objects.scala

Lines changed: 82 additions & 45 deletions
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:
@@ -658,8 +672,8 @@ class Objects(using Context @constructorOnly):
658672
extension (a: Value)
659673
def join(b: Value): Value =
660674
(a, b) match
661-
case (UnknownValue, _) => UnknownValue
662-
case (_, UnknownValue) => UnknownValue
675+
case (TopWidenedValue, _) => TopWidenedValue
676+
case (_, TopWidenedValue) => TopWidenedValue
663677
case (Package(_), _) => UnknownValue // should not happen
664678
case (_, Package(_)) => UnknownValue
665679
case (Bottom, b) => b
@@ -675,8 +689,8 @@ class Objects(using Context @constructorOnly):
675689
case (a: Ref, b: Ref) if a.equals(b) => Bottom
676690
case _ => a
677691

678-
def widen(height: Int)(using Context): Value =
679-
if height == 0 then UnknownValue
692+
def widen(height: Int)(using Context): Value = log("widening value " + a.show + " down to height " + height, printer, (_: Value).show) {
693+
if height == 0 then TopWidenedValue
680694
else
681695
a match
682696
case Bottom => Bottom
@@ -694,6 +708,7 @@ class Objects(using Context @constructorOnly):
694708
ref.widenedCopy(outer2, args2, env2)
695709

696710
case _ => a
711+
}
697712

698713
def filterType(tpe: Type)(using Context): Value =
699714
tpe match
@@ -706,21 +721,24 @@ class Objects(using Context @constructorOnly):
706721
// Filter the value according to a class symbol, and only leaves the sub-values
707722
// which could represent an object of the given class
708723
def filterClass(sym: Symbol)(using Context): Value =
709-
if !sym.isClass then a
710-
else
711-
val klass = sym.asClass
712-
a match
713-
case UnknownValue => UnknownValue
714-
case Package(_) => a
715-
case SafeValue(_) => a
716-
case ref: Ref => if ref.klass.isSubClass(klass) then ref else Bottom
717-
case ValueSet(values) => values.map(v => v.filterClass(klass)).join
718-
case arr: OfArray => if defn.ArrayClass.isSubClass(klass) then arr else Bottom
719-
case fun: Fun =>
720-
if klass.isOneOf(AbstractOrTrait) && klass.baseClasses.exists(defn.isFunctionClass) then fun else Bottom
721-
722-
extension (value: Ref | UnknownValue.type)
723-
def widenRefOrCold(height : Int)(using Context) : Ref | UnknownValue.type = value.widen(height).asInstanceOf[ThisValue]
724+
if !sym.isClass then a
725+
else
726+
val klass = sym.asClass
727+
a match
728+
case UnknownValue | TopWidenedValue => a
729+
case Package(packageSym) =>
730+
if packageSym.moduleClass.equals(sym) || (klass.denot.isPackageObject && klass.owner.equals(sym)) then a else Bottom
731+
case v: SafeValue => if v.typeSymbol.asClass.isSubClass(klass) then a else Bottom
732+
case ref: Ref => if ref.klass.isSubClass(klass) then ref else Bottom
733+
case ValueSet(values) => values.map(v => v.filterClass(klass)).join
734+
case arr: OfArray => if defn.ArrayClass.isSubClass(klass) then arr else Bottom
735+
case fun: Fun =>
736+
if klass.isOneOf(AbstractOrTrait) && klass.baseClasses.exists(defn.isFunctionClass) then fun else Bottom
737+
738+
extension (value: ThisValue)
739+
def widenRefOrCold(height : Int)(using Context) : ThisValue =
740+
assert(height > 0, "Cannot call widenRefOrCold with height 0!")
741+
value.widen(height).asInstanceOf[ThisValue]
724742

725743
extension (values: Iterable[Value])
726744
def join: Value = if values.isEmpty then Bottom else values.reduce { (v1, v2) => v1.join(v2) }
@@ -743,6 +761,9 @@ class Objects(using Context @constructorOnly):
743761
*/
744762
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) {
745763
value.filterClass(meth.owner) match
764+
case TopWidenedValue =>
765+
report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position)
766+
Bottom
746767
case UnknownValue =>
747768
if reportUnknown then
748769
report.warning("Using unknown value. " + Trace.show, Trace.position)
@@ -751,10 +772,12 @@ class Objects(using Context @constructorOnly):
751772
UnknownValue
752773

753774
case Package(packageSym) =>
775+
if meth.equals(defn.throwMethod) then
776+
Bottom
754777
// calls on packages are unexpected. However the typer might mistakenly
755778
// set the receiver to be a package instead of package object.
756779
// See packageObjectStringInterpolator.scala
757-
if !meth.owner.denot.isPackageObject then
780+
else if !meth.owner.denot.isPackageObject then
758781
report.warning("[Internal error] Unexpected call on package = " + value.show + ", meth = " + meth.show + Trace.show, Trace.position)
759782
Bottom
760783
else
@@ -764,13 +787,13 @@ class Objects(using Context @constructorOnly):
764787

765788
case v @ SafeValue(tpe) =>
766789
// Assume such method is pure. Check return type, only try to analyze body if return type is not safe
767-
val target = resolve(v.typeref.symbol.asClass, meth)
790+
val target = resolve(v.typeSymbol.asClass, meth)
768791
if !target.hasSource then
769792
UnknownValue
770793
else
771794
val ddef = target.defTree.asInstanceOf[DefDef]
772795
val returnType = ddef.tpt.tpe
773-
if SafeValue.safeTypes.contains(returnType) then
796+
if SafeValue.safeTypeSymbols.contains(returnType.typeSymbol) then
774797
// since method is pure and return type is safe, no need to analyze method body
775798
SafeValue(returnType)
776799
else
@@ -835,7 +858,7 @@ class Objects(using Context @constructorOnly):
835858
if meth.owner.isClass then
836859
(ref, Env.NoEnv)
837860
else
838-
Env.resolveEnvByOwner(meth.owner.enclosingMethod, ref, summon[Env.Data]).getOrElse(UnknownValue -> Env.NoEnv)
861+
Env.resolveEnvByOwner(meth.owner.enclosingMethod, ref, summon[Env.Data]).getOrElse(TopWidenedValue -> Env.NoEnv)
839862

840863
val env2 = Env.ofDefDef(ddef, args.map(_.value), outerEnv)
841864
extendTrace(ddef) {
@@ -933,6 +956,9 @@ class Objects(using Context @constructorOnly):
933956
*/
934957
def select(value: Value, field: Symbol, receiver: Type, needResolve: Boolean = true): Contextual[Value] = log("select " + field.show + ", this = " + value.show, printer, (_: Value).show) {
935958
value.filterClass(field.owner) match
959+
case TopWidenedValue =>
960+
report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position)
961+
Bottom
936962
case UnknownValue =>
937963
if reportUnknown then
938964
report.warning("Using unknown value. " + Trace.show, Trace.position)
@@ -977,15 +1003,15 @@ class Objects(using Context @constructorOnly):
9771003
Bottom
9781004
else
9791005
// initialization error, reported by the initialization checker
980-
UnknownValue
1006+
Bottom
9811007
else if ref.hasVal(target) then
9821008
ref.valValue(target)
9831009
else if ref.isObjectRef && ref.klass.hasSource then
9841010
report.warning("Access uninitialized field " + field.show + ". " + Trace.show, Trace.position)
9851011
Bottom
9861012
else
9871013
// initialization error, reported by the initialization checker
988-
UnknownValue
1014+
Bottom
9891015

9901016
else
9911017
if ref.klass.isSubClass(receiver.widenSingleton.classSymbol) then
@@ -1019,15 +1045,21 @@ class Objects(using Context @constructorOnly):
10191045
*/
10201046
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) {
10211047
lhs.filterClass(field.owner) match
1048+
case TopWidenedValue =>
1049+
report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position)
1050+
case UnknownValue =>
1051+
if reportUnknown then
1052+
report.warning("Assigning to unknown value. " + Trace.show, Trace.position)
1053+
end if
10221054
case p: Package =>
10231055
report.warning("[Internal error] unexpected tree in assignment, package = " + p.packageSym.show + Trace.show, Trace.position)
10241056
case fun: Fun =>
10251057
report.warning("[Internal error] unexpected tree in assignment, fun = " + fun.code.show + Trace.show, Trace.position)
10261058
case arr: OfArray =>
10271059
report.warning("[Internal error] unexpected tree in assignment, array = " + arr.show + " field = " + field + Trace.show, Trace.position)
10281060

1029-
case SafeValue(_) | UnknownValue =>
1030-
report.warning("Assigning to base or unknown value is forbidden. " + Trace.show, Trace.position)
1061+
case SafeValue(_) =>
1062+
report.warning("Assigning to base value is forbidden. " + Trace.show, Trace.position)
10311063

10321064
case ValueSet(values) =>
10331065
values.foreach(ref => assign(ref, field, rhs, rhsTyp))
@@ -1063,9 +1095,13 @@ class Objects(using Context @constructorOnly):
10631095
Bottom
10641096

10651097
case UnknownValue =>
1066-
UnknownValue
1098+
if reportUnknown then
1099+
report.warning("Instantiating when outer is unknown. " + Trace.show, Trace.position)
1100+
Bottom
1101+
else
1102+
UnknownValue
10671103

1068-
case outer: (Ref | UnknownValue.type | Package) =>
1104+
case outer: (Ref | TopWidenedValue.type | Package) =>
10691105
if klass == defn.ArrayClass then
10701106
args.head.tree.tpe match
10711107
case ConstantType(Constants.Constant(0)) =>
@@ -1081,7 +1117,7 @@ class Objects(using Context @constructorOnly):
10811117
outer match
10821118
case Package(_) => // For top-level classes
10831119
(outer, Env.NoEnv)
1084-
case thisV : (Ref | UnknownValue.type) =>
1120+
case thisV : ThisValue =>
10851121
if klass.owner.isClass then
10861122
if klass.owner.is(Flags.Package) then
10871123
report.warning("[Internal error] top-level class should have `Package` as outer, class = " + klass.show + ", outer = " + outer.show + ", " + Trace.show, Trace.position)
@@ -1152,7 +1188,7 @@ class Objects(using Context @constructorOnly):
11521188
case fun: Fun =>
11531189
given Env.Data = Env.ofByName(sym, fun.env)
11541190
eval(fun.code, fun.thisV, fun.klass)
1155-
case UnknownValue =>
1191+
case UnknownValue | TopWidenedValue =>
11561192
report.warning("Calling on unknown value. " + Trace.show, Trace.position)
11571193
Bottom
11581194
case _: ValueSet | _: Ref | _: OfArray | _: Package | SafeValue(_) =>
@@ -1929,6 +1965,7 @@ class Objects(using Context @constructorOnly):
19291965
thisV match
19301966
case Bottom => Bottom
19311967
case UnknownValue => UnknownValue
1968+
case TopWidenedValue => TopWidenedValue
19321969
case ref: Ref =>
19331970
val outerCls = klass.owner.lexicallyEnclosingClass.asClass
19341971
if !ref.hasOuter(klass) then

0 commit comments

Comments
 (0)