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