@@ -94,13 +94,20 @@ class Objects(using Context @constructorOnly):
9494 */
9595 val BestEffort : Boolean = true
9696
97+ /** The analysis has run out of quota */
98+ class OutOfQuotaException (count : Int ) extends Exception
99+
100+ def checkCost (count : Int ): Unit =
101+ if count > 500 && BestEffort || count > 2000 && ! BestEffort then
102+ throw new OutOfQuotaException (count)
103+
97104 // ----------------------------- abstract domain -----------------------------
98105
99106 /** Syntax for the data structure abstraction used in abstract domain:
100107 *
101108 * ve ::= ObjectRef(class) // global object
102- * | InstanceRef(class, ownerObject, ctor, regions) // instance of a class
103- * | ArrayRef(ownerObject, regions) // represents values of native array class in Array.scala
109+ * | InstanceRef(class, ownerObject, ctor, regions) // instance of a class
110+ * | ArrayRef(ownerObject, regions) // represents values of native array class in Array.scala
104111 * | Fun(code, thisV, scope) // value elements that can be contained in ValueSet
105112 * | SafeValue // values on which method calls and field accesses won't cause warnings. Int, String, etc.
106113 * | UnknownValue // values whose source are unknown at compile time
@@ -113,8 +120,8 @@ class Objects(using Context @constructorOnly):
113120 * EnvRef(tree, ownerObject) // represents environments for evaluating methods, functions, or lazy/by-name values
114121 * EnvSet ::= Set(EnvRef)
115122 * InstanceBody ::= (valsMap: Map[Symbol, Value],
116- outersMap: Map[ClassSymbol, Value],
117- outerEnv: EnvSet) // represents combined information of all instances represented by a ref
123+ * outersMap: Map[ClassSymbol, Value],
124+ * outerEnv: EnvSet) // represents combined information of all instances represented by a ref
118125 * Heap ::= Ref -> InstanceBody // heap is mutable
119126 * EnvBody ::= (valsMap: Map[Symbol, Value],
120127 * thisV: Value,
@@ -364,10 +371,39 @@ class Objects(using Context @constructorOnly):
364371 private [State ] val checkingObjects = new mutable.ArrayBuffer [ObjectRef ]
365372 private [State ] val checkedObjects = new mutable.ArrayBuffer [ObjectRef ]
366373 private [State ] val pendingTraces = new mutable.ArrayBuffer [Trace ]
374+
375+ /** It records how many calls have being analyzed for the current object under check */
376+ private [State ] val checkingCosts = new mutable.ArrayBuffer [Int ]
377+
378+ private [State ] val quotaExhaustedObjects = new mutable.ArrayBuffer [ObjectRef ]
379+
380+ def addChecking (obj : ObjectRef ): Unit =
381+ this .checkingObjects += obj
382+ this .checkingCosts += 0
383+
384+ def popChecking (): Unit =
385+ val index = this .checkingObjects.size - 1
386+ checkingObjects.remove(index)
387+ checkingCosts.remove(index)
388+
389+ def addChecked (obj : ObjectRef ): Unit =
390+ this .checkedObjects += obj
391+
392+ def addQuotaExhausted (obj : ObjectRef ): Unit =
393+ this .quotaExhaustedObjects += obj
367394 end Data
368395
369396 def currentObject (using data : Data ): ClassSymbol = data.checkingObjects.last.klass
370397
398+ def recordCall ()(using data : Data ): Unit =
399+ val lastIndex = data.checkingCosts.size - 1
400+ val callCount = data.checkingCosts(lastIndex) + 1
401+ data.checkingCosts(lastIndex) = callCount
402+ checkCost(callCount)
403+
404+ def isQuotaExhausted (obj : ObjectRef )(using data : Data ): Boolean =
405+ data.quotaExhaustedObjects.contains(obj)
406+
371407 private def doCheckObject (classSym : ClassSymbol )(using ctx : Context , data : Data , heap : Heap .MutableData , envMap : EnvMap .EnvMapMutableData ) =
372408 val tpl = classSym.defTree.asInstanceOf [TypeDef ].rhs.asInstanceOf [Template ]
373409
@@ -386,10 +422,20 @@ class Objects(using Context @constructorOnly):
386422 val obj = ObjectRef (classSym)
387423 given Scope = obj
388424 log(" Iteration " + count) {
389- data.checkingObjects += obj
390- init(tpl, obj, classSym)
425+ data.addChecking(obj)
426+
427+ try
428+ init(tpl, obj, classSym)
429+ catch case _ : OutOfQuotaException =>
430+ if ! BestEffort then
431+ report.warning(" Giving up checking initializatino of " + classSym + " due to exhausted budget" , classSym.sourcePos)
432+ data.addQuotaExhausted(obj)
433+ data.addChecked(obj)
434+ return obj
435+
391436 assert(data.checkingObjects.last.klass == classSym, " Expect = " + classSym.show + " , found = " + data.checkingObjects.last.klass)
392- data.checkingObjects.remove(data.checkingObjects.size - 1 )
437+
438+ data.popChecking()
393439 }
394440
395441 val hasError = ctx.reporter.pendingMessages.nonEmpty
@@ -400,10 +446,10 @@ class Objects(using Context @constructorOnly):
400446 else
401447 if ! BestEffort then
402448 report.warning(" Giving up checking initializatino of " + classSym + " due to complex code" , classSym.sourcePos)
403- data.checkedObjects += obj
449+ data.addChecked( obj)
404450 obj
405451 else
406- data.checkedObjects += obj
452+ data.addChecked( obj)
407453 obj
408454 end iterate
409455
@@ -1110,6 +1156,7 @@ class Objects(using Context @constructorOnly):
11101156
11111157 val env2 = Env .ofDefDef(ddef, args.map(_.value), thisV, outerEnv)
11121158 extendTrace(ddef) {
1159+ State .recordCall()
11131160 given Scope = env2
11141161 cache.cachedEval(ref, ddef.rhs, cacheResult = true ) { expr =>
11151162 Returns .installHandler(meth)
@@ -1144,7 +1191,10 @@ class Objects(using Context @constructorOnly):
11441191 case env : Env .EnvRef => Env .ofDefDef(ddef, args.map(_.value), thisVOfClosure, Env .EnvSet (Set (env)))
11451192 }
11461193 given Scope = funEnv
1147- extendTrace(code) { eval(ddef.rhs, thisVOfClosure, klass, cacheResult = true ) }
1194+ extendTrace(code) {
1195+ State .recordCall()
1196+ eval(ddef.rhs, thisVOfClosure, klass, cacheResult = true )
1197+ }
11481198 else
11491199 // The methods defined in `Any` and `AnyRef` are trivial and don't affect initialization.
11501200 if meth.owner == defn.AnyClass || meth.owner == defn.ObjectClass then
@@ -1187,6 +1237,7 @@ class Objects(using Context @constructorOnly):
11871237 extendTrace(cls.defTree) { eval(tpl, ref, cls, cacheResult = true ) }
11881238 else
11891239 extendTrace(ddef) { // The return values for secondary constructors can be ignored
1240+ State .recordCall()
11901241 Returns .installHandler(ctor)
11911242 eval(ddef.rhs, ref, cls, cacheResult = true )
11921243 Returns .popHandler(ctor)
@@ -1241,34 +1292,49 @@ class Objects(using Context @constructorOnly):
12411292 if target.is(Flags .Lazy ) then // select a lazy field
12421293 if ref.hasVal(target) then
12431294 ref.valValue(target)
1295+
12441296 else if target.hasSource then
12451297 val rhs = target.defTree.asInstanceOf [ValDef ].rhs
12461298 given Scope = Env .ofByName(target, rhs, ref, Env .NoEnv )
12471299 val result = eval(rhs, ref, target.owner.asClass, cacheResult = true )
12481300 ref.initVal(target, result)
12491301 result
1302+
12501303 else
12511304 UnknownValue
1305+
12521306 else if target.exists then
12531307 def isNextFieldOfColonColon : Boolean = ref.klass == defn.ConsClass && target.name.toString == " next"
12541308 if target.isMutableVarOrAccessor && ! isNextFieldOfColonColon then
12551309 if ref.hasVar(target) then
12561310 if ref.owner == State .currentObject then
12571311 ref.varValue(target)
1312+
12581313 else
12591314 errorReadOtherStaticObject(State .currentObject, ref)
12601315 Bottom
1316+
12611317 else if ref.isObjectRef && ref.klass.hasSource then
1262- report.warning(" Access uninitialized field " + field.show + " . " + Trace .show, Trace .position)
1318+ if State .isQuotaExhausted(ref.asObjectRef) then
1319+ if ! BestEffort then
1320+ report.warning(" Access uninitialized field of quota exhausted object " + field.show + " . " + Trace .show, Trace .position)
1321+
1322+ else
1323+ report.warning(" Access uninitialized field " + field.show + " . " + Trace .show, Trace .position)
1324+
12631325 Bottom
1326+
12641327 else
12651328 // initialization error, reported by the initialization checker
12661329 Bottom
1330+
12671331 else if ref.hasVal(target) then
12681332 ref.valValue(target)
1333+
12691334 else if ref.isObjectRef && ref.klass.hasSource then
12701335 report.warning(" Access uninitialized field " + field.show + " . " + Trace .show, Trace .position)
12711336 Bottom
1337+
12721338 else
12731339 // initialization error, reported by the initialization checker
12741340 Bottom
0 commit comments