1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
|
package scala.tools.nsc
package backend.jvm
package opt
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.Test
import scala.tools.asm.Opcodes._
import org.junit.Assert._
import scala.tools.asm.tree._
import scala.tools.testing.AssertUtil._
import CodeGenTools._
import scala.tools.partest.ASMConverters
import ASMConverters._
import AsmUtils._
import scala.collection.convert.decorateAsScala._
import scala.tools.testing.ClearAfterClass
object InlinerIllegalAccessTest extends ClearAfterClass.Clearable {
var compiler = newCompiler(extraArgs = "-Ybackend:GenBCode -Yopt:l:none")
def clear(): Unit = { compiler = null }
}
@RunWith(classOf[JUnit4])
class InlinerIllegalAccessTest extends ClearAfterClass {
ClearAfterClass.stateToClear = InlinerIllegalAccessTest
val compiler = InlinerIllegalAccessTest.compiler
import compiler.genBCode.bTypes._
def addToRepo(cls: List[ClassNode]): Unit = for (c <- cls) byteCodeRepository.classes(c.name) = (c, ByteCodeRepository.Classfile)
def assertEmpty(ins: Option[AbstractInsnNode]) = for (i <- ins) throw new AssertionError(textify(i))
@Test
def typeAccessible(): Unit = {
val code =
"""package a {
| private class C { // the Scala compiler makes all classes public
| def f1 = new C // NEW a/C
| def f2 = new Array[C](0) // ANEWARRAY a/C
| def f3 = new Array[Array[C]](0) // ANEWARRAY [La/C;
| }
| class D
|}
|package b {
| class E
|}
""".stripMargin
val allClasses = compileClasses(compiler)(code)
val List(cClass, dClass, eClass) = allClasses
assert(cClass.name == "a/C" && dClass.name == "a/D" && eClass.name == "b/E", s"${cClass.name}, ${dClass.name}, ${eClass.name}")
addToRepo(allClasses) // they are not on the compiler's classpath, so we add them manually to the code repo
val methods = cClass.methods.asScala.filter(_.name(0) == 'f').toList
def check(classNode: ClassNode, test: Option[AbstractInsnNode] => Unit) = {
for (m <- methods)
test(inliner.findIllegalAccess(m.instructions, classBTypeFromParsedClassfile(classNode.name)))
}
check(cClass, assertEmpty)
check(dClass, assertEmpty)
check(eClass, assertEmpty) // C is public, so accessible in E
byteCodeRepository.classes.clear()
classBTypeFromInternalName.clear()
cClass.access &= ~ACC_PUBLIC // ftw
addToRepo(allClasses)
// private classes can be accessed from the same package
check(cClass, assertEmpty)
check(dClass, assertEmpty) // accessing a private class in the same package is OK
check(eClass, {
case Some(ti: TypeInsnNode) if Set("a/C", "[La/C;")(ti.desc) => ()
// MatchError otherwise
})
}
@Test
def memberAccessible(): Unit = {
val code =
"""package a {
| class C {
| /*public*/ def a = 0
| /*default*/ def b = 0
| protected def c = 0
| private def d = 0
|
| /*public static*/ def e = 0
| /*default static*/ def f = 0
| protected /*static*/ def g = 0
| private /*static*/ def h = 0
|
| def raC = a
| def rbC = b
| def rcC = c
| def rdC = d
| def reC = e
| def rfC = f
| def rgC = g
| def rhC = h
| }
|
| class D extends C {
| def rbD = b // 1: default access b, accessed in D, declared in C. can be inlined into any class in the same package as C.
| def rcD = c // 2: protected c, accessed in D. can be inlined into C, D or E, but not into F (F and D are unrelated).
|
| def rfD = f // 1
| def rgD = g // 2
| }
| class E extends D
|
| class F extends C
|
| class G
|}
|
|package b {
| class H extends a.C
| class I
|}
""".stripMargin
val allClasses = compileClasses(compiler)(code)
val List(cCl, dCl, eCl, fCl, gCl, hCl, iCl) = allClasses
addToRepo(allClasses)
// set flags that Scala scala doesn't (default access, static) - a hacky way to test all access modes.
val names = ('a' to 'h').map(_.toString).toSet
val List(a, b, c, d, e, f, g, h) = cCl.methods.asScala.toList.filter(m => names(m.name))
def checkAccess(a: MethodNode, expected: Int): Unit = {
assert((a.access & (ACC_STATIC | ACC_PUBLIC | ACC_PROTECTED | ACC_PRIVATE)) == expected, s"${a.name}, ${a.access}")
}
checkAccess(a, ACC_PUBLIC)
b.access &= ~ACC_PUBLIC; checkAccess(b, 0) // make it default access
c.access &= ~ACC_PUBLIC; c.access |= ACC_PROTECTED; checkAccess(c, ACC_PROTECTED) // make it protected - scalac actually never emits PROTECTED in bytecode, see javaFlags in BTypesFromSymbols
checkAccess(d, ACC_PRIVATE)
e.access |= ACC_STATIC; checkAccess(e, ACC_STATIC | ACC_PUBLIC)
f.access &= ~ACC_PUBLIC; f.access |= ACC_STATIC; checkAccess(f, ACC_STATIC)
g.access &= ~ACC_PUBLIC; g.access |= (ACC_STATIC | ACC_PROTECTED); checkAccess(g, ACC_STATIC | ACC_PROTECTED)
h.access |= ACC_STATIC; checkAccess(h, ACC_STATIC | ACC_PRIVATE)
val List(raC, rbC, rcC, rdC, reC, rfC, rgC, rhC) = cCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name)
val List(rbD, rcD, rfD, rgD) = dCl.methods.asScala.toList.filter(_.name(0) == 'r').sortBy(_.name)
def check(method: MethodNode, dest: ClassNode, test: Option[AbstractInsnNode] => Unit): Unit = {
test(inliner.findIllegalAccess(method.instructions, classBTypeFromParsedClassfile(dest.name)))
}
val cOrDOwner = (_: Option[AbstractInsnNode] @unchecked) match {
case Some(mi: MethodInsnNode) if Set("a/C", "a/D")(mi.owner) => ()
// MatchError otherwise
}
// PUBLIC
// public methods allowed everywhere
for (m <- Set(raC, reC); c <- allClasses) check(m, c, assertEmpty)
// DEFAULT ACCESS
// default access OK in same package
for (m <- Set(rbC, rfC, rbD, rfD); c <- allClasses) {
if (c.name startsWith "a/") check(m, c, assertEmpty)
else check(m, c, cOrDOwner)
}
// PROTECTED
// protected accessed in same class, or protected static accessed in subclass(rgD).
// can be inlined to subclasses, and classes in the same package (gCl)
for (m <- Set(rcC, rgC, rgD); c <- Set(cCl, dCl, eCl, fCl, gCl, hCl)) check(m, c, assertEmpty)
// protected in non-subclass and different package
for (m <- Set(rcC, rgC)) check(m, iCl, cOrDOwner)
// non-static protected accessed in subclass (rcD). can be inlined to related class, or classes in the same package
for (c <- Set(cCl, dCl, eCl, fCl, gCl)) check(rcD, c, assertEmpty)
// rcD cannot be inlined into non-related classes, if the declaration and destination are not in the same package
for (c <- Set(hCl, iCl)) check(rcD, c, cOrDOwner)
// PRIVATE
// privated method accesses can only be inlined in the same class
for (m <- Set(rdC, rhC)) check(m, cCl, assertEmpty)
for (m <- Set(rdC, rhC); c <- allClasses.tail) check(m, c, cOrDOwner)
}
}
|