@@ -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