Skip to content
This repository was archived by the owner on Sep 1, 2020. It is now read-only.

Commit 57b8da4

Browse files
committed
Nullness Analysis
Tracks nullness of values using an ASM analyzer. Tracking nullness requires alias tracking for local variables and stack values. For example, after an instance call, local variables that point to the same object as the receiver are treated not-null.
1 parent a436e90 commit 57b8da4

File tree

8 files changed

+1237
-8
lines changed

8 files changed

+1237
-8
lines changed
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
package scala.tools.nsc
2+
package backend.jvm
3+
package analysis
4+
5+
import scala.annotation.switch
6+
import scala.collection.{mutable, immutable}
7+
import scala.tools.asm.Opcodes
8+
import scala.tools.asm.tree._
9+
import scala.tools.asm.tree.analysis.{Analyzer, Value, Frame, Interpreter}
10+
import opt.BytecodeUtils._
11+
12+
object AliasingFrame {
13+
private var _idCounter: Long = 0l
14+
private def nextId = { _idCounter += 1; _idCounter }
15+
}
16+
17+
class AliasingFrame[V <: Value](nLocals: Int, nStack: Int) extends Frame[V](nLocals, nStack) {
18+
import Opcodes._
19+
20+
// Auxiliary constructor required for implementing `AliasingAnalyzer.newFrame`
21+
def this(src: Frame[_ <: V]) {
22+
this(src.getLocals, src.getMaxStackSize)
23+
init(src)
24+
}
25+
26+
/**
27+
* For each slot (entry in the `values` array of the frame), an id that uniquely represents
28+
* the object stored in it. If two values have the same id, they are aliases of the same
29+
* object.
30+
*/
31+
private val aliasIds: Array[Long] = Array.fill(nLocals + nStack)(AliasingFrame.nextId)
32+
33+
/**
34+
* The object alias id of for a value index.
35+
*/
36+
def aliasId(entry: Int) = aliasIds(entry)
37+
38+
/**
39+
* Returns the indices of the values array which are aliases of the object `id`.
40+
*/
41+
def valuesWithAliasId(id: Long): Set[Int] = immutable.BitSet.empty ++ aliasIds.indices.filter(i => aliasId(i) == id)
42+
43+
/**
44+
* The set of aliased values for a given entry in the `values` array.
45+
*/
46+
def aliasesOf(entry: Int): Set[Int] = valuesWithAliasId(aliasIds(entry))
47+
48+
/**
49+
* Define a new alias. For example, given
50+
* var a = this // this, a have the same aliasId
51+
* then an assignment
52+
* b = a
53+
* will set the same the aliasId for `b`.
54+
*/
55+
private def newAlias(assignee: Int, source: Int): Unit = {
56+
aliasIds(assignee) = aliasIds(source)
57+
}
58+
59+
/**
60+
* An assignment
61+
* a = someUnknownValue()
62+
* sets a fresh alias id for `a`.
63+
* A stack value is also removed from its alias set when being consumed.
64+
*/
65+
private def removeAlias(assignee: Int): Unit = {
66+
aliasIds(assignee) = AliasingFrame.nextId
67+
}
68+
69+
override def execute(insn: AbstractInsnNode, interpreter: Interpreter[V]): Unit = {
70+
// Make the extendsion methods easier to use (otherwise we have to repeat `this`.stackTop)
71+
def stackTop: Int = this.stackTop
72+
def peekStack(n: Int): V = this.peekStack(n)
73+
74+
val (consumed, produced) = InstructionStackEffect(insn, this) // needs to be called before super.execute, see its doc
75+
super.execute(insn, interpreter)
76+
77+
(insn.getOpcode: @switch) match {
78+
case ALOAD =>
79+
newAlias(assignee = stackTop, source = insn.asInstanceOf[VarInsnNode].`var`)
80+
81+
case DUP =>
82+
val top = stackTop
83+
newAlias(assignee = top, source = top - 1)
84+
85+
case DUP_X1 =>
86+
val top = stackTop
87+
newAlias(assignee = top, source = top - 1)
88+
newAlias(assignee = top - 1, source = top - 2)
89+
newAlias(assignee = top - 2, source = top)
90+
91+
case DUP_X2 =>
92+
// Check if the second element on the stack is size 2
93+
// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dup_x2
94+
val isSize2 = peekStack(1).getSize == 2
95+
val top = stackTop
96+
newAlias(assignee = top, source = top - 1)
97+
newAlias(assignee = top - 1, source = top - 2)
98+
if (isSize2) {
99+
// Size 2 values on the stack only take one slot in the `values` array
100+
newAlias(assignee = top - 2, source = top)
101+
} else {
102+
newAlias(assignee = top - 2, source = top - 3)
103+
newAlias(assignee = top - 3, source = top)
104+
}
105+
106+
case DUP2 =>
107+
val isSize2 = peekStack(0).getSize == 2
108+
val top = stackTop
109+
if (isSize2) {
110+
newAlias(assignee = top, source = top - 1)
111+
} else {
112+
newAlias(assignee = top - 1, source = top - 3)
113+
newAlias(assignee = top, source = top - 2)
114+
}
115+
116+
case DUP2_X1 =>
117+
val isSize2 = peekStack(0).getSize == 2
118+
val top = stackTop
119+
if (isSize2) {
120+
newAlias(assignee = top, source = top - 1)
121+
newAlias(assignee = top - 1, source = top - 2)
122+
newAlias(assignee = top - 2, source = top)
123+
} else {
124+
newAlias(assignee = top, source = top - 2)
125+
newAlias(assignee = top - 1, source = top - 3)
126+
newAlias(assignee = top - 2, source = top - 4)
127+
newAlias(assignee = top - 4, source = top)
128+
newAlias(assignee = top - 5, source = top - 1)
129+
}
130+
131+
case DUP2_X2 =>
132+
val top = stackTop
133+
// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dup2_x2
134+
val v1isSize2 = peekStack(0).getSize == 2
135+
if (v1isSize2) {
136+
newAlias(assignee = top, source = top - 1)
137+
newAlias(assignee = top - 1, source = top - 2)
138+
val v2isSize2 = peekStack(1).getSize == 2
139+
if (v2isSize2) {
140+
// Form 4
141+
newAlias(assignee = top - 2, source = top)
142+
} else {
143+
// Form 2
144+
newAlias(assignee = top - 2, source = top - 3)
145+
newAlias(assignee = top - 3, source = top)
146+
}
147+
} else {
148+
newAlias(assignee = top, source = top - 2)
149+
newAlias(assignee = top - 1, source = top - 3)
150+
newAlias(assignee = top - 2, source = top - 4)
151+
val v3isSize2 = peekStack(2).getSize == 2
152+
if (v3isSize2) {
153+
// Form 3
154+
newAlias(assignee = top - 3, source = top)
155+
newAlias(assignee = top - 4, source = top - 1)
156+
} else {
157+
// Form 1
158+
newAlias(assignee = top - 3, source = top - 5)
159+
newAlias(assignee = top - 4, source = top)
160+
newAlias(assignee = top - 5, source = top - 1)
161+
}
162+
}
163+
164+
case SWAP =>
165+
val top = stackTop
166+
val idTop = aliasIds(top)
167+
aliasIds(top) = aliasIds(top - 1)
168+
aliasIds(top - 1) = idTop
169+
170+
case opcode =>
171+
if (opcode == ASTORE) {
172+
// Not a separate case because we need to remove the consumed stack value from alias sets after.
173+
val stackTopBefore = stackTop - produced + consumed
174+
val local = insn.asInstanceOf[VarInsnNode].`var`
175+
newAlias(assignee = local, source = stackTopBefore)
176+
// if the value written is size 2, it overwrites the subsequent slot, which is then no
177+
// longer an alias of anything. see the corresponding case in `Frame.execute`.
178+
if (getLocal(local).getSize == 2)
179+
removeAlias(local + 1)
180+
181+
// if the value at the preceding index is size 2, it is no longer valid, so we remove its
182+
// aliasing. see corresponding case in `Frame.execute`
183+
if (local > 0) {
184+
val precedingValue = getLocal(local - 1)
185+
if (precedingValue != null && precedingValue.getSize == 2)
186+
removeAlias(local - 1)
187+
}
188+
}
189+
190+
// Remove consumed stack values from aliasing sets.
191+
// Example: iadd
192+
// - before: local1, local2, stack1, consumed1, consumed2
193+
// - after: local1, local2, stack1, produced1 // stackTop = 3
194+
val firstConsumed = stackTop - produced + 1 // firstConsumed = 3
195+
for (i <- 0 until consumed)
196+
removeAlias(firstConsumed + i) // remove aliases for 3 and 4
197+
198+
// We don't need to set the aliases ids for the produced values: the aliasIds array already
199+
// contains fresh ids for non-used stack values (ensured by removeAlias).
200+
}
201+
}
202+
203+
/**
204+
* Merge the AliasingFrame `other` into this AliasingFrame.
205+
*
206+
* Aliases that are common in both frames are kept. Example:
207+
*
208+
* var x, y = null
209+
* if (...) {
210+
* x = a
211+
* y = a // (x, y, a) are aliases
212+
* } else {
213+
* x = a
214+
* y = b // (x, a) and (y, b)
215+
* }
216+
* [...] // (x, a)
217+
*/
218+
override def merge(other: Frame[_ <: V], interpreter: Interpreter[V]): Boolean = {
219+
val valuesChanged = super.merge(other, interpreter)
220+
var aliasesChanged = false
221+
val aliasingOther = other.asInstanceOf[AliasingFrame[_]]
222+
for (i <- aliasIds.indices) {
223+
val thisAliases = aliasesOf(i)
224+
val thisNotOther = thisAliases diff (thisAliases intersect aliasingOther.aliasesOf(i))
225+
if (thisNotOther.nonEmpty) {
226+
aliasesChanged = true
227+
thisNotOther foreach removeAlias
228+
}
229+
}
230+
valuesChanged || aliasesChanged
231+
}
232+
233+
override def init(src: Frame[_ <: V]): Frame[V] = {
234+
super.init(src)
235+
compat.Platform.arraycopy(src.asInstanceOf[AliasingFrame[_]].aliasIds, 0, aliasIds, 0, aliasIds.length)
236+
this
237+
}
238+
}
239+
240+
/**
241+
* An analyzer that uses AliasingFrames instead of bare Frames. This can be used when an analysis
242+
* needs to track aliases, but doesn't require a more specific Frame subclass.
243+
*/
244+
class AliasingAnalyzer[V <: Value](interpreter: Interpreter[V]) extends Analyzer[V](interpreter) {
245+
override def newFrame(nLocals: Int, nStack: Int): AliasingFrame[V] = new AliasingFrame(nLocals, nStack)
246+
override def newFrame(src: Frame[_ <: V]): AliasingFrame[V] = new AliasingFrame(src)
247+
}

0 commit comments

Comments
 (0)