1/* 2 * Copyright (c) 2013, 2015, 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 24import com.sun.source.tree.ClassTree; 25import com.sun.source.tree.CompilationUnitTree; 26import com.sun.source.tree.MethodTree; 27import com.sun.source.tree.NewClassTree; 28import com.sun.source.tree.Tree; 29import com.sun.source.tree.VariableTree; 30import com.sun.source.util.JavacTask; 31import com.sun.source.util.TreeScanner; 32import com.sun.source.util.Trees; 33import com.sun.tools.javac.api.JavacTool; 34import com.sun.tools.javac.code.Flags; 35import com.sun.tools.javac.file.JavacFileManager; 36import com.sun.tools.javac.tree.JCTree.JCMethodDecl; 37import java.io.File; 38import java.io.IOException; 39import java.net.URI; 40import java.net.URISyntaxException; 41import java.util.ArrayList; 42import java.util.Arrays; 43import java.util.Iterator; 44import java.util.List; 45import javax.tools.Diagnostic; 46import javax.tools.DiagnosticListener; 47import javax.tools.FileObject; 48import javax.tools.ForwardingJavaFileManager; 49import javax.tools.JavaFileManager; 50import javax.tools.JavaFileObject; 51import javax.tools.SimpleJavaFileObject; 52 53/**Takes a source file, parses it once to get the warnings inside the file and 54 * then for each and every declaration in the file, it tries to place 55 * the @SuppressWarnings annotation on the declaration and verifies than no 56 * warnings are produced inside the declaration, but all are produced outside it. 57 * 58 * Currently only works with <code>unchecked,deprecation,cast,divzero</code> warnings. 59 */ 60public class VerifySuppressWarnings { 61 62 private static final List<String> STANDARD_PARAMS = 63 Arrays.asList("-Xlint:unchecked,deprecation,cast,divzero"); 64 65 public static void main(String... args) throws IOException, URISyntaxException { 66 if (args.length != 1) throw new IllegalStateException("Must provide class name!"); 67 String testContent = null; 68 List<File> sourcePath = new ArrayList<>(); 69 for (String sourcePaths : System.getProperty("test.src.path").split(File.pathSeparator)) { 70 sourcePath.add(new File(sourcePaths)); 71 } 72 JavacFileManager fm = JavacTool.create().getStandardFileManager(null, null, null); 73 for (File sp : sourcePath) { 74 File inp = new File(sp, args[0]); 75 76 if (inp.canRead()) { 77 testContent = fm.getJavaFileObject(inp.toPath()).getCharContent(true).toString(); 78 } 79 } 80 if (testContent == null) throw new IllegalStateException(); 81 final List<Diagnostic<?>> diagnostics = new ArrayList<>(); 82 DiagnosticListener<JavaFileObject> collectDiagnostics = new DiagnosticListener<JavaFileObject>() { 83 @Override public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 84 diagnostics.add(diagnostic); 85 } 86 }; 87 JavaFileObject testFile = new TestFO(new URI("mem://" + args[0]), testContent); 88 JavacTask task = JavacTool.create().getTask(null, 89 new TestFM(fm), 90 collectDiagnostics, 91 STANDARD_PARAMS, 92 null, 93 Arrays.asList(testFile)); 94 final Trees trees = Trees.instance(task); 95 final CompilationUnitTree cut = task.parse().iterator().next(); 96 task.analyze(); 97 98 final List<int[]> declarationSpans = new ArrayList<>(); 99 100 new TreeScanner<Void, Void>() { 101 @Override public Void visitClass(ClassTree node, Void p) { 102 handleDeclaration(node); 103 return super.visitClass(node, p); 104 } 105 @Override public Void visitMethod(MethodTree node, Void p) { 106 handleDeclaration(node); 107 return super.visitMethod(node, p); 108 } 109 @Override public Void visitVariable(VariableTree node, Void p) { 110 handleDeclaration(node); 111 return super.visitVariable(node, p); 112 } 113 114 @Override 115 public Void visitNewClass(NewClassTree node, Void p) { 116 if (node.getClassBody() != null) { 117 scan(node.getClassBody().getMembers(), null); 118 } 119 return null; 120 } 121 122 private void handleDeclaration(Tree node) { 123 int endPos = (int) trees.getSourcePositions().getEndPosition(cut, node); 124 125 if (endPos == (-1)) { 126 if (node.getKind() == Tree.Kind.METHOD && (((JCMethodDecl) node).getModifiers().flags & Flags.GENERATEDCONSTR) != 0) { 127 return ; 128 } 129 throw new IllegalStateException(); 130 } 131 132 declarationSpans.add(new int[] {(int) trees.getSourcePositions().getStartPosition(cut, node), endPos}); 133 } 134 }.scan(cut, null); 135 136 for (final int[] declarationSpan : declarationSpans) { 137 final String suppressWarnings = 138 "@SuppressWarnings({\"deprecation\", \"unchecked\", \"serial\", \"divzero\"})"; 139 final String updatedContent = testContent.substring(0, declarationSpan[0]) + suppressWarnings + testContent.substring(declarationSpan[0]); 140 final List<Diagnostic<?>> foundErrors = new ArrayList<>(diagnostics); 141 DiagnosticListener<JavaFileObject> verifyDiagnostics = new DiagnosticListener<JavaFileObject>() { 142 @Override public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 143 long adjustedPos = diagnostic.getPosition(); 144 145 if (adjustedPos >= declarationSpan[0]) adjustedPos -= suppressWarnings.length(); 146 147 if (declarationSpan[0] <= adjustedPos && adjustedPos <= declarationSpan[1]) { 148 throw new IllegalStateException("unsuppressed: " + diagnostic.getMessage(null)); 149 } 150 151 boolean found = false; 152 153 for (Iterator<Diagnostic<?>> it = foundErrors.iterator(); it.hasNext();) { 154 Diagnostic<?> d = it.next(); 155 if (d.getPosition() == adjustedPos && d.getCode().equals(diagnostic.getCode())) { 156 it.remove(); 157 found = true; 158 break; 159 } 160 } 161 162 if (!found) { 163 throw new IllegalStateException("diagnostic not originally reported: " + diagnostic.getMessage(null)); 164 } 165 } 166 }; 167 168 JavaFileObject updatedFile = new TestFO(new URI("mem://" + args[0]), updatedContent); 169 JavacTask testTask = JavacTool.create().getTask(null, 170 new TestFM(fm), 171 verifyDiagnostics, 172 STANDARD_PARAMS, 173 null, 174 Arrays.asList(updatedFile)); 175 176 testTask.analyze(); 177 178 for (Diagnostic<?> d : foundErrors) { 179 if (d.getPosition() < declarationSpan[0] || declarationSpan[1] < d.getPosition()) { 180 throw new IllegalStateException("missing: " + d.getMessage(null)); 181 } 182 } 183 } 184 } 185 186 private static final class TestFO extends SimpleJavaFileObject { 187 private final String content; 188 public TestFO(URI uri, String content) { 189 super(uri, Kind.SOURCE); 190 this.content = content; 191 } 192 193 @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { 194 return content; 195 } 196 197 @Override public boolean isNameCompatible(String simpleName, Kind kind) { 198 return true; 199 } 200 } 201 202 private static final class TestFM extends ForwardingJavaFileManager<JavaFileManager> { 203 204 public TestFM(JavaFileManager fileManager) { 205 super(fileManager); 206 } 207 208 @Override 209 public boolean isSameFile(FileObject a, FileObject b) { 210 return a.equals(b); 211 } 212 213 } 214} 215