Analyzer.java revision 2758:3c1b5fcf6fad
11556Srgrimes/* 21556Srgrimes * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. 31556Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 41556Srgrimes * 51556Srgrimes * This code is free software; you can redistribute it and/or modify it 61556Srgrimes * under the terms of the GNU General Public License version 2 only, as 71556Srgrimes * published by the Free Software Foundation. Oracle designates this 81556Srgrimes * particular file as subject to the "Classpath" exception as provided 91556Srgrimes * by Oracle in the LICENSE file that accompanied this code. 101556Srgrimes * 111556Srgrimes * This code is distributed in the hope that it will be useful, but WITHOUT 121556Srgrimes * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 131556Srgrimes * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 141556Srgrimes * version 2 for more details (a copy is included in the LICENSE file that 151556Srgrimes * accompanied this code). 161556Srgrimes * 171556Srgrimes * You should have received a copy of the GNU General Public License version 181556Srgrimes * 2 along with this work; if not, write to the Free Software Foundation, 191556Srgrimes * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 201556Srgrimes * 211556Srgrimes * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 221556Srgrimes * or visit www.oracle.com if you need additional information or have any 231556Srgrimes * questions. 241556Srgrimes */ 251556Srgrimes 261556Srgrimespackage com.sun.tools.javac.comp; 271556Srgrimes 281556Srgrimesimport com.sun.tools.javac.code.Source; 291556Srgrimesimport com.sun.tools.javac.code.Type; 301556Srgrimesimport com.sun.tools.javac.code.Types; 311556Srgrimesimport com.sun.tools.javac.tree.JCTree; 321556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCBlock; 331556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCClassDecl; 341556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCDoWhileLoop; 3520413Ssteveimport com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; 361556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCForLoop; 371556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCIf; 381556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCLambda; 391556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCMethodDecl; 401556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCMethodInvocation; 4135773Scharnierimport com.sun.tools.javac.tree.JCTree.JCNewClass; 4236006Scharnierimport com.sun.tools.javac.tree.JCTree.JCStatement; 4335773Scharnierimport com.sun.tools.javac.tree.JCTree.JCSwitch; 441556Srgrimesimport com.sun.tools.javac.tree.JCTree.JCTypeApply; 4599109Sobrienimport com.sun.tools.javac.tree.JCTree.JCVariableDecl; 4699109Sobrienimport com.sun.tools.javac.tree.JCTree.JCWhileLoop; 471556Srgrimesimport com.sun.tools.javac.tree.JCTree.Tag; 4836006Scharnierimport com.sun.tools.javac.tree.TreeCopier; 491556Srgrimesimport com.sun.tools.javac.tree.TreeInfo; 501556Srgrimesimport com.sun.tools.javac.tree.TreeMaker; 511556Srgrimesimport com.sun.tools.javac.tree.TreeScanner; 521556Srgrimesimport com.sun.tools.javac.util.Context; 5391079Smarkmimport com.sun.tools.javac.util.Filter; 5491079Smarkmimport com.sun.tools.javac.util.JCDiagnostic; 551556Srgrimesimport com.sun.tools.javac.util.JCDiagnostic.DiagnosticType; 561556Srgrimesimport com.sun.tools.javac.util.List; 5739925Saleximport com.sun.tools.javac.util.ListBuffer; 581556Srgrimesimport com.sun.tools.javac.util.Log; 591556Srgrimesimport com.sun.tools.javac.util.Names; 601556Srgrimesimport com.sun.tools.javac.util.Options; 611556Srgrimes 6227874Sbrianimport java.util.EnumSet; 631556Srgrimesimport java.util.HashMap; 6455225Ssheldonhimport java.util.Map; 6555225Ssheldonhimport java.util.function.Predicate; 6655225Ssheldonh 6755225Ssheldonhimport static com.sun.tools.javac.code.Flags.GENERATEDCONSTR; 68105396Smarkmimport static com.sun.tools.javac.code.Flags.SYNTHETIC; 6947129Sjmgimport static com.sun.tools.javac.code.TypeTag.CLASS; 701556Srgrimesimport static com.sun.tools.javac.tree.JCTree.Tag.APPLY; 7190108Simpimport static com.sun.tools.javac.tree.JCTree.Tag.CLASSDEF; 7290108Simpimport static com.sun.tools.javac.tree.JCTree.Tag.METHODDEF; 7390108Simpimport static com.sun.tools.javac.tree.JCTree.Tag.NEWCLASS; 741556Srgrimesimport static com.sun.tools.javac.tree.JCTree.Tag.TYPEAPPLY; 751556Srgrimes 7690108Simp/** 771556Srgrimes * Helper class for defining custom code analysis, such as finding instance creation expression 781556Srgrimes * that can benefit from diamond syntax. 7930073Sdanny */ 8047129Sjmgpublic class Analyzer { 8191079Smarkm protected static final Context.Key<Analyzer> analyzerKey = new Context.Key<>(); 8291079Smarkm 8328037Sbrian final Types types; 8485615Sdillon final Log log; 855233Sbde final Attr attr; 8627874Sbrian final DeferredAttr deferredAttr; 8727874Sbrian final TreeMaker make; 8827874Sbrian final Names names; 891556Srgrimes 9027874Sbrian final EnumSet<AnalyzerMode> analyzerModes; 9128037Sbrian 9211738Sache public static Analyzer instance(Context context) { 931556Srgrimes Analyzer instance = context.get(analyzerKey); 9430073Sdanny if (instance == null) 9547129Sjmg instance = new Analyzer(context); 965233Sbde return instance; 9747129Sjmg } 981556Srgrimes 991556Srgrimes protected Analyzer(Context context) { 1005233Sbde context.put(analyzerKey, this); 1015233Sbde types = Types.instance(context); 1025233Sbde log = Log.instance(context); 1035233Sbde attr = Attr.instance(context); 1041556Srgrimes deferredAttr = DeferredAttr.instance(context); 10528037Sbrian make = TreeMaker.instance(context); 10628037Sbrian names = Names.instance(context); 10728037Sbrian Options options = Options.instance(context); 10847129Sjmg String findOpt = options.get("find"); 10947129Sjmg //parse modes 11047129Sjmg Source source = Source.instance(context); 1111556Srgrimes analyzerModes = AnalyzerMode.getAnalyzerModes(findOpt, source); 1121556Srgrimes } 1131556Srgrimes 1141556Srgrimes /** 1151556Srgrimes * This enum defines supported analyzer modes, as well as defining the logic for decoding 11685615Sdillon * the {@code -XDfind} option. 11785615Sdillon */ 11863761Sjwd enum AnalyzerMode { 1191556Srgrimes DIAMOND("diamond", Source::allowDiamond), 12060718Sdbaker LAMBDA("lambda", Source::allowLambda), 1211556Srgrimes METHOD("method", Source::allowGraphInference); 1225233Sbde 1235233Sbde final String opt; 1245233Sbde final Predicate<Source> sourceFilter; 1255233Sbde 1265233Sbde AnalyzerMode(String opt, Predicate<Source> sourceFilter) { 12760718Sdbaker this.opt = opt; 12860718Sdbaker this.sourceFilter = sourceFilter; 12928037Sbrian } 13028025Sbrian 13128025Sbrian /** 13227874Sbrian * This method is used to parse the {@code find} option. 1331556Srgrimes * Possible modes are separated by colon; a mode can be excluded by 1341556Srgrimes * prepending '-' to its name. Finally, the special mode 'all' can be used to 1351556Srgrimes * add all modes to the resulting enum. 1361556Srgrimes */ 1371556Srgrimes static EnumSet<AnalyzerMode> getAnalyzerModes(String opt, Source source) { 1381556Srgrimes if (opt == null) { 1391556Srgrimes return EnumSet.noneOf(AnalyzerMode.class); 1401556Srgrimes } 14124976Sdanny List<String> modes = List.from(opt.split(",")); 1421556Srgrimes EnumSet<AnalyzerMode> res = EnumSet.noneOf(AnalyzerMode.class); 1435233Sbde if (modes.contains("all")) { 1445233Sbde res = EnumSet.allOf(AnalyzerMode.class); 1451556Srgrimes } 1461556Srgrimes for (AnalyzerMode mode : values()) { 1471556Srgrimes if (modes.contains(mode.opt)) { 1481556Srgrimes res.add(mode); 1499944Sache } else if (modes.contains("-" + mode.opt) || !mode.sourceFilter.test(source)) { 1501556Srgrimes res.remove(mode); 1511556Srgrimes } 1521556Srgrimes } 1531556Srgrimes return res; 1541556Srgrimes } 1551556Srgrimes } 1561556Srgrimes 1571556Srgrimes /** 15847129Sjmg * A statement analyzer is a work-unit that matches certain AST nodes (of given type {@code S}), 1591556Srgrimes * rewrites them to different AST nodes (of type {@code T}) and then generates some meaningful 16028037Sbrian * messages in case the analysis has been successful. 16128037Sbrian */ 1621556Srgrimes abstract class StatementAnalyzer<S extends JCTree, T extends JCTree> { 1631556Srgrimes 1641556Srgrimes AnalyzerMode mode; 1651556Srgrimes JCTree.Tag tag; 16627874Sbrian 16727874Sbrian StatementAnalyzer(AnalyzerMode mode, Tag tag) { 16827874Sbrian this.mode = mode; 16928025Sbrian this.tag = tag; 17028025Sbrian } 17127874Sbrian 17227874Sbrian /** 17327874Sbrian * Is this analyzer allowed to run? 17427874Sbrian */ 17527874Sbrian boolean isEnabled() { 17630073Sdanny return analyzerModes.contains(mode); 1771556Srgrimes } 1781556Srgrimes 1791556Srgrimes /** 18055225Ssheldonh * Should this analyzer be rewriting the given tree? 18155225Ssheldonh */ 182105396Smarkm abstract boolean match(S tree); 18390108Simp 1841556Srgrimes /** 18590108Simp * Rewrite a given AST node into a new one 1861556Srgrimes */ 18728037Sbrian abstract T map(S oldTree, S newTree); 18855225Ssheldonh 1891556Srgrimes /** 19028037Sbrian * Entry-point for comparing results and generating diagnostics. 19128037Sbrian */ 19228037Sbrian abstract void process(S oldTree, T newTree, boolean hasErrors); 19328037Sbrian 19428037Sbrian } 19528037Sbrian 19648214Scracauer /** 19728037Sbrian * This analyzer checks if generic instance creation expression can use diamond syntax. 19832756Sjb */ 19928037Sbrian class DiamondInitializer extends StatementAnalyzer<JCNewClass, JCNewClass> { 20032756Sjb 20128037Sbrian DiamondInitializer() { 20228037Sbrian super(AnalyzerMode.DIAMOND, NEWCLASS); 20328037Sbrian } 20428037Sbrian 20528037Sbrian @Override 20628037Sbrian boolean match(JCNewClass tree) { 20728037Sbrian return tree.clazz.hasTag(TYPEAPPLY) && 20828037Sbrian !TreeInfo.isDiamond(tree) && 20928037Sbrian tree.def == null; 2101556Srgrimes } 2111556Srgrimes 21228037Sbrian @Override 2131556Srgrimes JCNewClass map(JCNewClass oldTree, JCNewClass newTree) { 21428037Sbrian if (newTree.clazz.hasTag(TYPEAPPLY)) { 21528037Sbrian ((JCTypeApply)newTree.clazz).arguments = List.nil(); 21628037Sbrian } 21728037Sbrian return newTree; 21828037Sbrian } 21928037Sbrian 22028037Sbrian @Override 22128037Sbrian void process(JCNewClass oldTree, JCNewClass newTree, boolean hasErrors) { 22228037Sbrian if (!hasErrors) { 2231556Srgrimes List<Type> inferredArgs = newTree.type.getTypeArguments(); 22455225Ssheldonh List<Type> explicitArgs = oldTree.type.getTypeArguments(); 22530013Sjoerg for (Type t : inferredArgs) { 22630013Sjoerg if (!types.isSameType(t, explicitArgs.head)) { 22753082Ssheldonh log.warning(oldTree.clazz, "diamond.redundant.args.1", 22855225Ssheldonh oldTree.clazz.type, newTree.clazz.type); 22955225Ssheldonh return; 23055225Ssheldonh } 23128037Sbrian explicitArgs = explicitArgs.tail; 23255225Ssheldonh } 23355225Ssheldonh //exact match 23455225Ssheldonh log.warning(oldTree.clazz, "diamond.redundant.args"); 23555225Ssheldonh } 23655225Ssheldonh } 23755225Ssheldonh } 23855225Ssheldonh 23955225Ssheldonh /** 24055225Ssheldonh * This analyzer checks if anonymous instance creation expression can replaced by lambda. 24155225Ssheldonh */ 24228037Sbrian class LambdaAnalyzer extends StatementAnalyzer<JCNewClass, JCLambda> { 24328037Sbrian 24428037Sbrian LambdaAnalyzer() { 24528037Sbrian super(AnalyzerMode.LAMBDA, NEWCLASS); 24628037Sbrian } 24728037Sbrian 24828037Sbrian @Override 24928037Sbrian boolean match (JCNewClass tree){ 25028037Sbrian Type clazztype = tree.clazz.type; 25128037Sbrian return tree.def != null && 25228037Sbrian clazztype.hasTag(CLASS) && 25328037Sbrian types.isFunctionalInterface(clazztype.tsym) && 25428037Sbrian decls(tree.def).length() == 1; 25528037Sbrian } 25628037Sbrian //where 25728037Sbrian private List<JCTree> decls(JCClassDecl decl) { 25828037Sbrian ListBuffer<JCTree> decls = new ListBuffer<>(); 25928037Sbrian for (JCTree t : decl.defs) { 26028037Sbrian if (t.hasTag(METHODDEF)) { 26128037Sbrian JCMethodDecl md = (JCMethodDecl)t; 26228037Sbrian if ((md.getModifiers().flags & GENERATEDCONSTR) == 0) { 26328037Sbrian decls.add(md); 2641556Srgrimes } 26528037Sbrian } else { 2661556Srgrimes decls.add(t); 2671556Srgrimes } 26876749Sru } 26976749Sru return decls.toList(); 27076749Sru } 2711556Srgrimes 2721556Srgrimes @Override 27315068Sache JCLambda map (JCNewClass oldTree, JCNewClass newTree){ 2741556Srgrimes JCMethodDecl md = (JCMethodDecl)decls(newTree.def).head; 27547129Sjmg List<JCVariableDecl> params = md.params; 27647129Sjmg JCBlock body = md.body; 27747129Sjmg return make.Lambda(params, body); 27847129Sjmg } 27947129Sjmg @Override 28047129Sjmg void process (JCNewClass oldTree, JCLambda newTree, boolean hasErrors){ 28147129Sjmg if (!hasErrors) { 28247129Sjmg log.warning(oldTree.def, "potential.lambda.found"); 28347129Sjmg } 28447129Sjmg } 28547129Sjmg } 28647129Sjmg 28747129Sjmg /** 28847129Sjmg * This analyzer checks if generic method call has redundant type arguments. 2891556Srgrimes */ 2901556Srgrimes class RedundantTypeArgAnalyzer extends StatementAnalyzer<JCMethodInvocation, JCMethodInvocation> { 2911556Srgrimes 2921556Srgrimes RedundantTypeArgAnalyzer() { 29390108Simp super(AnalyzerMode.METHOD, APPLY); 2941556Srgrimes } 2951556Srgrimes 2961556Srgrimes @Override 2971556Srgrimes boolean match (JCMethodInvocation tree){ 2981556Srgrimes return tree.typeargs != null && 2991556Srgrimes tree.typeargs.nonEmpty(); 30090108Simp } 3011556Srgrimes @Override 30226465Scharnier JCMethodInvocation map (JCMethodInvocation oldTree, JCMethodInvocation newTree){ 30377934Sdd newTree.typeargs = List.nil(); 30444598Sbrian return newTree; 30553082Ssheldonh } 30653082Ssheldonh @Override 3071556Srgrimes void process (JCMethodInvocation oldTree, JCMethodInvocation newTree, boolean hasErrors){ 3081556Srgrimes if (!hasErrors) { 309 //exact match 310 log.warning(oldTree, "method.redundant.typeargs"); 311 } 312 } 313 } 314 315 @SuppressWarnings({"unchecked", "rawtypes"}) 316 StatementAnalyzer<JCTree, JCTree>[] analyzers = new StatementAnalyzer[] { 317 new DiamondInitializer(), 318 new LambdaAnalyzer(), 319 new RedundantTypeArgAnalyzer() 320 }; 321 322 /** 323 * Analyze an AST node if needed. 324 */ 325 void analyzeIfNeeded(JCTree tree, Env<AttrContext> env) { 326 if (!analyzerModes.isEmpty() && 327 !env.info.isSpeculative && 328 TreeInfo.isStatement(tree)) { 329 JCStatement stmt = (JCStatement)tree; 330 analyze(stmt, env); 331 } 332 } 333 334 /** 335 * Analyze an AST node; this involves collecting a list of all the nodes that needs rewriting, 336 * and speculatively type-check the rewritten code to compare results against previously attributed code. 337 */ 338 void analyze(JCStatement statement, Env<AttrContext> env) { 339 AnalysisContext context = new AnalysisContext(); 340 StatementScanner statementScanner = new StatementScanner(context); 341 statementScanner.scan(statement); 342 343 if (!context.treesToAnalyzer.isEmpty()) { 344 345 //add a block to hoist potential dangling variable declarations 346 JCBlock fakeBlock = make.Block(SYNTHETIC, List.of(statement)); 347 348 TreeMapper treeMapper = new TreeMapper(context); 349 //TODO: to further refine the analysis, try all rewriting combinations 350 deferredAttr.attribSpeculative(fakeBlock, env, attr.statInfo, treeMapper, 351 t -> new AnalyzeDeferredDiagHandler(context)); 352 353 context.treeMap.entrySet().forEach(e -> { 354 context.treesToAnalyzer.get(e.getKey()) 355 .process(e.getKey(), e.getValue(), context.errors.nonEmpty()); 356 }); 357 } 358 } 359 360 /** 361 * Simple deferred diagnostic handler which filters out all messages and keep track of errors. 362 */ 363 class AnalyzeDeferredDiagHandler extends Log.DeferredDiagnosticHandler { 364 AnalysisContext context; 365 366 public AnalyzeDeferredDiagHandler(AnalysisContext context) { 367 super(log, d -> { 368 if (d.getType() == DiagnosticType.ERROR) { 369 context.errors.add(d); 370 } 371 return true; 372 }); 373 this.context = context; 374 } 375 } 376 377 /** 378 * This class is used to pass around contextual information bewteen analyzer classes, such as 379 * trees to be rewritten, errors occurred during the speculative attribution step, etc. 380 */ 381 class AnalysisContext { 382 /** Map from trees to analyzers. */ 383 Map<JCTree, StatementAnalyzer<JCTree, JCTree>> treesToAnalyzer = new HashMap<>(); 384 385 /** Map from original AST nodes to rewritten AST nodes */ 386 Map<JCTree, JCTree> treeMap = new HashMap<>(); 387 388 /** Errors in rewritten tree */ 389 ListBuffer<JCDiagnostic> errors = new ListBuffer<>(); 390 } 391 392 /** 393 * Subclass of {@link com.sun.tools.javac.tree.TreeScanner} which visit AST-nodes w/o crossing 394 * statement boundaries. 395 */ 396 class StatementScanner extends TreeScanner { 397 398 /** context */ 399 AnalysisContext context; 400 401 StatementScanner(AnalysisContext context) { 402 this.context = context; 403 } 404 405 @Override 406 @SuppressWarnings("unchecked") 407 public void scan(JCTree tree) { 408 if (tree != null) { 409 for (StatementAnalyzer<JCTree, JCTree> analyzer : analyzers) { 410 if (analyzer.isEnabled() && 411 tree.hasTag(analyzer.tag) && 412 analyzer.match(tree)) { 413 context.treesToAnalyzer.put(tree, analyzer); 414 break; //TODO: cover cases where multiple matching analyzers are found 415 } 416 } 417 } 418 super.scan(tree); 419 } 420 421 @Override 422 public void visitClassDef(JCClassDecl tree) { 423 //do nothing (prevents seeing same stuff twice 424 } 425 426 @Override 427 public void visitMethodDef(JCMethodDecl tree) { 428 //do nothing (prevents seeing same stuff twice 429 } 430 431 @Override 432 public void visitBlock(JCBlock tree) { 433 //do nothing (prevents seeing same stuff twice 434 } 435 436 @Override 437 public void visitSwitch(JCSwitch tree) { 438 scan(tree.getExpression()); 439 } 440 441 @Override 442 public void visitForLoop(JCForLoop tree) { 443 scan(tree.getInitializer()); 444 scan(tree.getCondition()); 445 scan(tree.getUpdate()); 446 } 447 448 @Override 449 public void visitForeachLoop(JCEnhancedForLoop tree) { 450 scan(tree.getExpression()); 451 } 452 453 @Override 454 public void visitWhileLoop(JCWhileLoop tree) { 455 scan(tree.getCondition()); 456 } 457 458 @Override 459 public void visitDoLoop(JCDoWhileLoop tree) { 460 scan(tree.getCondition()); 461 } 462 463 @Override 464 public void visitIf(JCIf tree) { 465 scan(tree.getCondition()); 466 } 467 } 468 469 /** 470 * Subclass of TreeCopier that maps nodes matched by analyzers onto new AST nodes. 471 */ 472 class TreeMapper extends TreeCopier<Void> { 473 474 AnalysisContext context; 475 476 TreeMapper(AnalysisContext context) { 477 super(make); 478 this.context = context; 479 } 480 481 @Override 482 @SuppressWarnings("unchecked") 483 public <Z extends JCTree> Z copy(Z tree, Void _unused) { 484 Z newTree = super.copy(tree, _unused); 485 StatementAnalyzer<JCTree, JCTree> analyzer = context.treesToAnalyzer.get(tree); 486 if (analyzer != null) { 487 newTree = (Z)analyzer.map(tree, newTree); 488 context.treeMap.put(tree, newTree); 489 } 490 return newTree; 491 } 492 } 493} 494