1 /*
2 * Copyright (c) 2023, 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 import java.lang.invoke.MethodHandles;
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.net.URI;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.concurrent.Callable;
35 import javax.tools.Diagnostic;
36 import javax.tools.DiagnosticCollector;
37 import javax.tools.FileObject;
38 import javax.tools.ForwardingJavaFileManager;
39 import javax.tools.JavaCompiler;
40 import javax.tools.JavaFileManager;
41 import javax.tools.JavaFileObject;
42 import javax.tools.SimpleJavaFileObject;
43 import javax.tools.ToolProvider;
44
45 /**
46 * This program tries to compile a large number of classes that exercise a fair amount of
47 * features in javac.
48 */
49 public class JavacBenchApp {
50 static class ClassFile extends SimpleJavaFileObject {
51 private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
52 protected ClassFile(String name) {
53 super(URI.create("memo:///" + name.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
54 }
55 @Override
56 public ByteArrayOutputStream openOutputStream() {
57 return this.baos;
58 }
59 byte[] toByteArray() {
60 return baos.toByteArray();
61 }
62 }
63
64 static class FileManager extends ForwardingJavaFileManager<JavaFileManager> {
65 private Map<String, ClassFile> classesMap = new HashMap<String, ClassFile>();
66 protected FileManager(JavaFileManager fileManager) {
67 super(fileManager);
68 }
69 @Override
70 public ClassFile getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject source) {
71 ClassFile classFile = new ClassFile(name);
72 classesMap.put(name, classFile);
73 return classFile;
74 }
75 public Map<String, byte[]> getCompiledClasses() {
76 Map<String, byte[]> result = new HashMap<>();
77 for (Map.Entry<String, ClassFile> entry : classesMap.entrySet()) {
78 result.put(entry.getKey(), entry.getValue().toByteArray());
79 }
80 return result;
81 }
82 }
83
84 static class SourceFile extends SimpleJavaFileObject {
85 private CharSequence sourceCode;
86 public SourceFile(String name, CharSequence sourceCode) {
87 super(URI.create("memo:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
88 this.sourceCode = sourceCode;
89 }
90 @Override
91 public CharSequence getCharContent(boolean ignore) {
92 return this.sourceCode;
93 }
94 }
95
96 public Map<String, byte[]> compile() {
97 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
98 DiagnosticCollector<JavaFileObject> ds = new DiagnosticCollector<>();
99 Collection<SourceFile> sourceFiles = sources;
100
101 try (FileManager fileManager = new FileManager(compiler.getStandardFileManager(ds, null, null))) {
102 JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, sourceFiles);
103 if (task.call()) {
104 return fileManager.getCompiledClasses();
105 } else {
106 for (Diagnostic<? extends JavaFileObject> d : ds.getDiagnostics()) {
107 System.out.format("Line: %d, %s in %s", d.getLineNumber(), d.getMessage(null), d.getSource().getName());
108 }
109 throw new InternalError("compilation failure");
110 }
111 } catch (IOException e) {
112 throw new InternalError(e);
113 }
114 }
115
116 List<SourceFile> sources;
117
118 static final String imports = """
119 import java.lang.*;
120 import java.util.*;
121 """;
122
123 static final String testClassBody = """
124 // Some comments
125 static long x;
126 static final long y;
127 static {
128 y = System.currentTimeMillis();
129 }
130 /* More comments */
131 @Deprecated
132 String func() { return "String " + this + y; }
133 public static void main(String args[]) {
134 try {
135 x = Long.parseLong(args[0]);
136 } catch (Throwable t) {
137 t.printStackTrace();
138 }
139 doit(() -> {
140 System.out.println("Hello Lambda");
141 Thread.dumpStack();
142 });
143 }
144 static List<String> list = List.of("1", "2");
145 class InnerClass1 {
146 static final long yy = y;
147 }
148 static void doit(Runnable r) {
149 for (var x : list) {
150 r.run();
151 }
152 }
153 static String patternMatch(String arg, Object o) {
154 if (o instanceof String s) {
155 return "1234";
156 }
157 final String b = "B";
158 return switch (arg) {
159 case "A" -> "a";
160 case b -> "b";
161 default -> "c";
162 };
163 }
164 public sealed class SealedInnerClass {}
165 public final class Foo extends SealedInnerClass {}
166 enum Expression {
167 ADDITION,
168 SUBTRACTION,
169 MULTIPLICATION,
170 DIVISION
171 }
172 public record Point(int x, int y) {
173 public Point(int x) {
174 this(x, 0);
175 }
176 }
177 """;
178
179 String sanitySource = """
180 public class Sanity implements java.util.concurrent.Callable<String> {
181 public String call() {
182 return "this is a test";
183 }
184 }
185 """;
186
187 void setup(int count) {
188 sources = new ArrayList<>(count);
189 for (int i = 0; i < count; i++) {
190 String source = imports + "public class Test" + i + " {" + testClassBody + "}";
191 sources.add(new SourceFile("Test" + i, source));
192 }
193
194 sources.add(new SourceFile("Sanity", sanitySource));
195 }
196
197 @SuppressWarnings("unchecked")
198 static void validate(byte[] sanityClassFile) throws Throwable {
199 MethodHandles.Lookup lookup = MethodHandles.lookup();
200 Class<?> cls = lookup.defineClass(sanityClassFile);
201 Callable<String> obj = (Callable<String>)cls.getDeclaredConstructor().newInstance();
202 String s = obj.call();
203 if (!s.equals("this is a test")) {
204 throw new RuntimeException("Expected \"this is a test\", but got \"" + s + "\"");
205 }
206 }
207
208 public static void main(String args[]) throws Throwable {
209 long started = System.currentTimeMillis();
210 JavacBenchApp bench = new JavacBenchApp();
211
212 int count = 0;
213 if (args.length > 0) {
214 count = Integer.parseInt(args[0]);
215 if (count >= 0) {
216 bench.setup(count);
217 Map<String, byte[]> allClasses = bench.compile();
218 validate(allClasses.get("Sanity"));
219 }
220 }
221 if (System.getProperty("JavacBenchApp.silent") == null) {
222 // Set this property when running with "perf stat", etc
223 long elapsed = System.currentTimeMillis() - started;
224 System.out.println("Generated source code for " + bench.sources.size() + " classes and compiled them in " + elapsed + " ms");
225 }
226 }
227 }
228