@@ -18,7 +18,6 @@ import util.{ SourcePosition, NoSourcePosition }
18
18
import config .Printers .init as printer
19
19
import reporting .StoreReporter
20
20
import reporting .trace as log
21
- import reporting .trace .force as forcelog
22
21
import typer .Applications .*
23
22
24
23
import Errors .*
@@ -30,6 +29,7 @@ import scala.collection.mutable
30
29
import scala .annotation .tailrec
31
30
import scala .annotation .constructorOnly
32
31
import dotty .tools .dotc .core .Flags .AbstractOrTrait
32
+ import dotty .tools .dotc .util .SrcPos
33
33
34
34
/** Check initialization safety of static objects
35
35
*
@@ -55,10 +55,10 @@ import dotty.tools.dotc.core.Flags.AbstractOrTrait
55
55
* This principle not only put initialization of static objects on a solid foundation, but also
56
56
* avoids whole-program analysis.
57
57
*
58
- * 2. The design is based on the concept of "cold aliasing " --- a cold alias may not be actively
59
- * used during initialization, i.e., it's forbidden to call methods or access fields of a cold
60
- * alias. Method arguments are cold aliases by default unless specified to be sensitive. Method
61
- * parameters captured in lambdas or inner classes are always cold aliases .
58
+ * 2. The design is based on the concept of "TopWidenedValue " --- a TopWidenedValue may not be actively
59
+ * used during initialization, i.e., it's forbidden to call methods or access fields of a TopWidenedValue.
60
+ * Method arguments are TopWidenedValues by default unless specified to be sensitive. Method
61
+ * parameters captured in lambdas or inner classes are always TopWidenedValues .
62
62
*
63
63
* 3. It is inter-procedural and flow-sensitive.
64
64
*
@@ -94,12 +94,11 @@ class Objects(using Context @constructorOnly):
94
94
* | OfArray(object[owner], regions)
95
95
* | Fun(..., env) // value elements that can be contained in ValueSet
96
96
* | SafeValue // values on which method calls and field accesses won't cause warnings. Int, String, etc.
97
- * | UnknownValue
98
97
* vs ::= ValueSet(ve) // set of abstract values
99
98
* Bottom ::= ValueSet(Empty)
100
- * val ::= ve | TopWidenedValue | vs | Package // all possible abstract values in domain
99
+ * val ::= ve | TopWidenedValue | UnknownValue | vs | Package // all possible abstract values in domain
101
100
* Ref ::= ObjectRef | OfClass // values that represent a reference to some (global or instance) object
102
- * ThisValue ::= Ref | UnknownValue // possible values for 'this'
101
+ * ThisValue ::= Ref | TopWidenedValue // possible values for 'this'
103
102
*
104
103
* refMap = Ref -> ( valsMap, varsMap, outersMap ) // refMap stores field informations of an object or instance
105
104
* valsMap = valsym -> val // maps immutable fields to their values
@@ -233,36 +232,59 @@ class Objects(using Context @constructorOnly):
233
232
* Assumption: all methods calls on such values should not trigger initialization of global objects
234
233
* or read/write mutable fields
235
234
*/
236
- case class SafeValue (tpe : Type ) extends ValueElement :
237
- // tpe could be a AppliedType(java.lang.Class, T)
238
- val baseType = if tpe.isInstanceOf [AppliedType ] then tpe.asInstanceOf [AppliedType ].underlying else 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)
235
+ case class SafeValue (typeSymbol : Symbol ) extends ValueElement :
236
+ assert(SafeValue .safeTypeSymbols.contains(typeSymbol), " Invalid creation of SafeValue! Type = " + typeSymbol)
242
237
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
245
238
246
239
object SafeValue :
247
240
val safeTypeSymbols =
241
+ defn.StringClass ::
248
242
(defn.ScalaNumericValueTypeList ++
249
- List (defn.UnitType , defn.BooleanType , defn.StringType . asInstanceOf [ TypeRef ], defn. NullType , defn.ClassClass .typeRef))
243
+ List (defn.UnitType , defn.BooleanType , defn.NullType , defn.ClassClass .typeRef))
250
244
.map(_.symbol)
251
245
246
+ def getSafeTypeSymbol (tpe : Type ): Option [Symbol ] =
247
+ val baseType = if tpe.isInstanceOf [AppliedType ] then tpe.asInstanceOf [AppliedType ].underlying else tpe
248
+ if baseType.isInstanceOf [TypeRef ] then
249
+ val typeRef = baseType.asInstanceOf [TypeRef ]
250
+ val typeSymbol = typeRef.symbol
251
+ val typeAlias = typeRef.translucentSuperType
252
+ if safeTypeSymbols.contains(typeSymbol) then
253
+ Some (typeSymbol)
254
+ else if typeAlias.isInstanceOf [TypeRef ] && typeAlias.asInstanceOf [TypeRef ].symbol == defn.StringClass then
255
+ // Special case, type scala.Predef.String = java.lang.String
256
+ Some (defn.StringClass )
257
+ else None
258
+ else
259
+ None
260
+
261
+ def apply (tpe : Type ): SafeValue =
262
+ // tpe could be a AppliedType(java.lang.Class, T)
263
+ val typeSymbol = getSafeTypeSymbol(tpe)
264
+ assert(typeSymbol.isDefined, " Invalid creation of SafeValue with type " + tpe)
265
+ new SafeValue (typeSymbol.get)
266
+
252
267
/**
253
268
* Represents a set of values
254
269
*
255
270
* It comes from `if` expressions.
256
271
*/
257
- case class ValueSet (values : ListSet [ValueElement ]) extends Value :
272
+ case class ValueSet (values : Set [ValueElement ]) extends Value :
258
273
def show (using Context ) = values.map(_.show).mkString(" [" , " ," , " ]" )
259
274
260
- case class Package (packageSym : Symbol ) extends Value :
261
- def show (using Context ): String = " Package(" + packageSym.show + " )"
275
+ case class Package (packageModuleClass : ClassSymbol ) extends Value :
276
+ def show (using Context ): String = " Package(" + packageModuleClass.show + " )"
277
+
278
+ object Package :
279
+ def apply (packageSym : Symbol ): Package =
280
+ assert(packageSym.is(Flags .Package ), " Invalid symbol to create Package!" )
281
+ Package (packageSym.moduleClass.asClass)
262
282
263
283
/** Represents values unknown to the checker, such as values loaded without source
284
+ * UnknownValue is not ValueElement since RefSet containing UnknownValue
285
+ * is equivalent to UnknownValue
264
286
*/
265
- case object UnknownValue extends ValueElement :
287
+ case object UnknownValue extends Value :
266
288
def show (using Context ): String = " UnknownValue"
267
289
268
290
/** Represents values lost due to widening
@@ -636,22 +658,26 @@ class Objects(using Context @constructorOnly):
636
658
637
659
extension (a : Value )
638
660
def join (b : Value ): Value =
661
+ assert(! a.isInstanceOf [Package ] && ! b.isInstanceOf [Package ])
639
662
(a, b) match
640
663
case (TopWidenedValue , _) => TopWidenedValue
641
664
case (_, TopWidenedValue ) => TopWidenedValue
642
- case (Package (_) , _) => UnknownValue // should not happen
643
- case (_, Package (_)) => UnknownValue
665
+ case (UnknownValue , _) => UnknownValue
666
+ case (_, UnknownValue ) => UnknownValue
644
667
case (Bottom , b) => b
645
668
case (a, Bottom ) => a
646
669
case (ValueSet (values1), ValueSet (values2)) => ValueSet (values1 ++ values2)
647
670
case (a : ValueElement , ValueSet (values)) => ValueSet (values + a)
648
671
case (ValueSet (values), b : ValueElement ) => ValueSet (values + b)
649
- case (a : ValueElement , b : ValueElement ) => ValueSet (ListSet (a, b))
672
+ case (a : ValueElement , b : ValueElement ) => ValueSet (Set (a, b))
673
+ case _ => Bottom
650
674
651
675
def remove (b : Value ): Value = (a, b) match
652
676
case (ValueSet (values1), b : ValueElement ) => ValueSet (values1 - b)
653
677
case (ValueSet (values1), ValueSet (values2)) => ValueSet (values1.removedAll(values2))
654
678
case (a : Ref , b : Ref ) if a.equals(b) => Bottom
679
+ case (a : SafeValue , b : SafeValue ) if a == b => Bottom
680
+ case (a : Package , b : Package ) if a == b => Bottom
655
681
case _ => a
656
682
657
683
def widen (height : Int )(using Context ): Value = log(" widening value " + a.show + " down to height " + height, printer, (_ : Value ).show) {
@@ -664,7 +690,7 @@ class Objects(using Context @constructorOnly):
664
690
values.map(ref => ref.widen(height)).join
665
691
666
692
case Fun (code, thisV, klass, env) =>
667
- Fun (code, thisV.widenRefOrCold (height), klass, env.widen(height - 1 ))
693
+ Fun (code, thisV.widenThisValue (height), klass, env.widen(height - 1 ))
668
694
669
695
case ref @ OfClass (klass, outer, _, args, env) =>
670
696
val outer2 = outer.widen(height - 1 )
@@ -691,8 +717,10 @@ class Objects(using Context @constructorOnly):
691
717
val klass = sym.asClass
692
718
a match
693
719
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
720
+ case Package (packageModuleClass) =>
721
+ // the typer might mistakenly set the receiver to be a package instead of package object.
722
+ // See pos/packageObjectStringInterpolator.scala
723
+ if packageModuleClass == klass || (klass.denot.isPackageObject && klass.owner == packageModuleClass) then a else Bottom
696
724
case v : SafeValue => if v.typeSymbol.asClass.isSubClass(klass) then a else Bottom
697
725
case ref : Ref => if ref.klass.isSubClass(klass) then ref else Bottom
698
726
case ValueSet (values) => values.map(v => v.filterClass(klass)).join
@@ -701,8 +729,8 @@ class Objects(using Context @constructorOnly):
701
729
if klass.isOneOf(AbstractOrTrait ) && klass.baseClasses.exists(defn.isFunctionClass) then fun else Bottom
702
730
703
731
extension (value : ThisValue )
704
- def widenRefOrCold (height : Int )(using Context ) : ThisValue =
705
- assert(height > 0 , " Cannot call widenRefOrCold with height 0!" )
732
+ def widenThisValue (height : Int )(using Context ) : ThisValue =
733
+ assert(height > 0 , " Cannot call widenThisValue with height 0!" )
706
734
value.widen(height).asInstanceOf [ThisValue ]
707
735
708
736
extension (values : Iterable [Value ])
@@ -714,6 +742,12 @@ class Objects(using Context @constructorOnly):
714
742
*/
715
743
val reportUnknown : Boolean = false
716
744
745
+ def reportWarningForUnknownValue (msg : => String , pos : SrcPos )(using Context ): Value =
746
+ if reportUnknown then
747
+ report.warning(msg, pos)
748
+ Bottom
749
+ else
750
+ UnknownValue
717
751
718
752
/** Handle method calls `e.m(args)`.
719
753
*
@@ -730,13 +764,9 @@ class Objects(using Context @constructorOnly):
730
764
report.warning(" Value is unknown to the checker due to widening. " + Trace .show, Trace .position)
731
765
Bottom
732
766
case UnknownValue =>
733
- if reportUnknown then
734
- report.warning(" Using unknown value. " + Trace .show, Trace .position)
735
- Bottom
736
- else
737
- UnknownValue
767
+ reportWarningForUnknownValue(" Using unknown value. " + Trace .show, Trace .position)
738
768
739
- case Package (packageSym ) =>
769
+ case Package (packageModuleClass ) =>
740
770
if meth.equals(defn.throwMethod) then
741
771
Bottom
742
772
// calls on packages are unexpected. However the typer might mistakenly
@@ -750,17 +780,18 @@ class Objects(using Context @constructorOnly):
750
780
val packageObj = accessObject(meth.owner.moduleClass.asClass)
751
781
call(packageObj, meth, args, receiver, superType, needResolve)
752
782
753
- case v @ SafeValue (tpe ) =>
783
+ case v @ SafeValue (_ ) =>
754
784
// Assume such method is pure. Check return type, only try to analyze body if return type is not safe
755
785
val target = resolve(v.typeSymbol.asClass, meth)
756
786
if ! target.hasSource then
757
787
UnknownValue
758
788
else
759
789
val ddef = target.defTree.asInstanceOf [DefDef ]
760
790
val returnType = ddef.tpt.tpe
761
- if SafeValue .safeTypeSymbols.contains(returnType.typeSymbol) then
791
+ val typeSymbol = SafeValue .getSafeTypeSymbol(returnType)
792
+ if typeSymbol.isDefined then
762
793
// since method is pure and return type is safe, no need to analyze method body
763
- SafeValue (returnType )
794
+ SafeValue (typeSymbol.get )
764
795
else
765
796
val cls = target.owner.enclosingClass.asClass
766
797
// convert SafeType to an OfClass before analyzing method body
@@ -864,7 +895,7 @@ class Objects(using Context @constructorOnly):
864
895
value
865
896
else
866
897
// In future, we will have Tasty for stdlib classes and can abstractly interpret that Tasty.
867
- // For now, return `Cold ` to ensure soundness and trigger a warning.
898
+ // For now, return `UnknownValue ` to ensure soundness and trigger a warning when reportUnknown = true .
868
899
UnknownValue
869
900
end if
870
901
end if
@@ -925,11 +956,7 @@ class Objects(using Context @constructorOnly):
925
956
report.warning(" Value is unknown to the checker due to widening. " + Trace .show, Trace .position)
926
957
Bottom
927
958
case UnknownValue =>
928
- if reportUnknown then
929
- report.warning(" Using unknown value. " + Trace .show, Trace .position)
930
- Bottom
931
- else
932
- UnknownValue
959
+ reportWarningForUnknownValue(" Using unknown value. " + Trace .show, Trace .position)
933
960
934
961
case v @ SafeValue (_) =>
935
962
if v.typeSymbol != defn.NullClass then
@@ -938,13 +965,13 @@ class Objects(using Context @constructorOnly):
938
965
end if
939
966
Bottom
940
967
941
- case Package (packageSym ) =>
968
+ case Package (packageModuleClass ) =>
942
969
if field.isStaticObject then
943
970
accessObject(field.moduleClass.asClass)
944
971
else if field.is(Flags .Package ) then
945
972
Package (field)
946
973
else
947
- report.warning(" [Internal error] Unexpected selection on package " + packageSym .show + " , field = " + field.show + Trace .show, Trace .position)
974
+ report.warning(" [Internal error] Unexpected selection on package " + packageModuleClass .show + " , field = " + field.show + Trace .show, Trace .position)
948
975
Bottom
949
976
950
977
case ref : Ref =>
@@ -1016,11 +1043,9 @@ class Objects(using Context @constructorOnly):
1016
1043
case TopWidenedValue =>
1017
1044
report.warning(" Value is unknown to the checker due to widening. " + Trace .show, Trace .position)
1018
1045
case UnknownValue =>
1019
- if reportUnknown then
1020
- report.warning(" Assigning to unknown value. " + Trace .show, Trace .position)
1021
- end if
1046
+ val _ = reportWarningForUnknownValue(" Assigning to unknown value. " + Trace .show, Trace .position)
1022
1047
case p : Package =>
1023
- report.warning(" [Internal error] unexpected tree in assignment, package = " + p.packageSym. show + Trace .show, Trace .position)
1048
+ report.warning(" [Internal error] unexpected tree in assignment, package = " + p.show + Trace .show, Trace .position)
1024
1049
case fun : Fun =>
1025
1050
report.warning(" [Internal error] unexpected tree in assignment, fun = " + fun.code.show + Trace .show, Trace .position)
1026
1051
case arr : OfArray =>
@@ -1063,11 +1088,7 @@ class Objects(using Context @constructorOnly):
1063
1088
Bottom
1064
1089
1065
1090
case UnknownValue =>
1066
- if reportUnknown then
1067
- report.warning(" Instantiating when outer is unknown. " + Trace .show, Trace .position)
1068
- Bottom
1069
- else
1070
- UnknownValue
1091
+ reportWarningForUnknownValue(" Instantiating when outer is unknown. " + Trace .show, Trace .position)
1071
1092
1072
1093
case outer : (Ref | TopWidenedValue .type | Package ) =>
1073
1094
if klass == defn.ArrayClass then
@@ -1091,7 +1112,7 @@ class Objects(using Context @constructorOnly):
1091
1112
report.warning(" [Internal error] top-level class should have `Package` as outer, class = " + klass.show + " , outer = " + outer.show + " , " + Trace .show, Trace .position)
1092
1113
(Bottom , Env .NoEnv )
1093
1114
else
1094
- (thisV.widenRefOrCold (1 ), Env .NoEnv )
1115
+ (thisV.widenThisValue (1 ), Env .NoEnv )
1095
1116
else
1096
1117
// klass.enclosingMethod returns its primary constructor
1097
1118
Env .resolveEnv(klass.owner.enclosingMethod, thisV, summon[Env .Data ]).getOrElse(UnknownValue -> Env .NoEnv )
@@ -1154,8 +1175,10 @@ class Objects(using Context @constructorOnly):
1154
1175
case fun : Fun =>
1155
1176
given Env .Data = Env .ofByName(sym, fun.env)
1156
1177
eval(fun.code, fun.thisV, fun.klass)
1157
- case UnknownValue | TopWidenedValue =>
1158
- report.warning(" Calling on unknown value. " + Trace .show, Trace .position)
1178
+ case UnknownValue =>
1179
+ reportWarningForUnknownValue(" Calling on unknown value. " + Trace .show, Trace .position)
1180
+ case TopWidenedValue =>
1181
+ report.warning(" Calling on value lost due to widening. " + Trace .show, Trace .position)
1159
1182
Bottom
1160
1183
case _ : ValueSet | _ : Ref | _ : OfArray | _ : Package | SafeValue (_) =>
1161
1184
report.warning(" [Internal error] Unexpected by-name value " + value.show + " . " + Trace .show, Trace .position)
@@ -1607,7 +1630,7 @@ class Objects(using Context @constructorOnly):
1607
1630
end if
1608
1631
end if
1609
1632
// TODO: receiverType is the companion object type, not the class itself;
1610
- // cannot filter scritunee by this type
1633
+ // cannot filter scrutinee by this type
1611
1634
(receiverType, scrutinee)
1612
1635
1613
1636
case Ident (nme.WILDCARD ) | Ident (nme.WILDCARD_STAR ) =>
0 commit comments