Skip to content

Commit 2abda6e

Browse files
committed
Enforce quota in analysis
1 parent 227ac2b commit 2abda6e

File tree

1 file changed

+77
-11
lines changed

1 file changed

+77
-11
lines changed

compiler/src/dotty/tools/dotc/transform/init/Objects.scala

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)