1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /*
 25  * @test
 26  * @run testng TestStringConcatTransform
 27  */
 28 
 29 import org.testng.Assert;
 30 import org.testng.annotations.DataProvider;
 31 import org.testng.annotations.NoInjection;
 32 import org.testng.annotations.Test;
 33 
 34 import java.lang.reflect.InvocationTargetException;
 35 import java.lang.reflect.code.OpTransformer;
 36 import java.lang.reflect.code.analysis.StringConcatTransformer;
 37 
 38 import java.lang.reflect.Method;
 39 import java.lang.reflect.code.analysis.SSA;
 40 import java.lang.reflect.code.interpreter.Interpreter;
 41 import java.lang.reflect.code.op.CoreOp;
 42 import java.lang.runtime.CodeReflection;
 43 import java.util.Arrays;
 44 import java.util.HashMap;
 45 import java.util.Map;
 46 
 47 public class TestStringConcatTransform {
 48 
 49     static final String TESTSTR = "TESTING STRING";
 50 
 51     static final Map<Class<?>, Object> valMap;
 52 
 53     static {
 54         valMap = new HashMap<>();
 55         valMap.put(byte.class, (byte) 42);
 56         valMap.put(short.class, (short) 42);
 57         valMap.put(int.class, 42);
 58         valMap.put(long.class, (long) 42);
 59         valMap.put(float.class, 42f);
 60         valMap.put(double.class, 42d);
 61         valMap.put(char.class, 'z');
 62         valMap.put(boolean.class, false);
 63 
 64         valMap.put(Byte.class, (byte) 42);
 65         valMap.put(Short.class, (short) 42);
 66         valMap.put(Integer.class, 42);
 67         valMap.put(Long.class, (long) 42);
 68         valMap.put(Float.class, 42f);
 69         valMap.put(Double.class, 42d);
 70         valMap.put(Character.class, 'z');
 71         valMap.put(Boolean.class, false);
 72 
 73         valMap.put(Object.class, new Object() {
 74             @Override
 75             public String toString() {
 76                 return "I'm a test string.";
 77             }
 78         });
 79         valMap.put(TestObject.class, new TestObject());
 80         valMap.put(String.class, TESTSTR);
 81         valMap.put(StringBuilder.class, new StringBuilder("test"));
 82     }
 83 
 84     public static final class TestObject {
 85         TestObject() {
 86         }
 87 
 88         @Override
 89         public String toString() {
 90             return "TestObject String";
 91         }
 92     }
 93 
 94     @Test(dataProvider = "getClassMethods")
 95     public void testModelTransform(@NoInjection Method method) {
 96         CoreOp.FuncOp model = method.getCodeModel().orElseThrow();
 97         CoreOp.FuncOp f_transformed = model.transform(new StringConcatTransformer());
 98         Object[] args = prepArgs(method);
 99 
100         model.writeTo(System.out);
101         f_transformed.writeTo(System.out);
102 
103         var interpreted = Interpreter.invoke(model, args);
104         var transformed_interpreted = Interpreter.invoke(f_transformed, args);
105 
106         Assert.assertEquals(interpreted, transformed_interpreted);
107 
108     }
109 
110     @Test(dataProvider = "getClassMethods")
111     public void testSSAModelTransform(@NoInjection Method method) {
112         Object[] args = prepArgs(method);
113         testStringConcat(method, args);
114     }
115 
116     //Testing to make sure StringBuilders aren't caught up in the concat transformation
117     @Test
118     public void testStringBuilderUnchanged() {
119         Method method;
120 
121         try {
122             method = TestStringConcatTransform.class.getMethod("stringBuilderArgCheck", String.class, String.class, StringBuilder.class);
123         } catch (NoSuchMethodException e) {
124            throw new RuntimeException(e);
125         }
126         Object[] args = {"Foo", "Bar", new StringBuilder("test")};
127         testStringConcat(method, args);
128 
129         Assert.assertEquals("test", args[2].toString());
130     }
131 
132     private void testStringConcat(Method method, Object[] args) {
133         CoreOp.FuncOp model = method.getCodeModel().orElseThrow();
134         CoreOp.FuncOp transformed_model = model.transform(new StringConcatTransformer());
135         CoreOp.FuncOp ssa_model = generateSSA(model);
136         CoreOp.FuncOp ssa_transformed_model = ssa_model.transform(new StringConcatTransformer());
137 
138         var model_interpreted = Interpreter.invoke(model, args);
139         var transformed_model_interpreted = Interpreter.invoke(transformed_model, args);
140         var ssa_interpreted = Interpreter.invoke(ssa_model, args);
141         var ssa_transformed_interpreted = Interpreter.invoke(ssa_transformed_model, args);
142         Object jvm_interpreted;
143         try {
144             jvm_interpreted = method.invoke(null, args);
145         } catch (IllegalAccessException | InvocationTargetException e) {
146             throw new RuntimeException(e);
147         }
148         Assert.assertEquals(model_interpreted, transformed_model_interpreted);
149         Assert.assertEquals(transformed_model_interpreted, ssa_interpreted);
150         Assert.assertEquals(ssa_interpreted, ssa_transformed_interpreted);
151         Assert.assertEquals(ssa_transformed_interpreted, jvm_interpreted);
152 
153     }
154 
155     public static Object[] prepArgs(Method m) {
156         Class<?>[] argTypes = m.getParameterTypes();
157         Object[] args = new Object[argTypes.length];
158         for (int i = 0; i < argTypes.length; i++) {
159             args[i] = valMap.get(argTypes[i]);
160         }
161         return args;
162     }
163 
164     @DataProvider(name = "getClassMethods")
165     public static Object[][] getClassMethods() {
166         return getTestMethods(TestStringConcatTransform.class);
167     }
168 
169     public static Object[][] getTestMethods(Class<?> clazz) {
170         Object[][] res = Arrays.stream(clazz.getMethods())
171                 .filter((method) -> method.isAnnotationPresent(CodeReflection.class))
172                 .map(m -> new Object[]{m})
173                 .toArray(Object[][]::new);
174         return res;
175     }
176 
177     static CoreOp.FuncOp generateSSA(CoreOp.FuncOp f) {
178         CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
179         lf = SSA.transform(lf);
180         lf.writeTo(System.out);
181         return lf;
182     }
183 
184     @CodeReflection
185     public static String intConcat(int i, String s) {
186         return i + s + "hello" + 52;
187     }
188 
189 
190     @CodeReflection
191     public static String intConcatAssignment(int i, String s) {
192         String s1 = i + s;
193         return s1 + "hello" + 52;
194     }
195 
196     @CodeReflection
197     public static String intConcatExprAssignment(int i, String s) {
198         String r;
199         String inter = i + (r = s + "hello") + 52;
200         return r + inter;
201     }
202 
203     @CodeReflection
204     public static String intConcatWideExpr(int i, String s) {
205         String s1 = i + s;
206         return s1 + "hello" + 52 + "world" + 26 + "!";
207     }
208 
209     @CodeReflection
210     public static String intConcatDoubVar(int i, String s) {
211         String r;
212         String inter = i + (r = s + "hello") + 52;
213         String inter2 = i + (r = s + "hello") + 52 + inter;
214         return r + inter2;
215     }
216 
217     @CodeReflection
218     public static String intConcatNestedSplit(int i, String s) {
219         String q, r;
220         String inter = i + (q = r = s + "hello") + 52;
221         return q + r + inter;
222     }
223 
224     @CodeReflection
225     public static String nonLeftAssociativeTree(String a, String b, String c, String d) {
226         String s = (a + b) + (c + d);
227         return s;
228     }
229 
230     @CodeReflection
231     public static String stringBuilderCheck(String a, String d) {
232         StringBuilder sb = new StringBuilder("test");
233         String s = sb + a;
234         String t = s + d;
235         return t;
236     }
237 
238     @CodeReflection
239     public static String stringBuilderArgCheck(String a, String d, StringBuilder c) {
240         StringBuilder sb = c;
241         String s = sb + a;
242         String t = s + d;
243         return t;
244     }
245 
246     @CodeReflection
247     public static String leftAssociativeTree(String a, String b, String c, String d) {
248         String s = ((a + b) + c) + d;
249         return s;
250     }
251 
252     @CodeReflection
253     public static String rightAssociativeTree(String a, String b, String c, String d) {
254         String s = (a + (b + (c + d)));
255         return s;
256     }
257 
258     @CodeReflection
259     public static String widenPrimitives(short a, byte b, int c, int d) {
260         String s = (a + (b + (c + d + "hi")));
261         return s;
262     }
263 }
264