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     );
 67 
 68     public static void main(String... args) throws Exception {
 69         String testSrc = System.getProperty("test.src");
 70         File baseDir = Path.of(testSrc).toFile();
 71         new TestIRFromAnnotation().run(baseDir);
 72     }
 73 
 74     void run(File baseDir) throws Exception {
 75         for (File file : getAllFiles(List.of(baseDir))) {
 76             if (!file.exists() || !file.getName().endsWith(".java") || isExcluded(file)) {
 77                 continue;
 78             }
 79             analyze(file);
 80         }
 81     }
 82 
 83     boolean isExcluded(File file) {
 84         return EXCLUDED_TEST.contains(file.getName());
 85     }
 86 
 87     void analyze(File source) {
 88         try {
 89             JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 90             JavaFileManager fileManager = compiler.getStandardFileManager(null, null, Charset.defaultCharset());
 91             JavacTask task = (JavacTask)compiler.getTask(null, fileManager, null,
 92                     List.of("-proc:only",
 93                             "--enable-preview",
 94                             "--add-modules=jdk.incubator.code",
 95                             "--source", Integer.toString(SourceVersion.latest().runtimeVersion().feature())),
 96                     null, List.of(new SourceFile(source)));
 97             task.setProcessors(List.of(new Processor()));
 98             task.analyze();
 99         } catch (Throwable ex) {
100             throw new AssertionError("Unexpected exception when analyzing: " + source, ex);
101         }
102     }
103 
104     File[] getAllFiles(List<File> roots) throws IOException {
105         long now = System.currentTimeMillis();
106         ArrayList<File> buf = new ArrayList<>();
107         for (File file : roots) {
108             Files.walkFileTree(file.toPath(), new SimpleFileVisitor<Path>() {
109                 @Override
110                 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
111                     buf.add(file.toFile());
112                     return FileVisitResult.CONTINUE;
113                 }
114             });
115         }
116         long delta = System.currentTimeMillis() - now;
117         System.err.println("All files = " + buf.size() + " " + delta);
118         return buf.toArray(new File[buf.size()]);
119     }
120 
121     static class SourceFile extends SimpleJavaFileObject {
122 
123         private final File file;
124         protected SourceFile(File file) {
125             super(file.toURI(), Kind.SOURCE);
126             this.file = file;
127         }
128 
129         @Override
130         public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
131             return Files.readString(file.toPath());
132         }
133     }
134 
135     public static class Processor extends JavacTestingAbstractProcessor {
136 
137         public boolean process(Set<? extends TypeElement> annotations,
138                                RoundEnvironment roundEnvironment) {
139             class Scan extends ElementScanner<Void,Void> {
140                 @Override
141                 public Void visitExecutable(ExecutableElement e, Void p) {
142                     IR ir = e.getAnnotation(IR.class);
143                     if (ir == null) {
144                         return null; // skip
145                     }
146                     Optional<FuncOp> body = Op.ofElement(processingEnv, e);
147                     if (!body.isPresent()) {
148                         throw new AssertionError(String.format("No body found in method %s annotated with @IR",
149                                 toMethodString(e)));
150                     }
151                     String actualOp = canonicalizeModel((FuncOp)body.get());
152                     String expectedOp = canonicalizeModel(ir.value());
153                     if (!actualOp.equals(expectedOp)) {
154                         throw new AssertionError(String.format("Bad IR found in %s:\n%s\nExpected:\n%s",
155                                 toMethodString(e), actualOp, expectedOp));
156                     }
157                     return null;
158                 }
159             }
160             Scan scan = new Scan();
161             for (Element e : roundEnvironment.getRootElements()) {
162                 scan.scan(e);
163             }
164             return true;
165         }
166     }
167 
168     // serializes dropping location information, parses, and then serializes, dropping location information
169     static String canonicalizeModel(Op o) {
170         return canonicalizeModel(serialize(o));
171     }
172 
173     // parses, and then serializes, dropping location information
174     static String canonicalizeModel(String d) {
175         return serialize(OpParser.fromText(JavaOp.JAVA_DIALECT_FACTORY, d).get(0));
176     }
177 
178     // serializes, dropping location information
179     static String serialize(Op o) {
180         StringWriter w = new StringWriter();
181         OpWriter.writeTo(w, o, OpWriter.LocationOption.DROP_LOCATION);
182         return w.toString();
183     }
184 
185     static String toMethodString(ExecutableElement e) {
186         return e.getEnclosingElement() + "." + e;
187     }
188 
189 }