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 }