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