1 import jdk.incubator.code.CodeTransformer;
  2 import jdk.incubator.code.Op;
  3 import jdk.incubator.code.Reflect;
  4 import jdk.incubator.code.TypeElement;
  5 import jdk.incubator.code.dialect.core.CoreOp;
  6 import jdk.incubator.code.dialect.core.CoreType;
  7 import jdk.incubator.code.dialect.java.JavaOp;
  8 import jdk.incubator.code.dialect.java.PrimitiveType;
  9 import jdk.incubator.code.interpreter.Interpreter;
 10 import org.junit.jupiter.api.Assertions;
 11 import org.junit.jupiter.api.Test;
 12 import org.junit.jupiter.params.ParameterizedTest;
 13 import org.junit.jupiter.params.provider.MethodSource;
 14 
 15 import java.lang.invoke.MethodHandles;
 16 import java.lang.reflect.Method;
 17 import java.lang.reflect.Modifier;
 18 import java.time.LocalDate;
 19 import java.util.Arrays;
 20 import java.util.List;
 21 import java.util.Optional;
 22 import java.util.stream.Stream;
 23 
 24 import static jdk.incubator.code.dialect.java.PrimitiveType.*;
 25 
 26 /*
 27  * @test
 28  * @modules jdk.incubator.code
 29  * @run junit TestEvaluation
 30  * @run main Unreflect TestEvaluation
 31  * @run junit TestEvaluation
 32  */
 33 public class TestEvaluation {
 34     @Reflect
 35     static int primitiveLiteral() {
 36         return 1;
 37     }
 38     @Reflect
 39     static String stringLiteral() {
 40         return "A";
 41     }
 42 
 43     @Reflect
 44     static int unaryOperator() {
 45         return +1;
 46     }
 47     @Reflect
 48     static int unaryOperator2() {
 49         return -1;
 50     }
 51     @Reflect
 52     static int unaryOperator3() {
 53         return ~1;
 54     }
 55     @Reflect
 56     static boolean unaryOperator4() {
 57         return !false;
 58     }
 59 
 60     @Reflect
 61     static int multiplicativeOperator() {
 62         return 1 * 2;
 63     }
 64     @Reflect
 65     static int multiplicativeOperator2() {
 66         return 1 / 2;
 67     }
 68     @Reflect
 69     static int multiplicativeOperator3() {
 70         return 1 % 2;
 71     }
 72 
 73     @Reflect
 74     static int additiveOperator() {
 75         return 1 + 2;
 76     }
 77     @Reflect
 78     static String additiveOperator2() {
 79         return "number " + 1;
 80     }
 81     @Reflect
 82     static int additiveOperator3() {
 83         return 1 - 2;
 84     }
 85 
 86     @Reflect
 87     static int shiftOperator() {
 88         return 1 << 2;
 89     }
 90     @Reflect
 91     static int shiftOperator2() {
 92         return 1 >> 2;
 93     }
 94     @Reflect
 95     static int shiftOperator3() {
 96         return -1 >>> 2;
 97     }
 98 
 99     @Reflect
100     static boolean relationalOperator() {
101         return 1 < 2;
102     }
103     @Reflect
104     static boolean relationalOperator2() {
105         return 1 <= 2;
106     }
107     @Reflect
108     static boolean relationalOperator3() {
109         return 1 > 2;
110     }
111     @Reflect
112     static boolean relationalOperator4() {
113         return 1 >= 2;
114     }
115 
116     @Reflect
117     static boolean equalityOperator() {
118         return 1 == 2;
119     }
120     @Reflect
121     static boolean equalityOperator2() {
122         return 1 != 2;
123     }
124     @Reflect
125     static boolean equalityOperator3() {
126         return "A" != "B";
127     }
128 
129     @Reflect
130     static int bitwiseOperator() {
131         return 1 & 2;
132     }
133     @Reflect
134     static int bitwiseOperator2() {
135         return 1 | 2;
136     }
137     @Reflect
138     static int bitwiseOperator3() {
139         return 1 ^ 2;
140     }
141 
142     @Reflect
143     static boolean logicalOperator() {
144         return true & false;
145     }
146     @Reflect
147     static boolean logicalOperator2() {
148         return false | true;
149     }
150     @Reflect
151     static boolean logicalOperator3() {
152         return true ^ false;
153     }
154 
155     @Reflect
156     static boolean conditionalAndOperator() {
157         return true && false;
158     }
159     @Reflect
160     static boolean conditionalOrOperator() {
161         return true || false;
162     }
163 
164     @Reflect
165     static int ternaryConditionalOperator() {
166         return 1 != 2 | 2 > 3 ? 3 : 4;
167     }
168 
169 
170     @Reflect
171     static int constantVar() {
172         final int x = 1;
173         return x;
174     }
175     @Reflect
176     static String strConstantVar() {
177         final String s = "u";
178         return s;
179     }
180     @Reflect
181     static String strConstantVar2() {
182         final int x = 1;
183         final String s = x == 1 ? "one" : "not one";
184         return s;
185     }
186     @Reflect
187     static String strConstantVar3() {
188         final String s = 123 + "456";
189         return s;
190     }
191     @Reflect
192     static String fcNotConstantVar() {
193         // initialized with a non-constant expression
194         final String s = LocalDate.now().toString();
195         return s;
196     }
197     @Reflect
198     static Object fcNotConstantVar2() {
199         // type not primitive nor String
200         final Object o = "s";
201         return o;
202     }
203     @Reflect
204     static int fcFinalVariableNotInitialized() {
205         final int i;
206         i = 1;
207         return i;
208     }
209 
210     //@Reflect
211     static int fcEffectivelyFinalVar() {
212         // @@@ should fail
213         // currently we lack sufficent info to determine if a variable was declared final in source code
214         int x = 1;
215         return x;
216     }
217 
218     static final int Y1 = 3;
219     @Reflect
220     static int staticFinalField() {
221         return Y1;
222     }
223     final String V = "X";
224     //@Reflect
225     String instanceField() {
226         // V is constant variable, but we can't get its value from the model
227         return V;
228     }
229     static final Integer Y2 = 3;
230     @Reflect
231     static Integer fcFieldOfWrongType() {
232         // field type not primitive nor String
233         return Y2;
234     }
235     enum E {A}
236     static final E e = E.A;
237     @Reflect
238     static E fcFieldOfWrongType2() {
239         return e;
240     }
241     static int Y4 = 3;
242     @Reflect
243     static int fcFieldNotDeclaredFinal() {
244         return Y4;
245     }
246     static final int Y3 = Math.max(1, 2);
247     //@Reflect
248     static int fcFieldInitializedWithNonConstantExpr() {
249         // according to JLS, access to Y3 is not a constant expression
250         // because Y3 is not a constant variable, because it's initilaized with a non-constant expression,
251         // but we currently have a limitation in the API
252         return Y3;
253     }
254     static final String S;
255     static {
256         S = "A";
257     }
258     //@Reflect
259     static String fcFieldBlankFinal() {
260         // according to JLS, access to S is not a constant expression
261         // because S is not a constant variable, because it lacks an initializer,
262         // but we currently have a limitation in the API
263         return S;
264     }
265 
266     @ParameterizedTest
267     @MethodSource("cases")
268     void test(Method m) throws NoSuchMethodException {
269         CoreOp.FuncOp f = Op.ofMethod(m).get();
270         Op op = ((Op.Result) f.body().entryBlock().terminatingOp().operands().getFirst()).op();
271         MethodHandles.Lookup l = MethodHandles.lookup();
272         Optional<Object> v = JavaOp.JavaExpression.evaluate(l, (Op & JavaOp.JavaExpression) op);
273         if (m.getName().startsWith("fc")) {
274             Assertions.assertTrue(v.isEmpty());
275         } else {
276             Assertions.assertTrue(v.isPresent());
277             Object[] args = new Object[0];
278             if ((m.getModifiers() & Modifier.STATIC) == 0) { // instance method
279                 args = new Object[] {this};
280             }
281             // TODO use BytecodeGenerator instead of Interpreter
282             Object expected = Interpreter.invoke(l, f.transform(CodeTransformer.LOWERING_TRANSFORMER), args);
283             Assertions.assertEquals(expected, v.get());
284         }
285     }
286 
287     static Stream<Method> cases() {
288         return Arrays.stream(TestEvaluation.class.getDeclaredMethods())
289                 .filter(m -> m.isAnnotationPresent(Reflect.class));
290     }
291 
292     @Reflect
293     static int fc2() {
294         int x = 1;
295         x++;
296         return x;
297     }
298     @Reflect
299     static boolean fc3() {
300         return "abc" instanceof String;
301     }
302 
303     static int z = 0;
304     @Reflect
305     static int fc4() {
306         return z;
307     }
308     @Reflect
309     static Object fc6() {
310         return (Object) 1;
311     }
312     @Reflect
313     static Object fc7() {
314         final int x = Math.abs(-1);
315         return x;
316     }
317     @Reflect
318     static boolean fc8() {
319         return 1d > Math.pow(2, 2);
320     }
321     @Reflect
322     static String fc9() {
323         return null;
324     }
325 
326     static CoreOp.FuncOp conversionModel(TypeElement source, TypeElement target) {
327         return CoreOp.func("conv", CoreType.functionType(target)).body(b -> {
328             var v = b.op(CoreOp.constant(source, valueOne(source)));
329             var r = b.op(JavaOp.conv(target, v));
330             b.op(CoreOp.return_(r));
331         });
332     }
333 
334     static Object valueOne(TypeElement t) {
335         if (t.equals(BOOLEAN)) {
336             return true;
337         } if (t.equals(BYTE)) {
338             return (byte) 1;
339         } else if (t.equals(SHORT)) {
340             return (short) 1;
341         } else if (t.equals(CHAR)) {
342             return (char) 1;
343         } else if (t.equals(INT)) {
344             return 1;
345         } else if (t.equals(LONG)) {
346             return 1L;
347         } else if (t.equals(FLOAT)) {
348             return 1f;
349         } else if (t.equals(DOUBLE)) {
350             return 1d;
351         }
352         throw new IllegalArgumentException("Unkown value one for type " + t);
353     }
354 
355     @Test
356     void testConversion() {
357         var pt = new PrimitiveType[] {BOOLEAN, BYTE, SHORT, CHAR, INT, LONG, FLOAT, DOUBLE};
358         for (PrimitiveType from : pt) {
359             for (PrimitiveType to : pt) {
360                 CoreOp.FuncOp f = conversionModel(from, to);
361                 Op op = ((Op.Result) f.body().entryBlock().terminatingOp().operands().getFirst()).op();
362                 MethodHandles.Lookup l = MethodHandles.lookup();
363                 Optional<Object> v = JavaOp.JavaExpression.evaluate(l, (JavaOp.ConvOp) op);
364                 if ((to.equals(BOOLEAN) && !from.equals(BOOLEAN)) || (from.equals(BOOLEAN) && !to.equals(BOOLEAN))) {
365                     Assertions.assertTrue(v.isEmpty());
366                 } else {
367                     Assertions.assertTrue(v.isPresent());
368                     Object expected = Interpreter.invoke(l, f);
369                     Assertions.assertEquals(expected, v.get());
370                 }
371             }
372         }
373     }
374 
375     @Test
376     void testInvalidConversion() {
377         CoreOp.FuncOp funcOp = CoreOp.func("ic", CoreType.FUNCTION_TYPE_VOID).body(b -> {
378             // String -> int
379             b.op(JavaOp.conv(INT, b.op(CoreOp.constant(J_L_STRING, "A"))));
380             // int -> String
381             b.op(JavaOp.conv(J_L_STRING, b.op(CoreOp.constant(INT, 1))));
382             b.op(CoreOp.return_());
383         });
384 
385         List<Op> convOps = funcOp.body().entryBlock().ops().stream().filter(op -> op instanceof JavaOp.ConvOp).toList();
386         for (Op convOp : convOps) {
387             Optional<Object> opt = JavaOp.JavaExpression.evaluate(MethodHandles.lookup(), (JavaOp.ConvOp) convOp);
388             Assertions.assertTrue(opt.isEmpty());
389         }
390     }
391 
392     @Test
393     void testInvalidConstants() {
394         CoreOp.FuncOp funcOp = CoreOp.func("ic", CoreType.FUNCTION_TYPE_VOID).body(b -> {
395             // valid constant op result type but invalid values
396             b.op(CoreOp.constant(J_L_STRING, null));
397             b.op(CoreOp.constant(INT, new Object()));
398             // invalid constant op result type but valid values
399             b.op(CoreOp.constant(J_L_OBJECT, 1));
400             b.op(CoreOp.constant(J_L_BOOLEAN, true));
401             b.op(CoreOp.return_());
402         });
403 
404         List<Op> constantOps = funcOp.body().entryBlock().ops().stream().filter(op -> op instanceof CoreOp.ConstantOp).toList();
405         for (Op cop : constantOps) {
406             Optional<Object> opt = JavaOp.JavaExpression.evaluate(MethodHandles.lookup(), (CoreOp.ConstantOp) cop);
407             Assertions.assertTrue(opt.isEmpty());
408         }
409     }
410 
411     @Test
412     void testCasts() {
413         // invalid
414         CoreOp.FuncOp iv = CoreOp.func("ic", CoreType.FUNCTION_TYPE_VOID).body(b -> {
415             // we can have cast to String but the value we cast has a non-type String
416             // this will not be allowed by the Java language
417             // e.g. String s = (String) 1;
418             b.op(JavaOp.cast(J_L_STRING, b.op(CoreOp.constant(INT, 1))));
419             b.op(CoreOp.return_());
420         });
421         List<JavaOp.CastOp> castOps = iv.body().entryBlock().ops().stream().filter(op -> op instanceof JavaOp.CastOp)
422                 .map(op -> (JavaOp.CastOp) op).toList();
423         for (JavaOp.CastOp castOp : castOps) {
424             Optional<Object> opt = JavaOp.JavaExpression.evaluate(MethodHandles.lookup(), castOp);
425             Assertions.assertTrue(opt.isEmpty());
426         }
427 
428         // valid
429         CoreOp.FuncOp v = CoreOp.func("vc", CoreType.FUNCTION_TYPE_VOID).body(b -> {
430             // cast of str literal
431             b.op(JavaOp.cast(J_L_STRING, b.op(CoreOp.constant(J_L_STRING, "1"))));
432             // cast of value of type String
433             b.op(JavaOp.cast(J_L_STRING,
434                     b.op(JavaOp.concat(
435                             b.op(CoreOp.constant(INT, 1)), b.op(CoreOp.constant(J_L_STRING, "2"))))));
436             b.op(CoreOp.return_());
437         });
438         List<JavaOp.CastOp> castOps2 = v.body().entryBlock().ops().stream().filter(op -> op instanceof JavaOp.CastOp)
439                 .map(op -> (JavaOp.CastOp) op).toList();
440         for (JavaOp.CastOp castOp : castOps2) {
441             Optional<Object> opt = JavaOp.JavaExpression.evaluate(MethodHandles.lookup(), castOp);
442             Assertions.assertTrue(opt.isPresent());
443         }
444     }
445 }