@@ -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 :
@@ -623,8 +637,8 @@ class Objects(using Context @constructorOnly):
623
637
extension (a : Value )
624
638
def join (b : Value ): Value =
625
639
(a, b) match
626
- case (UnknownValue , _) => UnknownValue
627
- case (_, UnknownValue ) => UnknownValue
640
+ case (TopWidenedValue , _) => TopWidenedValue
641
+ case (_, TopWidenedValue ) => TopWidenedValue
628
642
case (Package (_), _) => UnknownValue // should not happen
629
643
case (_, Package (_)) => UnknownValue
630
644
case (Bottom , b) => b
@@ -640,8 +654,8 @@ class Objects(using Context @constructorOnly):
640
654
case (a : Ref , b : Ref ) if a.equals(b) => Bottom
641
655
case _ => a
642
656
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
645
659
else
646
660
a match
647
661
case Bottom => Bottom
@@ -659,6 +673,7 @@ class Objects(using Context @constructorOnly):
659
673
ref.widenedCopy(outer2, args2, env2)
660
674
661
675
case _ => a
676
+ }
662
677
663
678
def filterType (tpe : Type )(using Context ): Value =
664
679
tpe match
@@ -671,21 +686,24 @@ class Objects(using Context @constructorOnly):
671
686
// Filter the value according to a class symbol, and only leaves the sub-values
672
687
// which could represent an object of the given class
673
688
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 ]
689
707
690
708
extension (values : Iterable [Value ])
691
709
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):
708
726
*/
709
727
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) {
710
728
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
711
732
case UnknownValue =>
712
733
if reportUnknown then
713
734
report.warning(" Using unknown value. " + Trace .show, Trace .position)
@@ -716,10 +737,12 @@ class Objects(using Context @constructorOnly):
716
737
UnknownValue
717
738
718
739
case Package (packageSym) =>
740
+ if meth.equals(defn.throwMethod) then
741
+ Bottom
719
742
// calls on packages are unexpected. However the typer might mistakenly
720
743
// set the receiver to be a package instead of package object.
721
744
// See packageObjectStringInterpolator.scala
722
- if ! meth.owner.denot.isPackageObject then
745
+ else if ! meth.owner.denot.isPackageObject then
723
746
report.warning(" [Internal error] Unexpected call on package = " + value.show + " , meth = " + meth.show + Trace .show, Trace .position)
724
747
Bottom
725
748
else
@@ -729,13 +752,13 @@ class Objects(using Context @constructorOnly):
729
752
730
753
case v @ SafeValue (tpe) =>
731
754
// 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)
733
756
if ! target.hasSource then
734
757
UnknownValue
735
758
else
736
759
val ddef = target.defTree.asInstanceOf [DefDef ]
737
760
val returnType = ddef.tpt.tpe
738
- if SafeValue .safeTypes .contains(returnType) then
761
+ if SafeValue .safeTypeSymbols .contains(returnType.typeSymbol ) then
739
762
// since method is pure and return type is safe, no need to analyze method body
740
763
SafeValue (returnType)
741
764
else
@@ -800,7 +823,7 @@ class Objects(using Context @constructorOnly):
800
823
if meth.owner.isClass then
801
824
(ref, Env .NoEnv )
802
825
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 )
804
827
805
828
val env2 = Env .ofDefDef(ddef, args.map(_.value), outerEnv)
806
829
extendTrace(ddef) {
@@ -898,6 +921,9 @@ class Objects(using Context @constructorOnly):
898
921
*/
899
922
def select (value : Value , field : Symbol , receiver : Type , needResolve : Boolean = true ): Contextual [Value ] = log(" select " + field.show + " , this = " + value.show, printer, (_ : Value ).show) {
900
923
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
901
927
case UnknownValue =>
902
928
if reportUnknown then
903
929
report.warning(" Using unknown value. " + Trace .show, Trace .position)
@@ -942,15 +968,15 @@ class Objects(using Context @constructorOnly):
942
968
Bottom
943
969
else
944
970
// initialization error, reported by the initialization checker
945
- UnknownValue
971
+ Bottom
946
972
else if ref.hasVal(target) then
947
973
ref.valValue(target)
948
974
else if ref.isObjectRef && ref.klass.hasSource then
949
975
report.warning(" Access uninitialized field " + field.show + " . " + Trace .show, Trace .position)
950
976
Bottom
951
977
else
952
978
// initialization error, reported by the initialization checker
953
- UnknownValue
979
+ Bottom
954
980
955
981
else
956
982
if ref.klass.isSubClass(receiver.widenSingleton.classSymbol) then
@@ -984,15 +1010,21 @@ class Objects(using Context @constructorOnly):
984
1010
*/
985
1011
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) {
986
1012
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
987
1019
case p : Package =>
988
1020
report.warning(" [Internal error] unexpected tree in assignment, package = " + p.packageSym.show + Trace .show, Trace .position)
989
1021
case fun : Fun =>
990
1022
report.warning(" [Internal error] unexpected tree in assignment, fun = " + fun.code.show + Trace .show, Trace .position)
991
1023
case arr : OfArray =>
992
1024
report.warning(" [Internal error] unexpected tree in assignment, array = " + arr.show + " field = " + field + Trace .show, Trace .position)
993
1025
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)
996
1028
997
1029
case ValueSet (values) =>
998
1030
values.foreach(ref => assign(ref, field, rhs, rhsTyp))
@@ -1028,9 +1060,13 @@ class Objects(using Context @constructorOnly):
1028
1060
Bottom
1029
1061
1030
1062
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
1032
1068
1033
- case outer : (Ref | UnknownValue .type | Package ) =>
1069
+ case outer : (Ref | TopWidenedValue .type | Package ) =>
1034
1070
if klass == defn.ArrayClass then
1035
1071
args.head.tree.tpe match
1036
1072
case ConstantType (Constants .Constant (0 )) =>
@@ -1046,7 +1082,7 @@ class Objects(using Context @constructorOnly):
1046
1082
outer match
1047
1083
case Package (_) => // For top-level classes
1048
1084
(outer, Env .NoEnv )
1049
- case thisV : ( Ref | UnknownValue . type ) =>
1085
+ case thisV : ThisValue =>
1050
1086
if klass.owner.isClass then
1051
1087
if klass.owner.is(Flags .Package ) then
1052
1088
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):
1115
1151
case fun : Fun =>
1116
1152
given Env .Data = Env .ofByName(sym, fun.env)
1117
1153
eval(fun.code, fun.thisV, fun.klass)
1118
- case UnknownValue =>
1154
+ case UnknownValue | TopWidenedValue =>
1119
1155
report.warning(" Calling on unknown value. " + Trace .show, Trace .position)
1120
1156
Bottom
1121
1157
case _ : ValueSet | _ : Ref | _ : OfArray | _ : Package | SafeValue (_) =>
@@ -1891,6 +1927,7 @@ class Objects(using Context @constructorOnly):
1891
1927
thisV match
1892
1928
case Bottom => Bottom
1893
1929
case UnknownValue => UnknownValue
1930
+ case TopWidenedValue => TopWidenedValue
1894
1931
case ref : Ref =>
1895
1932
val outerCls = klass.owner.lexicallyEnclosingClass.asClass
1896
1933
if ! ref.hasOuter(klass) then
0 commit comments