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