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