Analyzer.java revision 2905:520635aae9e1
155714Skris/* 255714Skris * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved. 355714Skris * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 455714Skris * 555714Skris * This code is free software; you can redistribute it and/or modify it 655714Skris * under the terms of the GNU General Public License version 2 only, as 755714Skris * published by the Free Software Foundation. Oracle designates this 855714Skris * particular file as subject to the "Classpath" exception as provided 955714Skris * by Oracle in the LICENSE file that accompanied this code. 1055714Skris * 1155714Skris * This code is distributed in the hope that it will be useful, but WITHOUT 1255714Skris * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1355714Skris * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1455714Skris * version 2 for more details (a copy is included in the LICENSE file that 1555714Skris * accompanied this code). 1655714Skris * 1755714Skris * You should have received a copy of the GNU General Public License version 1855714Skris * 2 along with this work; if not, write to the Free Software Foundation, 1955714Skris * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2055714Skris * 2155714Skris * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2255714Skris * or visit www.oracle.com if you need additional information or have any 2355714Skris * questions. 2455714Skris */ 2555714Skris 2655714Skrispackage com.sun.tools.javac.comp; 2755714Skris 2855714Skrisimport com.sun.source.tree.LambdaExpressionTree; 2955714Skrisimport com.sun.tools.javac.code.Source; 3055714Skrisimport com.sun.tools.javac.code.Type; 3155714Skrisimport com.sun.tools.javac.code.Types; 3255714Skrisimport com.sun.tools.javac.tree.JCTree; 3355714Skrisimport com.sun.tools.javac.tree.JCTree.JCBlock; 3455714Skrisimport 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, explicitArgs; 230 if (oldTree.def != null) { 231 inferredArgs = newTree.def.implementing.nonEmpty() 232 ? newTree.def.implementing.get(0).type.getTypeArguments() 233 : newTree.def.extending.type.getTypeArguments(); 234 explicitArgs = oldTree.def.implementing.nonEmpty() 235 ? oldTree.def.implementing.get(0).type.getTypeArguments() 236 : oldTree.def.extending.type.getTypeArguments(); 237 } else { 238 inferredArgs = newTree.type.getTypeArguments(); 239 explicitArgs = oldTree.type.getTypeArguments(); 240 } 241 for (Type t : inferredArgs) { 242 if (!types.isSameType(t, explicitArgs.head)) { 243 log.warning(oldTree.clazz, "diamond.redundant.args.1", 244 oldTree.clazz.type, newTree.clazz.type); 245 return; 246 } 247 explicitArgs = explicitArgs.tail; 248 } 249 //exact match 250 log.warning(oldTree.clazz, "diamond.redundant.args"); 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, "potential.lambda.found"); 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, "method.redundant.typeargs"); 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 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