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 import java.io.StringWriter;
25 import java.lang.reflect.Constructor;
26 import java.lang.reflect.Field;
27 import java.lang.reflect.Member;
28 import java.lang.reflect.Method;
29 import jdk.incubator.code.*;
30 import jdk.incubator.code.dialect.core.CoreOp;
31 import jdk.incubator.code.dialect.java.JavaOp;
32 import jdk.incubator.code.extern.OpParser;
33 import jdk.incubator.code.extern.OpWriter;
34 import jdk.incubator.code.CodeReflection;
35
36 import static jdk.incubator.code.dialect.core.CoreOp.return_;
37 import static jdk.incubator.code.dialect.core.CoreOp.func;
38 import static jdk.incubator.code.dialect.core.CoreType.FUNCTION_TYPE_VOID;
39
40 public class CodeReflectionTester {
41
42 static int nErrors = 0;
43
44 public static void main(String[] args) throws ReflectiveOperationException {
45 if (args.length != 1) {
46 System.err.println("Usage: CodeReflectionTester <classname>");
47 System.exit(1);
48 }
49 Class<?> clazz = Class.forName(args[0]);
50 check(clazz);
51 }
52
53 public static void check(Class<?> clazz) throws ReflectiveOperationException {
54 for (Method m : clazz.getDeclaredMethods()) {
55 check(m);
56 }
57 for (Field f : clazz.getDeclaredFields()) {
58 check(f);
59 }
60 for (Class<?> c : clazz.getDeclaredClasses()) {
61 check(c);
62 }
63 if (nErrors > 0) {
64 throw new AssertionError("Test failed with " + nErrors + " errors");
65 }
66 }
67
68 static void error(String msg, Object... args) {
69 nErrors++;
70 System.err.println("error: " + String.format(msg, args));
71 }
72
73 static void checkModel(Member member, String found, IR ir) {
74 String expected = null;
75 try {
76 expected = canonicalizeModel(member, ir.value());
77 } catch (Throwable ex) {
78 error("Cannot parse IR annotation in %s %s.\nFound:\n%s", memberKind(member), member.getName(), found);
79 return;
80 }
81 if (!found.equals(expected)) {
82 error("Bad IR\nFound:\n%s\n\nExpected:\n%s", found, expected);
83 }
84 }
85
86 static String memberKind(Member member) {
87 return switch (member) {
88 case Field _ -> "field";
89 case Method _ -> "method";
90 case Constructor<?> _ -> "constructor";
91 default -> throw new UnsupportedOperationException("Cannot get here");
92 };
93 }
94
95 static void check(Method method) throws ReflectiveOperationException {
96 if (!method.isAnnotationPresent(CodeReflection.class)) return;
97 String found = canonicalizeModel(method, Op.ofMethod(method).orElseThrow());
98 IR ir = method.getAnnotation(IR.class);
99 if (ir == null) {
100 error("No @IR annotation found on reflective method");
101 return;
102 }
103 checkModel(method, found, ir);
104 }
105
106 static void check(Field field) throws ReflectiveOperationException {
107 IR ir = field.getAnnotation(IR.class);
108 if (ir == null) return;
109 if (field.getType().equals(Quoted.class)) {
110 // transitional
111 Quoted quoted = (Quoted) field.get(null);
112 String found = canonicalizeModel(field, getModelOfQuotedOp(quoted));
113 checkModel(field, found, ir);
114 } else if (Quotable.class.isAssignableFrom(field.getType())) {
115 Quotable quotable = (Quotable) field.get(null);
116 Quoted quoted = Op.ofQuotable(quotable).get();
117 String found = canonicalizeModel(field, getModelOfQuotedOp(quoted));
118 checkModel(field, found, ir);
119 } else {
120 error("Field annotated with @IR should be of a quotable type (Quoted/Quotable)");
121 return;
122 }
123 }
124
125 // serializes dropping location information, parses, and then serializes, dropping location information
126 static String canonicalizeModel(Member m, Op o) {
127 return canonicalizeModel(m, serialize(o));
128 }
129
130 // parses, and then serializes, dropping location information
131 static String canonicalizeModel(Member m, String d) {
132 Op o;
133 try {
134 o = OpParser.fromString(JavaOp.JAVA_DIALECT_FACTORY, d).get(0);
135 } catch (Exception e) {
136 throw new IllegalStateException(m.toString(), e);
137 }
138 return serialize(o);
139 }
140
141 // serializes, dropping location information
142 static String serialize(Op o) {
143 StringWriter w = new StringWriter();
144 OpWriter.writeTo(w, o, OpWriter.LocationOption.DROP_LOCATION);
145 return w.toString();
146 }
147
148 static Op getModelOfQuotedOp(Quoted quoted) {
149 return func("f", FUNCTION_TYPE_VOID).body(fblock -> {
150 CopyContext cc = fblock.context();
151 for (Value cv : quoted.capturedValues().keySet()) {
152 Block.Parameter p = fblock.parameter(cv.type());
153 cc.mapValue(cv, p);
154 }
155
156 Op qOp = quoted.op();
157 // Associate the quoted ops ancestor body's entry block
158 // with the function's entry block, thereby ensuring that
159 // captured values mapped to the function's parameters
160 // are reachable
161 cc.mapBlock(qOp.ancestorBody().entryBlock(), fblock);
162 fblock.op(qOp);
163
164 fblock.op(CoreOp.return_());
165 });
166 }
167 }