1 /*
2 * Copyright (c) 2025, 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 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.stream.Collectors;
31 import java.io.PrintWriter;
32 import java.io.IOException;
33
34 import java.io.IOException;
35 import java.nio.file.DirectoryStream;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.util.stream.Stream;
40
41 import java.nio.file.Path;
42 import java.nio.file.Paths;
43 import java.nio.charset.Charset;
44
45 import javax.tools.Diagnostic;
46 import javax.tools.DiagnosticListener;
47 import javax.tools.JavaFileObject;
48 import javax.tools.SimpleJavaFileObject;
49
50 import com.sun.tools.javac.code.Attribute;
51 import com.sun.tools.javac.comp.Attr;
52 import com.sun.tools.javac.comp.AttrContext;
53 import com.sun.tools.javac.comp.CompileStates;
54 import com.sun.tools.javac.comp.Env;
55 import com.sun.tools.javac.comp.Modules;
56 import com.sun.tools.javac.file.JavacFileManager;
57 import com.sun.tools.javac.file.PathFileObject;
58 import com.sun.tools.javac.main.JavaCompiler;
59 import com.sun.tools.javac.tree.JCTree;
60 import com.sun.tools.javac.tree.JCTree.*;
61 import com.sun.tools.javac.tree.TreeInfo;
62 import com.sun.tools.javac.util.Assert;
63 import com.sun.tools.javac.util.Context;
64 import com.sun.tools.javac.util.DiagnosticSource;
65 import com.sun.tools.javac.util.JCDiagnostic;
66 import com.sun.tools.javac.util.List;
67 import com.sun.tools.javac.util.ListBuffer;
68 import com.sun.tools.javac.util.Log;
69 import com.sun.tools.javac.util.Options;
70
71 public class InitializationWarningTester {
72 Context context;
73 Options options;
74 MyJavaCompiler javaCompiler;
75 JavacFileManager javacFileManager;
76 PrintWriter errOut;
77 DiagnosticListener<JavaFileObject> diagnosticListener;
78
79 public static void main(String... args) throws Throwable {
80 String testSrc = System.getProperty("test.src");
81 Path baseDir = Paths.get(testSrc);
82 InitializationWarningTester tester = new InitializationWarningTester();
83 Assert.check(args.length > 0, "no args, ending");
84 Assert.check(args.length <= 2, "unexpected number of arguments");
85 String className = args[0];
86 String warningsGoldenFileName = args.length > 1 ? args[1] : null;
87 tester.test(baseDir, className, warningsGoldenFileName);
88 }
89
90 java.util.List<String> compilationOutput = new ArrayList<>();
91
92 public InitializationWarningTester() {
93 context = new Context();
94 diagnosticListener = new DiagnosticListener<JavaFileObject>() {
95 public void report(Diagnostic<? extends JavaFileObject> message) {
96 JCDiagnostic diagnostic = (JCDiagnostic) message;
97 String msgData = ((PathFileObject)diagnostic.getDiagnosticSource().getFile()).getShortName() +
98 ":" + diagnostic.getLineNumber() + ":" + diagnostic.getColumnNumber() + ": " + diagnostic.getCode();
99 if (diagnostic.getArgs() != null && diagnostic.getArgs().length > 0) {
100 msgData += ": " + Arrays.stream(diagnostic.getArgs()).map(o -> o.toString())
101 .collect(Collectors.joining(", "));
102 }
103 compilationOutput.add(msgData);
104 }
105 };
106 context.put(DiagnosticListener.class, diagnosticListener);
107 JavacFileManager.preRegister(context);
108 MyAttr.preRegister(context, this);
109 options = Options.instance(context);
110 options.put("--enable-preview", "--enable-preview");
111 options.put("--source", Integer.toString(Runtime.version().feature()));
112 options.put("-Xlint:initialization", "-Xlint:initialization");
113 javaCompiler = new MyJavaCompiler(context);
114 javacFileManager = new JavacFileManager(context, false, Charset.defaultCharset());
115 }
116
117 void test(Path baseDir, String className, String warningsGoldenFileName) throws Throwable {
118 Path javaFile = baseDir.resolve(className + ".java");
119 Path goldenFile = warningsGoldenFileName != null ? baseDir.resolve(warningsGoldenFileName) : null;
120
121 // compile
122 javaCompiler.compile(com.sun.tools.javac.util.List.of(javacFileManager.getJavaFileObject(javaFile)));
123 if (goldenFile != null) {
124 java.util.List<String> goldenFileContent = Files.readAllLines(goldenFile);
125 if (goldenFileContent.size() != compilationOutput.size()) {
126 System.err.println("compilation output length mismatch");
127 System.err.println(" golden file content:");
128 for (String s : goldenFileContent) {
129 System.err.println(" " + s);
130 }
131 System.err.println(" warning compilation result:");
132 for (String s : compilationOutput) {
133 System.err.println(" " + s);
134 }
135 throw new AssertionError("compilation output length mismatch");
136 }
137 for (int i = 0; i < goldenFileContent.size(); i++) {
138 String goldenLine = goldenFileContent.get(i);
139 String warningLine = compilationOutput.get(i);
140 Assert.check(warningLine.equals(goldenLine), "error, found:\n" + warningLine + "\nexpected:\n" + goldenLine);
141 }
142 } else {
143 if (compilationOutput.size() != 0) {
144 System.err.println(" expecting empty compilation output, got:");
145 for (String s : compilationOutput) {
146 System.err.println(" " + s);
147 }
148 throw new AssertionError("expected empty compilation output");
149 }
150 }
151 }
152
153 static class MyJavaCompiler extends JavaCompiler {
154 MyJavaCompiler(Context context) {
155 super(context);
156 // do not generate code
157 this.shouldStopPolicyIfNoError = CompileStates.CompileState.LOWER;
158 }
159 }
160
161 static class MyAttr extends Attr {
162 InitializationWarningTester tester;
163 static void preRegister(Context context, InitializationWarningTester tester) {
164 context.put(attrKey, (com.sun.tools.javac.util.Context.Factory<Attr>) c -> new MyAttr(c, tester));
165 }
166
167 MyAttr(Context context, InitializationWarningTester tester) {
168 super(context);
169 this.tester = tester;
170 }
171
172 @Override
173 public void visitMethodDef(JCMethodDecl tree) {
174 if (TreeInfo.isConstructor(tree)) {
175 /* remove the super constructor call if it has no arguments, that way the Attr super class
176 * will add a super() as the first statement in the constructor and will analyze the rest
177 * of the code in warnings only mode
178 */
179 if (TreeInfo.hasAnyConstructorCall(tree)) {
180 ListBuffer<JCStatement> newStats = new ListBuffer<>();
181 for (JCStatement statement : tree.body.stats) {
182 if (statement instanceof JCExpressionStatement expressionStatement &&
183 expressionStatement.expr instanceof JCMethodInvocation methodInvocation) {
184 if (TreeInfo.isConstructorCall(methodInvocation) &&
185 methodInvocation.args.isEmpty()) {
186 continue;
187 }
188 }
189 newStats.add(statement);
190 }
191 tree.body.stats = newStats.toList();
192 }
193 }
194 super.visitMethodDef(tree);
195 }
196 }
197 }