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