| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.codehaus.groovy.classgen.asm.sc |
| |
| import org.codehaus.groovy.classgen.asm.AbstractBytecodeTestCase |
| import org.codehaus.groovy.runtime.MethodClosure |
| |
| import static org.codehaus.groovy.control.CompilerConfiguration.DEFAULT as config |
| |
| class StaticCompilationTest extends AbstractBytecodeTestCase { |
| |
| void testEmptyMethod() { |
| def bytecode = compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m() {} |
| ''') |
| assert bytecode.hasStrictSequence( |
| ['public m()V', 'L0', 'LINENUMBER 3 L0', 'RETURN'] |
| ) |
| } |
| |
| void testPrimitiveReturn1() { |
| def bytecode = compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| int m() { 1 } |
| ''') |
| assert bytecode.hasStrictSequence( |
| ['ICONST_1', 'IRETURN'] |
| ) |
| } |
| |
| void testPrimitiveReturn2() { |
| def bytecode = compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| long m() { 1L } |
| ''') |
| assert bytecode.hasStrictSequence( |
| ['LCONST_1', 'LRETURN'] |
| ) |
| } |
| |
| void testPrimitiveReturn3() { |
| def bytecode = compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| short m() { 1 } |
| ''') |
| assert bytecode.hasStrictSequence( |
| ['ICONST_1', 'I2S', 'IRETURN'] |
| ) |
| } |
| |
| void testPrimitiveReturn4() { |
| def bytecode = compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| byte m() { 1 } |
| ''') |
| assert bytecode.hasStrictSequence( |
| ['ICONST_1', 'I2B', 'IRETURN'] |
| ) |
| } |
| |
| void testIdentityReturns() { |
| def bytecode = compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| int m(int i) { i } |
| ''') |
| assert bytecode.hasStrictSequence( |
| ['ILOAD', 'IRETURN'] |
| ) |
| |
| bytecode = compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| long m(long l) { l } |
| ''') |
| assert bytecode.hasStrictSequence( |
| ['LLOAD', 'LRETURN'] |
| ) |
| |
| bytecode = compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| short m(short l) { l } |
| ''') |
| assert bytecode.hasStrictSequence( |
| ['ILOAD', 'IRETURN'] |
| ) |
| |
| bytecode = compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| float m(float l) { l } |
| ''') |
| assert bytecode.hasStrictSequence( |
| ['FLOAD', 'FRETURN'] |
| ) |
| |
| bytecode = compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| double m(double l) { l } |
| ''') |
| assert bytecode.hasStrictSequence( |
| ['DLOAD', 'DRETURN'] |
| ) |
| |
| bytecode = compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| Object m(Object l) { l } |
| ''') |
| assert bytecode.hasStrictSequence( |
| ['ALOAD', 'ARETURN'] |
| ) |
| } |
| |
| void testSingleAssignment() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m() { |
| int a = 1 |
| } |
| ''').hasSequence([ |
| "ICONST_1", |
| "ISTORE", |
| "RETURN" |
| ]) |
| } |
| |
| void testReturnSingleAssignment() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| int m() { |
| int a = 1 |
| } |
| ''').hasSequence([ |
| "ICONST_1", |
| "ISTORE", |
| "ILOAD", |
| "IRETURN" |
| ]) |
| } |
| |
| void testIntLeftShift() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m() { |
| int a = 1 |
| int b = a << 32 |
| } |
| ''').hasStrictSequence([ |
| "ILOAD", |
| "BIPUSH 32", |
| "ISHL" |
| ]) |
| } |
| |
| void testLongLeftShift() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m() { |
| long a = 1L |
| long b = a << 32 |
| } |
| ''').hasStrictSequence([ |
| "LLOAD", |
| "BIPUSH 32", |
| "LSHL" |
| ]) |
| } |
| |
| void testArrayGet() { |
| if (config.indyEnabled) return; |
| // this test is done with indy in another tests case |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m(int[] arr) { |
| arr[0] |
| } |
| ''').hasStrictSequence([ |
| "ALOAD 1", |
| "ICONST_0", |
| "INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.intArrayGet ([II)I" |
| ]) |
| } |
| |
| void testArraySet() { |
| if (config.indyEnabled) return; |
| // this test is done with indy in another tests case |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m(int[] arr) { |
| arr[0] = 0 |
| } |
| ''').hasStrictSequence([ |
| "ICONST_0", |
| "ISTORE 2", |
| "ALOAD 1", |
| "ICONST_0", |
| "ILOAD 2", |
| "INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.intArraySet ([III)V" |
| ]) |
| } |
| |
| /* void testPlusPlus() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m() { |
| int i = 0 |
| i++ |
| } |
| ''').hasStrictSequence([ |
| "IINC", |
| ]) |
| } |
| |
| void testMinusMinus() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m() { |
| int i = 0 |
| i-- |
| } |
| ''').hasStrictSequence([ |
| "IINC", |
| ]) |
| } |
| |
| void testPlusEquals() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| int m() { |
| int i = 0 |
| i += 13 |
| return i |
| } |
| ''').hasStrictSequence([ |
| "ILOAD", |
| "ILOAD", |
| "IADD", |
| "ISTORE" |
| ]) |
| } |
| |
| void testPlusEqualsFromArgs() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m(int i, int j) { |
| i += j |
| } |
| ''').hasStrictSequence([ |
| "ILOAD", |
| "ILOAD", |
| "IADD", |
| "ISTORE" |
| ]) |
| }*/ |
| |
| void testFlow() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| String m(String str) { |
| def obj = 1 |
| obj = str |
| obj.toUpperCase() |
| } |
| m 'Cedric' |
| ''').hasStrictSequence([ |
| "ICONST", |
| "INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;", |
| "ASTORE", |
| "L1", |
| "ALOAD 2", |
| "POP", |
| "L2", |
| "LINENUMBER", |
| "ALOAD 1", |
| "ASTORE 3", |
| "ALOAD 3", |
| "ASTORE 2", |
| "ALOAD 3", |
| "POP", |
| "L3", |
| "LINENUMBER", |
| "ALOAD 2", |
| "CHECKCAST java/lang/String", |
| "INVOKEVIRTUAL java/lang/String.toUpperCase ()Ljava/lang/String;", |
| "ARETURN", |
| "L4" |
| ]) |
| } |
| |
| void testInstanceOf() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m(Object str) { |
| if (str instanceof String) { |
| str.toUpperCase() |
| } |
| } |
| m 'Cedric' |
| ''').hasStrictSequence([ |
| "ALOAD", |
| "CHECKCAST java/lang/String", |
| "INVOKEVIRTUAL java/lang/String.toUpperCase ()Ljava/lang/String;" |
| ]) |
| } |
| |
| void testShouldGenerateDirectConstructorCall() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| class Foo { |
| String msg |
| Foo(int x, String y) { msg = y*x } |
| static Foo foo() { |
| Foo result = [2,'Bar'] |
| } |
| } |
| ''').hasStrictSequence([ |
| 'ICONST_2', |
| 'LDC "Bar"', |
| 'INVOKESPECIAL Foo.<init> (ILjava/lang/String;)V' |
| ]) |
| } |
| |
| void testShouldGenerateDirectArrayConstruct() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m() { |
| int[] arr = [123,456] |
| } |
| ''').hasStrictSequence([ |
| 'ICONST_2', |
| 'NEWARRAY T_INT', |
| 'DUP', |
| 'ICONST_0', |
| 'BIPUSH 123', |
| 'IASTORE' |
| ]) |
| } |
| |
| void testShouldGenerateDirectBooleanArrayConstruct() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m() { |
| boolean[] arr = [123,false] |
| } |
| ''').hasStrictSequence([ |
| 'ICONST_2', |
| 'NEWARRAY T_BOOLEAN', |
| 'DUP', |
| 'ICONST_0', |
| 'BIPUSH 123', |
| 'INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;', |
| 'INVOKESTATIC org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox (Ljava/lang/Object;)Z', |
| 'BASTORE' |
| ]) |
| } |
| |
| void testShouldTriggerDirectCallToOuterClassGetter() { |
| assert compile(method: 'fromInner', classNamePattern: '.*Inner.*', ''' |
| class Holder { |
| String value |
| } |
| |
| @groovy.transform.CompileStatic |
| class Outer { |
| String outerProperty = 'outer' |
| private class Inner { |
| String fromInner() { |
| Holder holder = new Holder() |
| holder.value = outerProperty |
| holder.value |
| } |
| } |
| |
| String blah() { |
| new Inner().fromInner() |
| } |
| } |
| |
| def o = new Outer() |
| assert o.blah() == 'outer' |
| ''').hasStrictSequence([ |
| 'GETFIELD Outer$Inner.this$0', |
| 'INVOKEVIRTUAL Outer.getOuterProperty', |
| 'DUP', |
| 'ASTORE', |
| 'ALOAD', |
| 'ALOAD', |
| 'INVOKEVIRTUAL Holder.setValue' |
| ]) |
| } |
| |
| void testShouldOptimizeBytecodeByAvoidingCreationOfMopMethods() { |
| def shell = new GroovyShell() |
| def clazz = shell.evaluate ''' |
| import groovy.transform.TypeCheckingMode |
| import groovy.transform.CompileStatic |
| |
| @CompileStatic |
| class A { |
| def doSomething() { 'A' } |
| } |
| |
| @CompileStatic |
| class B extends A { |
| def doSomething() { 'B' + super.doSomething() } |
| } |
| |
| B |
| ''' |
| assert clazz instanceof Class |
| assert clazz.name == 'B' |
| def mopMethods = clazz.declaredMethods.findAll { it.name =~ /(super|this)\$/ } |
| assert mopMethods.empty |
| } |
| |
| void testShouldNotOptimizeBytecodeForMopMethodsBecauseOfSkip() { |
| def shell = new GroovyShell() |
| def clazz = shell.evaluate ''' |
| import groovy.transform.TypeCheckingMode |
| import groovy.transform.CompileStatic |
| |
| @CompileStatic |
| class A { |
| def doSomething() { 'A' } |
| } |
| |
| @CompileStatic |
| class B extends A { |
| @CompileStatic(TypeCheckingMode.SKIP) |
| def doSomething() { 'B' + super.doSomething() } |
| } |
| |
| B |
| ''' |
| assert clazz instanceof Class |
| assert clazz.name == 'B' |
| def mopMethods = clazz.declaredMethods.findAll { it.name =~ /(super|this)\$/ } |
| assert !mopMethods.empty |
| } |
| |
| // GROOVY-7124 |
| void testUseInvokeVirtualPreferredOverInvokeInterface() { |
| assert compile(method: 'foo', classNamePattern: 'B', ''' |
| interface A { void m() } |
| class B implements A { |
| void m() {} |
| @groovy.transform.CompileStatic |
| void foo() { |
| m() |
| } |
| } |
| ''').hasStrictSequence(['INVOKEVIRTUAL B.m']) |
| } |
| |
| void testShouldNotTryToCastToSupposedDelegateType() { |
| assertScript ''' |
| @groovy.transform.CompileStatic |
| class ClassCastOhNoes { |
| def foo(def o) { |
| def cl = { |
| delegate.getClass() |
| } |
| cl.delegate = o |
| cl() |
| } |
| } |
| new ClassCastOhNoes().foo(100) |
| ''' |
| } |
| |
| void testShouldOptimizeCharInit() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m() { |
| char c = 'x' |
| } |
| ''').hasStrictSequence([ |
| 'LINENUMBER 4', |
| 'BIPUSH 120', |
| 'ISTORE' |
| ]) |
| } |
| |
| void testShouldOptimizeCharComparison() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m() { |
| char c1 = 'x' |
| char c2 = 'x' |
| boolean b = c1==c2 |
| } |
| ''').hasSequence([ |
| 'LINENUMBER 4', |
| 'BIPUSH 120', |
| 'ISTORE', |
| 'BIPUSH 120', |
| 'ISTORE', |
| 'ILOAD', |
| 'ILOAD', |
| 'IF_ICMPNE', |
| ]) |
| |
| // make sure the code passes |
| assertScript ''' |
| @groovy.transform.CompileStatic |
| void m() { |
| char c1 = 'x' |
| char c2 = 'x' |
| boolean b = c1==c2 |
| assert b |
| char c3 = 'z' |
| b = c1==c3 |
| assert !b |
| } |
| m() |
| ''' |
| } |
| |
| void testForEachLoopOptimization() { |
| def loop_init = [ |
| 'ALOAD', // load array (might be more complex than just ALOAD in general case) |
| 'DUP', |
| 'ASTORE', // store array to local var |
| 'ARRAYLENGTH', // get array len |
| 'ISTORE', // store it to local var |
| 'ICONST_0', |
| 'ISTORE', // initialize loop index |
| 'ILOAD', // load loop index |
| 'ILOAD', // load array length |
| 'IF_ICMPGE' // if greater or equal, end of loop |
| ] |
| |
| // int[] |
| def intExample = ''' |
| @groovy.transform.CompileStatic |
| void m(int[] arr) { |
| for (int i: arr) { |
| println(i) |
| } |
| } |
| m([1,2] as int[]) |
| ''' |
| assert compile(method:'m', intExample).hasSequence([*loop_init,'IALOAD','ISTORE']) |
| |
| // short[] |
| def shortExample = ''' |
| @groovy.transform.CompileStatic |
| void m(short[] arr) { |
| for (short i: arr) { |
| println(i) |
| } |
| } |
| m([1,2] as short[]) |
| ''' |
| assert compile(method:'m', shortExample).hasSequence([*loop_init,'SALOAD','ISTORE']) |
| |
| // byte[] |
| def byteExample = ''' |
| @groovy.transform.CompileStatic |
| void m(byte[] arr) { |
| for (byte i: arr) { |
| println(i) |
| } |
| } |
| m([1,2] as byte[]) |
| ''' |
| assert compile(method:'m', byteExample).hasSequence([*loop_init,'BALOAD','ISTORE']) |
| |
| // long[] |
| def longExample = ''' |
| @groovy.transform.CompileStatic |
| void m(long[] arr) { |
| for (long i: arr) { |
| println(i) |
| } |
| } |
| m([1,2] as long[]) |
| ''' |
| assert compile(method:'m', longExample).hasSequence([*loop_init,'LALOAD','LSTORE']) |
| |
| // char[] |
| def charExample = ''' |
| @groovy.transform.CompileStatic |
| void m(char[] arr) { |
| for (char i: arr) { |
| println(i) |
| } |
| } |
| m('foo'.toCharArray()) |
| ''' |
| assert compile(method:'m', charExample).hasSequence([*loop_init,'CALOAD','ISTORE']) |
| |
| // boolean[] |
| def boolExample = ''' |
| @groovy.transform.CompileStatic |
| void m(boolean[] arr) { |
| for (boolean i: arr) { |
| println(i) |
| } |
| } |
| m([true,false] as boolean[]) |
| ''' |
| assert compile(method:'m', boolExample).hasSequence([*loop_init,'BALOAD','ISTORE']) |
| |
| // float[] |
| def floatExample = ''' |
| @groovy.transform.CompileStatic |
| void m(float[] arr) { |
| for (float i: arr) { |
| println(i) |
| } |
| } |
| m([1.5f,2.0f] as float[]) |
| ''' |
| assert compile(method:'m', floatExample).hasSequence([*loop_init,'FALOAD','FSTORE']) |
| |
| // double[] |
| def doubleExample = ''' |
| @groovy.transform.CompileStatic |
| void m(double[] arr) { |
| for (double i: arr) { |
| println(i) |
| } |
| } |
| m([1.1,2.2] as double[]) |
| ''' |
| assert compile(method:'m', doubleExample).hasSequence([*loop_init,'DALOAD','DSTORE']) |
| |
| // Any[] |
| def anyExample = ''' |
| @groovy.transform.CompileStatic |
| void m(String[] arr) { |
| for (String i: arr) { |
| println(i) |
| } |
| } |
| m(['a','b'] as String[]) |
| ''' |
| assert compile(method:'m', anyExample).hasSequence([*loop_init,'AALOAD','ASTORE']) |
| |
| // now check that everything runs fine |
| [byteExample,shortExample, intExample, charExample, boolExample, longExample, floatExample, doubleExample, anyExample].each { script -> |
| assertScript(script) |
| } |
| } |
| |
| void testCompareWithCharOptimization() { |
| String code = ''' |
| @groovy.transform.CompileStatic |
| boolean m(char[] arr) { |
| char c = arr[0] |
| ' '==c |
| } |
| assert m(' abc '.toCharArray()) == true |
| ''' |
| assert compile(method:'m',code).hasSequence(['BIPUSH','ILOAD','IF_ICMPNE']) |
| assertScript(code) |
| |
| code = ''' |
| @groovy.transform.CompileStatic |
| boolean m(char[] arr) { |
| char c = arr[0] |
| c==' ' |
| } |
| assert m(' abc '.toCharArray()) == true |
| ''' |
| assert compile(method:'m',code).hasSequence(['ILOAD','BIPUSH','IF_ICMPNE']) |
| assertScript(code) |
| } |
| |
| void testShouldRemoveUnnecessaryCast() { |
| assert compile(method: 'm', ''' |
| @groovy.transform.CompileStatic |
| void m() { |
| char c = (char) 'x' |
| } |
| ''').hasStrictSequence([ |
| 'LINENUMBER 4', |
| 'BIPUSH 120', |
| // No checkcast, but the idea is to check that further optimization was done |
| // because the RHS is no longer a CastExpression but a ConstantExpression |
| 'ISTORE' |
| ]) |
| } |
| |
| void testInstanceMethodReference() { |
| // dynamic case should be a method closure |
| assert String::toUpperCase instanceof MethodClosure |
| |
| // static case should be compiled into functional interface |
| String code = ''' |
| import groovy.transform.CompileStatic |
| import java.util.stream.Collectors |
| |
| @CompileStatic |
| void m() { |
| assert ['foo'].stream().map(String::toUpperCase).collect(Collectors.toList()) == ['FOO'] |
| } |
| m() |
| ''' |
| assert compile(method:'m', code).hasSequence([ |
| 'INVOKEDYNAMIC apply()Ljava/util/function/Function;', |
| /* handle kind 0x6 : INVOKESTATIC */ |
| 'java/lang/invoke/LambdaMetafactory.metafactory', |
| /* handle kind 0x5 : INVOKEVIRTUAL */ |
| 'java/lang/String.toUpperCase()Ljava/lang/String;' |
| ]) |
| assertScript(code) |
| } |
| } |