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 /*
 25  * @test
 26  * @enablePreview
 27  * @library ../lib
 28  * @modules jdk.compiler/com.sun.tools.javac.api
 29  * @summary Smoke test for accessing IR from annotation processors
 30  * @run main TestIRFromAnnotation
 31  */
 32 
 33 import com.sun.source.util.JavacTask;
 34 
 35 import java.io.File;
 36 import java.io.IOException;
 37 import java.io.StringWriter;
 38 import java.lang.reflect.code.Op;
 39 import java.lang.reflect.code.op.CoreOp.FuncOp;
 40 import java.lang.reflect.code.op.ExtendedOp;
 41 import java.lang.reflect.code.parser.OpParser;
 42 import java.lang.reflect.code.writer.OpWriter;
 43 import java.nio.charset.Charset;
 44 import java.nio.file.FileVisitResult;
 45 import java.nio.file.Files;
 46 import java.nio.file.Path;
 47 import java.nio.file.SimpleFileVisitor;
 48 import java.nio.file.attribute.BasicFileAttributes;
 49 import java.util.ArrayList;
 50 import java.util.List;
 51 import java.util.Optional;
 52 import java.util.Set;
 53 import javax.annotation.processing.RoundEnvironment;
 54 import javax.lang.model.SourceVersion;
 55 import javax.lang.model.element.*;
 56 import javax.tools.JavaCompiler;
 57 import javax.tools.JavaFileManager;
 58 import javax.tools.JavaFileObject;
 59 import javax.tools.SimpleJavaFileObject;
 60 import javax.tools.ToolProvider;
 61 
 62 public class TestIRFromAnnotation {
 63 
 64     public static void main(String... args) throws Exception {
 65         String testSrc = System.getProperty("test.src");
 66         File baseDir = Path.of(testSrc).toFile();
 67         new TestIRFromAnnotation().run(baseDir);
 68     }
 69 
 70     void run(File baseDir) throws Exception {
 71         for (File file : getAllFiles(List.of(baseDir))) {
 72             if (!file.exists() || !file.getName().endsWith(".java")) {
 73                 continue;
 74             }
 75             analyze(file);
 76         }
 77     }
 78 
 79     void analyze(File source) {
 80         try {
 81             JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 82             JavaFileManager fileManager = compiler.getStandardFileManager(null, null, Charset.defaultCharset());
 83             JavacTask task = (JavacTask)compiler.getTask(null, fileManager, null,
 84                     List.of("-proc:only",
 85                             "--enable-preview",
 86                             "--source", Integer.toString(SourceVersion.latest().runtimeVersion().feature())),
 87                     null, List.of(new SourceFile(source)));
 88             task.setProcessors(List.of(new Processor()));
 89             task.analyze();
 90         } catch (Throwable ex) {
 91             throw new AssertionError("Unexpected exception when analyzing: " + source, ex);
 92         }
 93     }
 94 
 95     File[] getAllFiles(List<File> roots) throws IOException {
 96         long now = System.currentTimeMillis();
 97         ArrayList<File> buf = new ArrayList<>();
 98         for (File file : roots) {
 99             Files.walkFileTree(file.toPath(), new SimpleFileVisitor<Path>() {
100                 @Override
101                 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
102                     buf.add(file.toFile());
103                     return FileVisitResult.CONTINUE;
104                 }
105             });
106         }
107         long delta = System.currentTimeMillis() - now;
108         System.err.println("All files = " + buf.size() + " " + delta);
109         return buf.toArray(new File[buf.size()]);
110     }
111 
112     static class SourceFile extends SimpleJavaFileObject {
113 
114         private final File file;
115         protected SourceFile(File file) {
116             super(file.toURI(), Kind.SOURCE);
117             this.file = file;
118         }
119 
120         @Override
121         public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
122             return Files.readString(file.toPath());
123         }
124     }
125 
126     public static class Processor extends JavacTestingAbstractProcessor {
127 
128         public boolean process(Set<? extends TypeElement> annotations,
129                                RoundEnvironment roundEnvironment) {
130             class Scan extends ElementScanner<Void,Void> {
131                 @Override
132                 public Void visitExecutable(ExecutableElement e, Void p) {
133                     IR ir = e.getAnnotation(IR.class);
134                     if (ir == null) {
135                         return null; // skip
136                     }
137                     Optional<Object> body = elements.getBody(e);
138                     if (!body.isPresent()) {
139                         throw new AssertionError(String.format("No body found in method %s annotated with @IR",
140                                 toMethodString(e)));
141                     }
142                     String actualOp = canonicalizeModel((FuncOp)body.get());
143                     String expectedOp = canonicalizeModel(ir.value());
144                     if (!actualOp.equals(expectedOp)) {
145                         throw new AssertionError(String.format("Bad IR found in %s:\n%s\nExpected:\n%s",
146                                 toMethodString(e), actualOp, expectedOp));
147                     }
148                     return null;
149                 }
150             }
151             Scan scan = new Scan();
152             for (Element e : roundEnvironment.getRootElements()) {
153                 scan.scan(e);
154             }
155             return true;
156         }
157     }
158 
159     // serializes dropping location information, parses, and then serializes, dropping location information
160     static String canonicalizeModel(Op o) {
161         return canonicalizeModel(serialize(o));
162     }
163 
164     // parses, and then serializes, dropping location information
165     static String canonicalizeModel(String d) {
166         return serialize(OpParser.fromString(ExtendedOp.FACTORY, d).get(0));
167     }
168 
169     // serializes, dropping location information
170     static String serialize(Op o) {
171         StringWriter w = new StringWriter();
172         OpWriter.writeTo(w, o, OpWriter.LocationOption.DROP_LOCATION);
173         return w.toString();
174     }
175 
176     static String toMethodString(ExecutableElement e) {
177         return e.getEnclosingElement() + "." + e;
178     }
179 
180 }