summaryrefslogtreecommitdiff
path: root/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala
blob: b1aa27fd2737fa89a7baedb0ac1288b0be646caf (plain) (blame)
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
199
200
201
202
203
204
205
206
package scala.tools.nsc
package backend.jvm
package opt

import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

import scala.tools.testing.BytecodeTesting
import scala.tools.testing.BytecodeTesting._

@RunWith(classOf[JUnit4])
class InlineWarningTest extends BytecodeTesting {
  def optCp = "-opt:l:classpath"
  override def compilerArgs = s"$optCp -opt-warnings"

  import compiler._

  val compilerWarnAll = cached("compilerWarnAll", () => newCompiler(extraArgs = s"$optCp -opt-warnings:_"))

  @Test
  def nonFinal(): Unit = {
    val code =
      """class C {
        |  @inline def m1 = 1
        |}
        |trait T {
        |  @inline def m2 = 1
        |}
        |class D extends C with T
        |
        |class Test {
        |  def t1(c: C, t: T, d: D) = c.m1 + t.m2 + d.m1 + d.m2
        |}
      """.stripMargin
    var count = 0
    val warns = Set(
      "C::m1()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.",
      "T::m2()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.",
      "D::m2()I is annotated @inline but could not be inlined:\nThe method is not final and may be overridden.")
    compileToBytes(code, allowMessage = i => {count += 1; warns.exists(i.msg contains _)})
    assert(count == 4, count)
  }

  @Test
  def handlerNonEmptyStack(): Unit = {
    val code =
      """class C {
        |  @noinline def q = 0
        |  @inline final def foo = try { q } catch { case e: Exception => 2 }
        |  def t1 = println(foo) // inline warning here: foo cannot be inlined on top of a non-empty stack
        |}
      """.stripMargin

    var c = 0
    compileToBytes(code, allowMessage = i => {c += 1; i.msg contains "operand stack at the callsite in C::t1()V contains more values"})
    assert(c == 1, c)
  }

//  @Test -- TODO
  def mixedWarnings(): Unit = {
    val javaCode =
      """public class A {
        |  public static final int bar() { return 100; }
        |}
      """.stripMargin

    val scalaCode =
      """class B {
        |  @inline final def flop = A.bar
        |  def g = flop
        |}
      """.stripMargin

    val warns = List(
      """failed to determine if bar should be inlined:
        |The method bar()I could not be found in the class A or any of its parents.
        |Note that class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin,

      """B::flop()I is annotated @inline but could not be inlined:
        |Failed to check if B::flop()I can be safely inlined to B without causing an IllegalAccessError. Checking instruction INVOKESTATIC A.bar ()I failed:
        |The method bar()I could not be found in the class A or any of its parents.
        |Note that class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin)

    var c = 0
    val List(b) = compileToBytes(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; warns.tail.exists(i.msg contains _)})
    assert(c == 1, c)

    // no warnings here
    newCompiler(extraArgs = s"$optCp -opt-warnings:none").compileToBytes(scalaCode, List((javaCode, "A.java")))

    c = 0
    newCompiler(extraArgs = s"$optCp -opt-warnings:no-inline-mixed").compileToBytes(scalaCode, List((javaCode, "A.java")), allowMessage = i => {c += 1; warns.exists(i.msg contains _)})
    assert(c == 2, c)
  }

  @Test
  def cannotInlinePrivateCallIntoDifferentClass(): Unit = {
    val code =
      """class A {
        |  @inline final def f = {
        |    @noinline def nested = 0
        |    nested
        |  }
        |
        |  def t = f // ok
        |}
        |
        |class B {
        |  def t(a: A) = a.f // not possible
        |}
      """.stripMargin

    val warn =
      """A::f()I is annotated @inline but could not be inlined:
        |The callee A::f()I contains the instruction INVOKESTATIC A.nested$1 ()I
        |that would cause an IllegalAccessError when inlined into class B""".stripMargin

    var c = 0
    compileToBytes(code, allowMessage = i => { c += 1; i.msg contains warn })
    assert(c == 1, c)
  }

  @Test
  def dontWarnWhenNotIlnineAnnotated(): Unit = {
    val code =
      """class A {
        |  final def f(t: Int => Int) = {
        |    @noinline def nested = 0
        |    nested + t(1)
        |  }
        |  def t = f(x => x + 1)
        |}
        |
        |class B {
        |  def t(a: A) = a.f(x => x + 1)
        |}
      """.stripMargin
    compileToBytes(code, allowMessage = _ => false) // no warnings allowed

    val warn =
      """A::f(Lscala/Function1;)I could not be inlined:
        |The callee A::f(Lscala/Function1;)I contains the instruction INVOKESTATIC A.nested$1 ()I
        |that would cause an IllegalAccessError when inlined into class B""".stripMargin

    var c = 0
    compilerWarnAll.compileToBytes(code, allowMessage = i => { c += 1; i.msg contains warn })
    assert(c == 1, c)
  }

  @Test
  def cannotMixStrictfp(): Unit = {
    val code =
      """import annotation.strictfp
        |class C {
        |  @strictfp @inline final def f = 0
        |  @strictfp def t1 = f
        |  def t2 = f
        |}
      """.stripMargin

    val warn =
      """C::f()I is annotated @inline but could not be inlined:
        |The callsite method C::t2()I
        |does not have the same strictfp mode as the callee C::f()I.""".stripMargin

    var c = 0
    compileToBytes(code, allowMessage = i => { c += 1; i.msg contains warn })
    assert(c == 1, c)
  }

  @Test // scala-dev#20
  def mixedCompilationSpuriousWarning(): Unit = {
    val jCode =
      """public class A {
        |  public static final int bar() { return 100; }
        |  public final int baz() { return 100; }
        |}
      """.stripMargin

    val sCode =
      """class C {
        |  @inline final def foo = A.bar()
        |  @inline final def fii(a: A) = a.baz()
        |  def t = foo + fii(new A)
        |}
      """.stripMargin

    val warns = List(
      """C::foo()I is annotated @inline but could not be inlined:
        |Failed to check if C::foo()I can be safely inlined to C without causing an IllegalAccessError. Checking instruction INVOKESTATIC A.bar ()I failed:
        |The method bar()I could not be found in the class A or any of its parents.
        |Note that class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin,

      """C::fii(LA;)I is annotated @inline but could not be inlined:
        |Failed to check if C::fii(LA;)I can be safely inlined to C without causing an IllegalAccessError. Checking instruction INVOKEVIRTUAL A.baz ()I failed:
        |The method baz()I could not be found in the class A or any of its parents.
        |Note that class A is defined in a Java source (mixed compilation), no bytecode is available.""".stripMargin
    )
    var c = 0
    compileClasses(sCode, javaCode = List((jCode, "A.java")), allowMessage = i => { c += 1;
      warns.exists(i.msg.contains)
    })
    assert(c == 2)
  }
}