Analyzer.java revision 4202:2bd34895dda2
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 * Analyze an AST node if needed. 340 */ 341 void analyzeIfNeeded(JCTree tree, Env<AttrContext> env) { 342 if (!analyzerModes.isEmpty() && 343 !env.info.isSpeculative && 344 TreeInfo.isStatement(tree)) { 345 JCStatement stmt = (JCStatement)tree; 346 analyze(stmt, env); 347 } 348 } 349 350 /** 351 * Analyze an AST node; this involves collecting a list of all the nodes that needs rewriting, 352 * and speculatively type-check the rewritten code to compare results against previously attributed code. 353 */ 354 void analyze(JCStatement statement, Env<AttrContext> env) { 355 AnalysisContext context = new AnalysisContext(); 356 StatementScanner statementScanner = new StatementScanner(context); 357 statementScanner.scan(statement); 358 359 if (!context.treesToAnalyzer.isEmpty()) { 360 361 //add a block to hoist potential dangling variable declarations 362 JCBlock fakeBlock = make.Block(SYNTHETIC, List.of(statement)); 363 364 TreeMapper treeMapper = new TreeMapper(context); 365 //TODO: to further refine the analysis, try all rewriting combinations 366 deferredAttr.attribSpeculative(fakeBlock, env, attr.statInfo, treeMapper, 367 t -> new AnalyzeDeferredDiagHandler(context), 368 argumentAttr.withLocalCacheContext()); 369 context.treeMap.entrySet().forEach(e -> { 370 context.treesToAnalyzer.get(e.getKey()) 371 .process(e.getKey(), e.getValue(), context.errors.nonEmpty()); 372 }); 373 } 374 } 375 376 /** 377 * Simple deferred diagnostic handler which filters out all messages and keep track of errors. 378 */ 379 class AnalyzeDeferredDiagHandler extends Log.DeferredDiagnosticHandler { 380 AnalysisContext context; 381 382 public AnalyzeDeferredDiagHandler(AnalysisContext context) { 383 super(log, d -> { 384 if (d.getType() == DiagnosticType.ERROR) { 385 context.errors.add(d); 386 } 387 return true; 388 }); 389 this.context = context; 390 } 391 } 392 393 /** 394 * This class is used to pass around contextual information bewteen analyzer classes, such as 395 * trees to be rewritten, errors occurred during the speculative attribution step, etc. 396 */ 397 class AnalysisContext { 398 /** Map from trees to analyzers. */ 399 Map<JCTree, StatementAnalyzer<JCTree, JCTree>> treesToAnalyzer = new HashMap<>(); 400 401 /** Map from original AST nodes to rewritten AST nodes */ 402 Map<JCTree, JCTree> treeMap = new HashMap<>(); 403 404 /** Errors in rewritten tree */ 405 ListBuffer<JCDiagnostic> errors = new ListBuffer<>(); 406 } 407 408 /** 409 * Subclass of {@link com.sun.tools.javac.tree.TreeScanner} which visit AST-nodes w/o crossing 410 * statement boundaries. 411 */ 412 class StatementScanner extends TreeScanner { 413 414 /** context */ 415 AnalysisContext context; 416 417 StatementScanner(AnalysisContext context) { 418 this.context = context; 419 } 420 421 @Override 422 @SuppressWarnings("unchecked") 423 public void scan(JCTree tree) { 424 if (tree != null) { 425 for (StatementAnalyzer<JCTree, JCTree> analyzer : analyzers) { 426 if (analyzer.isEnabled() && 427 tree.hasTag(analyzer.tag) && 428 analyzer.match(tree)) { 429 context.treesToAnalyzer.put(tree, analyzer); 430 break; //TODO: cover cases where multiple matching analyzers are found 431 } 432 } 433 } 434 super.scan(tree); 435 } 436 437 @Override 438 public void visitClassDef(JCClassDecl tree) { 439 //do nothing (prevents seeing same stuff twice 440 } 441 442 @Override 443 public void visitMethodDef(JCMethodDecl tree) { 444 //do nothing (prevents seeing same stuff twice 445 } 446 447 @Override 448 public void visitBlock(JCBlock tree) { 449 //do nothing (prevents seeing same stuff twice 450 } 451 452 @Override 453 public void visitSwitch(JCSwitch tree) { 454 scan(tree.getExpression()); 455 } 456 457 @Override 458 public void visitForLoop(JCForLoop tree) { 459 scan(tree.getInitializer()); 460 scan(tree.getCondition()); 461 scan(tree.getUpdate()); 462 } 463 464 @Override 465 public void visitForeachLoop(JCEnhancedForLoop tree) { 466 scan(tree.getExpression()); 467 } 468 469 @Override 470 public void visitWhileLoop(JCWhileLoop tree) { 471 scan(tree.getCondition()); 472 } 473 474 @Override 475 public void visitDoLoop(JCDoWhileLoop tree) { 476 scan(tree.getCondition()); 477 } 478 479 @Override 480 public void visitIf(JCIf tree) { 481 scan(tree.getCondition()); 482 } 483 } 484 485 /** 486 * Subclass of TreeCopier that maps nodes matched by analyzers onto new AST nodes. 487 */ 488 class TreeMapper extends TreeCopier<Void> { 489 490 AnalysisContext context; 491 492 TreeMapper(AnalysisContext context) { 493 super(make); 494 this.context = context; 495 } 496 497 @Override 498 @SuppressWarnings("unchecked") 499 public <Z extends JCTree> Z copy(Z tree, Void _unused) { 500 Z newTree = super.copy(tree, _unused); 501 StatementAnalyzer<JCTree, JCTree> analyzer = context.treesToAnalyzer.get(tree); 502 if (analyzer != null) { 503 newTree = (Z)analyzer.map(tree, newTree); 504 context.treeMap.put(tree, newTree); 505 } 506 return newTree; 507 } 508 509 @Override @DefinedBy(Api.COMPILER_TREE) 510 public JCTree visitLambdaExpression(LambdaExpressionTree node, Void _unused) { 511 JCLambda oldLambda = (JCLambda)node; 512 JCLambda newLambda = (JCLambda)super.visitLambdaExpression(node, _unused); 513 if (oldLambda.paramKind == ParameterKind.IMPLICIT) { 514 //reset implicit lambda parameters (whose type might have been set during attr) 515 newLambda.paramKind = ParameterKind.IMPLICIT; 516 newLambda.params.forEach(p -> p.vartype = null); 517 } 518 return newLambda; 519 } 520 } 521} 522