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 }