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 }