@@ -93,10 +93,11 @@ class Objects(using Context @constructorOnly):
93
93
* | OfClass(class, vs[outer], ctor, args, env) // instance of a class
94
94
* | OfArray(object[owner], regions)
95
95
* | 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
97
98
* vs ::= ValueSet(ve) // set of abstract values
98
99
* 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
100
101
* Ref ::= ObjectRef | OfClass // values that represent a reference to some (global or instance) object
101
102
* ThisValue ::= Ref | UnknownValue // possible values for 'this'
102
103
*
@@ -190,7 +191,7 @@ class Objects(using Context @constructorOnly):
190
191
191
192
def show (using Context ) =
192
193
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 + " )"
194
195
195
196
object OfClass :
196
197
def apply (
@@ -229,17 +230,24 @@ class Objects(using Context @constructorOnly):
229
230
230
231
/**
231
232
* 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
233
235
*/
234
236
case class SafeValue (tpe : Type ) extends ValueElement :
235
237
// tpe could be a AppliedType(java.lang.Class, T)
236
238
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
240
245
241
246
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)
243
251
244
252
/**
245
253
* Represents a set of values
@@ -253,20 +261,26 @@ class Objects(using Context @constructorOnly):
253
261
def show (using Context ): String = " Package(" + packageSym.show + " )"
254
262
255
263
/** 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
256
269
*
257
270
* This is the top of the abstract domain lattice, which should not
258
271
* be used during initialization.
259
272
*
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"
265
279
266
280
val Bottom = ValueSet (ListSet .empty)
267
281
268
282
/** Possible types for 'this' */
269
- type ThisValue = Ref | UnknownValue .type
283
+ type ThisValue = Ref | TopWidenedValue .type
270
284
271
285
/** Checking state */
272
286
object State :
@@ -658,8 +672,8 @@ class Objects(using Context @constructorOnly):
658
672
extension (a : Value )
659
673
def join (b : Value ): Value =
660
674
(a, b) match
661
- case (UnknownValue , _) => UnknownValue
662
- case (_, UnknownValue ) => UnknownValue
675
+ case (TopWidenedValue , _) => TopWidenedValue
676
+ case (_, TopWidenedValue ) => TopWidenedValue
663
677
case (Package (_), _) => UnknownValue // should not happen
664
678
case (_, Package (_)) => UnknownValue
665
679
case (Bottom , b) => b
@@ -675,8 +689,8 @@ class Objects(using Context @constructorOnly):
675
689
case (a : Ref , b : Ref ) if a.equals(b) => Bottom
676
690
case _ => a
677
691
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
680
694
else
681
695
a match
682
696
case Bottom => Bottom
@@ -694,6 +708,7 @@ class Objects(using Context @constructorOnly):
694
708
ref.widenedCopy(outer2, args2, env2)
695
709
696
710
case _ => a
711
+ }
697
712
698
713
def filterType (tpe : Type )(using Context ): Value =
699
714
tpe match
@@ -706,21 +721,24 @@ class Objects(using Context @constructorOnly):
706
721
// Filter the value according to a class symbol, and only leaves the sub-values
707
722
// which could represent an object of the given class
708
723
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 ]
724
742
725
743
extension (values : Iterable [Value ])
726
744
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):
743
761
*/
744
762
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) {
745
763
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
746
767
case UnknownValue =>
747
768
if reportUnknown then
748
769
report.warning(" Using unknown value. " + Trace .show, Trace .position)
@@ -751,10 +772,12 @@ class Objects(using Context @constructorOnly):
751
772
UnknownValue
752
773
753
774
case Package (packageSym) =>
775
+ if meth.equals(defn.throwMethod) then
776
+ Bottom
754
777
// calls on packages are unexpected. However the typer might mistakenly
755
778
// set the receiver to be a package instead of package object.
756
779
// See packageObjectStringInterpolator.scala
757
- if ! meth.owner.denot.isPackageObject then
780
+ else if ! meth.owner.denot.isPackageObject then
758
781
report.warning(" [Internal error] Unexpected call on package = " + value.show + " , meth = " + meth.show + Trace .show, Trace .position)
759
782
Bottom
760
783
else
@@ -764,13 +787,13 @@ class Objects(using Context @constructorOnly):
764
787
765
788
case v @ SafeValue (tpe) =>
766
789
// 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)
768
791
if ! target.hasSource then
769
792
UnknownValue
770
793
else
771
794
val ddef = target.defTree.asInstanceOf [DefDef ]
772
795
val returnType = ddef.tpt.tpe
773
- if SafeValue .safeTypes .contains(returnType) then
796
+ if SafeValue .safeTypeSymbols .contains(returnType.typeSymbol ) then
774
797
// since method is pure and return type is safe, no need to analyze method body
775
798
SafeValue (returnType)
776
799
else
@@ -835,7 +858,7 @@ class Objects(using Context @constructorOnly):
835
858
if meth.owner.isClass then
836
859
(ref, Env .NoEnv )
837
860
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 )
839
862
840
863
val env2 = Env .ofDefDef(ddef, args.map(_.value), outerEnv)
841
864
extendTrace(ddef) {
@@ -933,6 +956,9 @@ class Objects(using Context @constructorOnly):
933
956
*/
934
957
def select (value : Value , field : Symbol , receiver : Type , needResolve : Boolean = true ): Contextual [Value ] = log(" select " + field.show + " , this = " + value.show, printer, (_ : Value ).show) {
935
958
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
936
962
case UnknownValue =>
937
963
if reportUnknown then
938
964
report.warning(" Using unknown value. " + Trace .show, Trace .position)
@@ -977,15 +1003,15 @@ class Objects(using Context @constructorOnly):
977
1003
Bottom
978
1004
else
979
1005
// initialization error, reported by the initialization checker
980
- UnknownValue
1006
+ Bottom
981
1007
else if ref.hasVal(target) then
982
1008
ref.valValue(target)
983
1009
else if ref.isObjectRef && ref.klass.hasSource then
984
1010
report.warning(" Access uninitialized field " + field.show + " . " + Trace .show, Trace .position)
985
1011
Bottom
986
1012
else
987
1013
// initialization error, reported by the initialization checker
988
- UnknownValue
1014
+ Bottom
989
1015
990
1016
else
991
1017
if ref.klass.isSubClass(receiver.widenSingleton.classSymbol) then
@@ -1019,15 +1045,21 @@ class Objects(using Context @constructorOnly):
1019
1045
*/
1020
1046
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) {
1021
1047
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
1022
1054
case p : Package =>
1023
1055
report.warning(" [Internal error] unexpected tree in assignment, package = " + p.packageSym.show + Trace .show, Trace .position)
1024
1056
case fun : Fun =>
1025
1057
report.warning(" [Internal error] unexpected tree in assignment, fun = " + fun.code.show + Trace .show, Trace .position)
1026
1058
case arr : OfArray =>
1027
1059
report.warning(" [Internal error] unexpected tree in assignment, array = " + arr.show + " field = " + field + Trace .show, Trace .position)
1028
1060
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)
1031
1063
1032
1064
case ValueSet (values) =>
1033
1065
values.foreach(ref => assign(ref, field, rhs, rhsTyp))
@@ -1063,9 +1095,13 @@ class Objects(using Context @constructorOnly):
1063
1095
Bottom
1064
1096
1065
1097
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
1067
1103
1068
- case outer : (Ref | UnknownValue .type | Package ) =>
1104
+ case outer : (Ref | TopWidenedValue .type | Package ) =>
1069
1105
if klass == defn.ArrayClass then
1070
1106
args.head.tree.tpe match
1071
1107
case ConstantType (Constants .Constant (0 )) =>
@@ -1081,7 +1117,7 @@ class Objects(using Context @constructorOnly):
1081
1117
outer match
1082
1118
case Package (_) => // For top-level classes
1083
1119
(outer, Env .NoEnv )
1084
- case thisV : ( Ref | UnknownValue . type ) =>
1120
+ case thisV : ThisValue =>
1085
1121
if klass.owner.isClass then
1086
1122
if klass.owner.is(Flags .Package ) then
1087
1123
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):
1152
1188
case fun : Fun =>
1153
1189
given Env .Data = Env .ofByName(sym, fun.env)
1154
1190
eval(fun.code, fun.thisV, fun.klass)
1155
- case UnknownValue =>
1191
+ case UnknownValue | TopWidenedValue =>
1156
1192
report.warning(" Calling on unknown value. " + Trace .show, Trace .position)
1157
1193
Bottom
1158
1194
case _ : ValueSet | _ : Ref | _ : OfArray | _ : Package | SafeValue (_) =>
@@ -1929,6 +1965,7 @@ class Objects(using Context @constructorOnly):
1929
1965
thisV match
1930
1966
case Bottom => Bottom
1931
1967
case UnknownValue => UnknownValue
1968
+ case TopWidenedValue => TopWidenedValue
1932
1969
case ref : Ref =>
1933
1970
val outerCls = klass.owner.lexicallyEnclosingClass.asClass
1934
1971
if ! ref.hasOuter(klass) then
0 commit comments