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 }