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 }