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