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 }