Analyzer.java revision 2868:816bd88d33a8
1/* 2 * Copyright (c) 2014, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package com.sun.tools.javac.comp; 27 28import com.sun.source.tree.LambdaExpressionTree; 29import com.sun.tools.javac.code.Source; 30import com.sun.tools.javac.code.Type; 31import com.sun.tools.javac.code.Types; 32import com.sun.tools.javac.tree.JCTree; 33import com.sun.tools.javac.tree.JCTree.JCBlock; 34import com.sun.tools.javac.tree.JCTree.JCClassDecl; 35import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop; 36import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; 37import com.sun.tools.javac.tree.JCTree.JCForLoop; 38import com.sun.tools.javac.tree.JCTree.JCIf; 39import com.sun.tools.javac.tree.JCTree.JCLambda; 40import com.sun.tools.javac.tree.JCTree.JCLambda.ParameterKind; 41import com.sun.tools.javac.tree.JCTree.JCMethodDecl; 42import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; 43import com.sun.tools.javac.tree.JCTree.JCNewClass; 44import com.sun.tools.javac.tree.JCTree.JCStatement; 45import com.sun.tools.javac.tree.JCTree.JCSwitch; 46import com.sun.tools.javac.tree.JCTree.JCTypeApply; 47import com.sun.tools.javac.tree.JCTree.JCVariableDecl; 48import com.sun.tools.javac.tree.JCTree.JCWhileLoop; 49import com.sun.tools.javac.tree.JCTree.Tag; 50import com.sun.tools.javac.tree.TreeCopier; 51import com.sun.tools.javac.tree.TreeInfo; 52import com.sun.tools.javac.tree.TreeMaker; 53import com.sun.tools.javac.tree.TreeScanner; 54import com.sun.tools.javac.util.Context; 55import com.sun.tools.javac.util.DefinedBy; 56import com.sun.tools.javac.util.DefinedBy.Api; 57import com.sun.tools.javac.util.Filter; 58import com.sun.tools.javac.util.JCDiagnostic; 59import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType; 60import com.sun.tools.javac.util.List; 61import com.sun.tools.javac.util.ListBuffer; 62import com.sun.tools.javac.util.Log; 63import com.sun.tools.javac.util.Names; 64import com.sun.tools.javac.util.Options; 65 66import java.util.EnumSet; 67import java.util.HashMap; 68import java.util.Map; 69import java.util.function.Predicate; 70 71import static com.sun.tools.javac.code.Flags.GENERATEDCONSTR; 72import static com.sun.tools.javac.code.Flags.SYNTHETIC; 73import static com.sun.tools.javac.code.TypeTag.CLASS; 74import static com.sun.tools.javac.tree.JCTree.Tag.APPLY; 75import static com.sun.tools.javac.tree.JCTree.Tag.CLASSDEF; 76import static com.sun.tools.javac.tree.JCTree.Tag.METHODDEF; 77import static com.sun.tools.javac.tree.JCTree.Tag.NEWCLASS; 78import static com.sun.tools.javac.tree.JCTree.Tag.TYPEAPPLY; 79 80/** 81 * Helper class for defining custom code analysis, such as finding instance creation expression 82 * that can benefit from diamond syntax. 83 */ 84public class Analyzer { 85 protected static final Context.Key<Analyzer> analyzerKey = new Context.Key<>(); 86 87 final Types types; 88 final Log log; 89 final Attr attr; 90 final DeferredAttr deferredAttr; 91 final TreeMaker make; 92 final Names names; 93 private final boolean allowDiamondWithAnonymousClassCreation; 94 95 final EnumSet<AnalyzerMode> analyzerModes; 96 97 public static Analyzer instance(Context context) { 98 Analyzer instance = context.get(analyzerKey); 99 if (instance == null) 100 instance = new Analyzer(context); 101 return instance; 102 } 103 104 protected Analyzer(Context context) { 105 context.put(analyzerKey, this); 106 types = Types.instance(context); 107 log = Log.instance(context); 108 attr = Attr.instance(context); 109 deferredAttr = DeferredAttr.instance(context); 110 make = TreeMaker.instance(context); 111 names = Names.instance(context); 112 Options options = Options.instance(context); 113 String findOpt = options.get("find"); 114 //parse modes 115 Source source = Source.instance(context); 116 allowDiamondWithAnonymousClassCreation = source.allowDiamondWithAnonymousClassCreation(); 117 analyzerModes = AnalyzerMode.getAnalyzerModes(findOpt, source); 118 } 119 120 /** 121 * This enum defines supported analyzer modes, as well as defining the logic for decoding 122 * the {@code -XDfind} option. 123 */ 124 enum AnalyzerMode { 125 DIAMOND("diamond", Source::allowDiamond), 126 LAMBDA("lambda", Source::allowLambda), 127 METHOD("method", Source::allowGraphInference); 128 129 final String opt; 130 final Predicate<Source> sourceFilter; 131 132 AnalyzerMode(String opt, Predicate<Source> sourceFilter) { 133 this.opt = opt; 134 this.sourceFilter = sourceFilter; 135 } 136 137 /** 138 * This method is used to parse the {@code find} option. 139 * Possible modes are separated by colon; a mode can be excluded by 140 * prepending '-' to its name. Finally, the special mode 'all' can be used to 141 * add all modes to the resulting enum. 142 */ 143 static EnumSet<AnalyzerMode> getAnalyzerModes(String opt, Source source) { 144 if (opt == null) { 145 return EnumSet.noneOf(AnalyzerMode.class); 146 } 147 List<String> modes = List.from(opt.split(",")); 148 EnumSet<AnalyzerMode> res = EnumSet.noneOf(AnalyzerMode.class); 149 if (modes.contains("all")) { 150 res = EnumSet.allOf(AnalyzerMode.class); 151 } 152 for (AnalyzerMode mode : values()) { 153 if (modes.contains(mode.opt)) { 154 res.add(mode); 155 } else if (modes.contains("-" + mode.opt) || !mode.sourceFilter.test(source)) { 156 res.remove(mode); 157 } 158 } 159 return res; 160 } 161 } 162 163 /** 164 * A statement analyzer is a work-unit that matches certain AST nodes (of given type {@code S}), 165 * rewrites them to different AST nodes (of type {@code T}) and then generates some meaningful 166 * messages in case the analysis has been successful. 167 */ 168 abstract class StatementAnalyzer<S extends JCTree, T extends JCTree> { 169 170 AnalyzerMode mode; 171 JCTree.Tag tag; 172 173 StatementAnalyzer(AnalyzerMode mode, Tag tag) { 174 this.mode = mode; 175 this.tag = tag; 176 } 177 178 /** 179 * Is this analyzer allowed to run? 180 */ 181 boolean isEnabled() { 182 return analyzerModes.contains(mode); 183 } 184 185 /** 186 * Should this analyzer be rewriting the given tree? 187 */ 188 abstract boolean match(S tree); 189 190 /** 191 * Rewrite a given AST node into a new one 192 */ 193 abstract T map(S oldTree, S newTree); 194 195 /** 196 * Entry-point for comparing results and generating diagnostics. 197 */ 198 abstract void process(S oldTree, T newTree, boolean hasErrors); 199 200 } 201 202 /** 203 * This analyzer checks if generic instance creation expression can use diamond syntax. 204 */ 205 class DiamondInitializer extends StatementAnalyzer<JCNewClass, JCNewClass> { 206 207 DiamondInitializer() { 208 super(AnalyzerMode.DIAMOND, NEWCLASS); 209 } 210 211 @Override 212 boolean match(JCNewClass tree) { 213 return tree.clazz.hasTag(TYPEAPPLY) && 214 !TreeInfo.isDiamond(tree) && 215 (tree.def == null || allowDiamondWithAnonymousClassCreation); 216 } 217 218 @Override 219 JCNewClass map(JCNewClass oldTree, JCNewClass newTree) { 220 if (newTree.clazz.hasTag(TYPEAPPLY)) { 221 ((JCTypeApply)newTree.clazz).arguments = List.nil(); 222 } 223 return newTree; 224 } 225 226 @Override 227 void process(JCNewClass oldTree, JCNewClass newTree, boolean hasErrors) { 228 if (!hasErrors) { 229 List<Type> inferredArgs = newTree.type.getTypeArguments(); 230 List<Type> explicitArgs = oldTree.type.getTypeArguments(); 231 for (Type t : inferredArgs) { 232 if (!types.isSameType(t, explicitArgs.head)) { 233 log.warning(oldTree.clazz, "diamond.redundant.args.1", 234 oldTree.clazz.type, newTree.clazz.type); 235 return; 236 } 237 explicitArgs = explicitArgs.tail; 238 } 239 //exact match 240 log.warning(oldTree.clazz, "diamond.redundant.args"); 241 } 242 } 243 } 244 245 /** 246 * This analyzer checks if anonymous instance creation expression can replaced by lambda. 247 */ 248 class LambdaAnalyzer extends StatementAnalyzer<JCNewClass, JCLambda> { 249 250 LambdaAnalyzer() { 251 super(AnalyzerMode.LAMBDA, NEWCLASS); 252 } 253 254 @Override 255 boolean match (JCNewClass tree){ 256 Type clazztype = tree.clazz.type; 257 return tree.def != null && 258 clazztype.hasTag(CLASS) && 259 types.isFunctionalInterface(clazztype.tsym) && 260 decls(tree.def).length() == 1; 261 } 262 //where 263 private List<JCTree> decls(JCClassDecl decl) { 264 ListBuffer<JCTree> decls = new ListBuffer<>(); 265 for (JCTree t : decl.defs) { 266 if (t.hasTag(METHODDEF)) { 267 JCMethodDecl md = (JCMethodDecl)t; 268 if ((md.getModifiers().flags & GENERATEDCONSTR) == 0) { 269 decls.add(md); 270 } 271 } else { 272 decls.add(t); 273 } 274 } 275 return decls.toList(); 276 } 277 278 @Override 279 JCLambda map (JCNewClass oldTree, JCNewClass newTree){ 280 JCMethodDecl md = (JCMethodDecl)decls(newTree.def).head; 281 List<JCVariableDecl> params = md.params; 282 JCBlock body = md.body; 283 return make.Lambda(params, body); 284 } 285 @Override 286 void process (JCNewClass oldTree, JCLambda newTree, boolean hasErrors){ 287 if (!hasErrors) { 288 log.warning(oldTree.def, "potential.lambda.found"); 289 } 290 } 291 } 292 293 /** 294 * This analyzer checks if generic method call has redundant type arguments. 295 */ 296 class RedundantTypeArgAnalyzer extends StatementAnalyzer<JCMethodInvocation, JCMethodInvocation> { 297 298 RedundantTypeArgAnalyzer() { 299 super(AnalyzerMode.METHOD, APPLY); 300 } 301 302 @Override 303 boolean match (JCMethodInvocation tree){ 304 return tree.typeargs != null && 305 tree.typeargs.nonEmpty(); 306 } 307 @Override 308 JCMethodInvocation map (JCMethodInvocation oldTree, JCMethodInvocation newTree){ 309 newTree.typeargs = List.nil(); 310 return newTree; 311 } 312 @Override 313 void process (JCMethodInvocation oldTree, JCMethodInvocation newTree, boolean hasErrors){ 314 if (!hasErrors) { 315 //exact match 316 log.warning(oldTree, "method.redundant.typeargs"); 317 } 318 } 319 } 320 321 @SuppressWarnings({"unchecked", "rawtypes"}) 322 StatementAnalyzer<JCTree, JCTree>[] analyzers = new StatementAnalyzer[] { 323 new DiamondInitializer(), 324 new LambdaAnalyzer(), 325 new RedundantTypeArgAnalyzer() 326 }; 327 328 /** 329 * Analyze an AST node if needed. 330 */ 331 void analyzeIfNeeded(JCTree tree, Env<AttrContext> env) { 332 if (!analyzerModes.isEmpty() && 333 !env.info.isSpeculative && 334 TreeInfo.isStatement(tree)) { 335 JCStatement stmt = (JCStatement)tree; 336 analyze(stmt, env); 337 } 338 } 339 340 /** 341 * Analyze an AST node; this involves collecting a list of all the nodes that needs rewriting, 342 * and speculatively type-check the rewritten code to compare results against previously attributed code. 343 */ 344 void analyze(JCStatement statement, Env<AttrContext> env) { 345 AnalysisContext context = new AnalysisContext(); 346 StatementScanner statementScanner = new StatementScanner(context); 347 statementScanner.scan(statement); 348 349 if (!context.treesToAnalyzer.isEmpty()) { 350 351 //add a block to hoist potential dangling variable declarations 352 JCBlock fakeBlock = make.Block(SYNTHETIC, List.of(statement)); 353 354 TreeMapper treeMapper = new TreeMapper(context); 355 //TODO: to further refine the analysis, try all rewriting combinations 356 deferredAttr.attribSpeculative(fakeBlock, env, attr.statInfo, treeMapper, 357 t -> new AnalyzeDeferredDiagHandler(context)); 358 359 context.treeMap.entrySet().forEach(e -> { 360 context.treesToAnalyzer.get(e.getKey()) 361 .process(e.getKey(), e.getValue(), context.errors.nonEmpty()); 362 }); 363 } 364 } 365 366 /** 367 * Simple deferred diagnostic handler which filters out all messages and keep track of errors. 368 */ 369 class AnalyzeDeferredDiagHandler extends Log.DeferredDiagnosticHandler { 370 AnalysisContext context; 371 372 public AnalyzeDeferredDiagHandler(AnalysisContext context) { 373 super(log, d -> { 374 if (d.getType() == DiagnosticType.ERROR) { 375 context.errors.add(d); 376 } 377 return true; 378 }); 379 this.context = context; 380 } 381 } 382 383 /** 384 * This class is used to pass around contextual information bewteen analyzer classes, such as 385 * trees to be rewritten, errors occurred during the speculative attribution step, etc. 386 */ 387 class AnalysisContext { 388 /** Map from trees to analyzers. */ 389 Map<JCTree, StatementAnalyzer<JCTree, JCTree>> treesToAnalyzer = new HashMap<>(); 390 391 /** Map from original AST nodes to rewritten AST nodes */ 392 Map<JCTree, JCTree> treeMap = new HashMap<>(); 393 394 /** Errors in rewritten tree */ 395 ListBuffer<JCDiagnostic> errors = new ListBuffer<>(); 396 } 397 398 /** 399 * Subclass of {@link com.sun.tools.javac.tree.TreeScanner} which visit AST-nodes w/o crossing 400 * statement boundaries. 401 */ 402 class StatementScanner extends TreeScanner { 403 404 /** context */ 405 AnalysisContext context; 406 407 StatementScanner(AnalysisContext context) { 408 this.context = context; 409 } 410 411 @Override 412 @SuppressWarnings("unchecked") 413 public void scan(JCTree tree) { 414 if (tree != null) { 415 for (StatementAnalyzer<JCTree, JCTree> analyzer : analyzers) { 416 if (analyzer.isEnabled() && 417 tree.hasTag(analyzer.tag) && 418 analyzer.match(tree)) { 419 context.treesToAnalyzer.put(tree, analyzer); 420 break; //TODO: cover cases where multiple matching analyzers are found 421 } 422 } 423 } 424 super.scan(tree); 425 } 426 427 @Override 428 public void visitClassDef(JCClassDecl tree) { 429 //do nothing (prevents seeing same stuff twice 430 } 431 432 @Override 433 public void visitMethodDef(JCMethodDecl tree) { 434 //do nothing (prevents seeing same stuff twice 435 } 436 437 @Override 438 public void visitBlock(JCBlock tree) { 439 //do nothing (prevents seeing same stuff twice 440 } 441 442 @Override 443 public void visitSwitch(JCSwitch tree) { 444 scan(tree.getExpression()); 445 } 446 447 @Override 448 public void visitForLoop(JCForLoop tree) { 449 scan(tree.getInitializer()); 450 scan(tree.getCondition()); 451 scan(tree.getUpdate()); 452 } 453 454 @Override 455 public void visitForeachLoop(JCEnhancedForLoop tree) { 456 scan(tree.getExpression()); 457 } 458 459 @Override 460 public void visitWhileLoop(JCWhileLoop tree) { 461 scan(tree.getCondition()); 462 } 463 464 @Override 465 public void visitDoLoop(JCDoWhileLoop tree) { 466 scan(tree.getCondition()); 467 } 468 469 @Override 470 public void visitIf(JCIf tree) { 471 scan(tree.getCondition()); 472 } 473 } 474 475 /** 476 * Subclass of TreeCopier that maps nodes matched by analyzers onto new AST nodes. 477 */ 478 class TreeMapper extends TreeCopier<Void> { 479 480 AnalysisContext context; 481 482 TreeMapper(AnalysisContext context) { 483 super(make); 484 this.context = context; 485 } 486 487 @Override 488 @SuppressWarnings("unchecked") 489 public <Z extends JCTree> Z copy(Z tree, Void _unused) { 490 Z newTree = super.copy(tree, _unused); 491 StatementAnalyzer<JCTree, JCTree> analyzer = context.treesToAnalyzer.get(tree); 492 if (analyzer != null) { 493 newTree = (Z)analyzer.map(tree, newTree); 494 context.treeMap.put(tree, newTree); 495 } 496 return newTree; 497 } 498 499 @Override @DefinedBy(Api.COMPILER_TREE) 500 public JCTree visitLambdaExpression(LambdaExpressionTree node, Void _unused) { 501 JCLambda oldLambda = (JCLambda)node; 502 JCLambda newLambda = (JCLambda)super.visitLambdaExpression(node, _unused); 503 if (oldLambda.paramKind == ParameterKind.IMPLICIT) { 504 //reset implicit lambda parameters (whose type might have been set during attr) 505 newLambda.paramKind = ParameterKind.IMPLICIT; 506 newLambda.params.forEach(p -> p.vartype = null); 507 } 508 return newLambda; 509 } 510 } 511} 512