package com.sun.tools.javac.comp;
import java.util.ArrayDeque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.function.Predicate;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.NewClassTree;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Kinds.Kind;
import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.code.Source.Feature;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.comp.ArgumentAttr.LocalCacheContext;
import com.sun.tools.javac.resources.CompilerProperties.Warnings;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop;
import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop;
import com.sun.tools.javac.tree.JCTree.JCForLoop;
import com.sun.tools.javac.tree.JCTree.JCIf;
import com.sun.tools.javac.tree.JCTree.JCLambda;
import com.sun.tools.javac.tree.JCTree.JCLambda.ParameterKind;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCSwitch;
import com.sun.tools.javac.tree.JCTree.JCTry;
import com.sun.tools.javac.tree.JCTree.JCTypeApply;
import com.sun.tools.javac.tree.JCTree.JCUnary;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.JCWhileLoop;
import com.sun.tools.javac.tree.JCTree.Tag;
import com.sun.tools.javac.tree.TreeCopier;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeScanner;
import com.sun.tools.javac.util.Assert;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.DefinedBy;
import com.sun.tools.javac.util.DefinedBy.Api;
import com.sun.tools.javac.util.DiagnosticSource;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Options;
import com.sun.tools.javac.util.Position;
import static com.sun.tools.javac.code.Flags.GENERATEDCONSTR;
import static com.sun.tools.javac.code.TypeTag.CLASS;
import static com.sun.tools.javac.tree.JCTree.Tag.APPLY;
import static com.sun.tools.javac.tree.JCTree.Tag.FOREACHLOOP;
import static com.sun.tools.javac.tree.JCTree.Tag.LABELLED;
import static com.sun.tools.javac.tree.JCTree.Tag.METHODDEF;
import static com.sun.tools.javac.tree.JCTree.Tag.NEWCLASS;
import static com.sun.tools.javac.tree.JCTree.Tag.NULLCHK;
import static com.sun.tools.javac.tree.JCTree.Tag.TYPEAPPLY;
import static com.sun.tools.javac.tree.JCTree.Tag.VARDEF;
public class Analyzer {
protected static final Context.Key<Analyzer> analyzerKey = new Context.Key<>();
final Types types;
final Log log;
final Attr attr;
final DeferredAttr deferredAttr;
final ArgumentAttr argumentAttr;
final TreeMaker make;
final AnalyzerCopier copier;
private final boolean allowDiamondWithAnonymousClassCreation;
final EnumSet<AnalyzerMode> analyzerModes;
public static Analyzer instance(Context context) {
Analyzer instance = context.get(analyzerKey);
if (instance == null)
instance = new Analyzer(context);
return instance;
}
protected Analyzer(Context context) {
context.put(analyzerKey, this);
types = Types.instance(context);
log = Log.instance(context);
attr = Attr.instance(context);
deferredAttr = DeferredAttr.instance(context);
argumentAttr = ArgumentAttr.instance(context);
make = TreeMaker.instance(context);
copier = new AnalyzerCopier();
Options options = Options.instance(context);
String findOpt = options.get("find");
Source source = Source.instance(context);
allowDiamondWithAnonymousClassCreation = Feature.DIAMOND_WITH_ANONYMOUS_CLASS_CREATION.allowedInSource(source);
analyzerModes = AnalyzerMode.getAnalyzerModes(findOpt, source);
}
enum AnalyzerMode {
DIAMOND("diamond", Feature.DIAMOND),
LAMBDA("lambda", Feature.LAMBDA),
METHOD("method", Feature.GRAPH_INFERENCE),
LOCAL("local", Feature.LOCAL_VARIABLE_TYPE_INFERENCE);
final String opt;
final Feature feature;
AnalyzerMode(String opt, Feature feature) {
this.opt = opt;
this.feature = feature;
}
static EnumSet<AnalyzerMode> getAnalyzerModes(String opt, Source source) {
if (opt == null) {
return EnumSet.noneOf(AnalyzerMode.class);
}
List<String> modes = List.from(opt.split(","));
EnumSet<AnalyzerMode> res = EnumSet.noneOf(AnalyzerMode.class);
if (modes.contains("all")) {
res = EnumSet.allOf(AnalyzerMode.class);
}
for (AnalyzerMode mode : values()) {
if (modes.contains("-" + mode.opt) || !mode.feature.allowedInSource(source)) {
res.remove(mode);
} else if (modes.contains(mode.opt)) {
res.add(mode);
}
}
return res;
}
}
abstract class StatementAnalyzer<S extends JCTree, T extends JCTree> {
AnalyzerMode mode;
JCTree.Tag tag;
StatementAnalyzer(AnalyzerMode mode, Tag tag) {
this.mode = mode;
this.tag = tag;
}
boolean isEnabled() {
return analyzerModes.contains(mode);
}
abstract boolean match(S tree);
abstract List<T> rewrite(S oldTree);
abstract void process(S oldTree, T newTree, boolean hasErrors);
}
class DiamondInitializer extends StatementAnalyzer<JCNewClass, JCNewClass> {
DiamondInitializer() {
super(AnalyzerMode.DIAMOND, NEWCLASS);
}
@Override
boolean match(JCNewClass tree) {
return tree.clazz.hasTag(TYPEAPPLY) &&
!TreeInfo.isDiamond(tree) &&
(tree.def == null || allowDiamondWithAnonymousClassCreation);
}
@Override
List<JCNewClass> rewrite(JCNewClass oldTree) {
if (oldTree.clazz.hasTag(TYPEAPPLY)) {
JCNewClass nc = copier.copy(oldTree);
((JCTypeApply)nc.clazz).arguments = List.nil();
return List.of(nc);
} else {
return List.of(oldTree);
}
}
@Override
void process(JCNewClass oldTree, JCNewClass newTree, boolean hasErrors) {
if (!hasErrors) {
List<Type> inferredArgs, explicitArgs;
if (oldTree.def != null) {
inferredArgs = newTree.def.implementing.nonEmpty()
? newTree.def.implementing.get(0).type.getTypeArguments()
: newTree.def.extending.type.getTypeArguments();
explicitArgs = oldTree.def.implementing.nonEmpty()
? oldTree.def.implementing.get(0).type.getTypeArguments()
: oldTree.def.extending.type.getTypeArguments();
} else {
inferredArgs = newTree.type.getTypeArguments();
explicitArgs = oldTree.type.getTypeArguments();
}
for (Type t : inferredArgs) {
if (!types.isSameType(t, explicitArgs.head)) {
return;
}
explicitArgs = explicitArgs.tail;
}
log.warning(oldTree.clazz, Warnings.DiamondRedundantArgs);
}
}
}
class LambdaAnalyzer extends StatementAnalyzer<JCNewClass, JCLambda> {
LambdaAnalyzer() {
super(AnalyzerMode.LAMBDA, NEWCLASS);
}
@Override
boolean match (JCNewClass tree){
Type clazztype = tree.clazz.type;
return tree.def != null &&
clazztype.hasTag(CLASS) &&
types.isFunctionalInterface(clazztype.tsym) &&
decls(tree.def).length() == 1;
}
private List<JCTree> decls(JCClassDecl decl) {
ListBuffer<JCTree> decls = new ListBuffer<>();
for (JCTree t : decl.defs) {
if (t.hasTag(METHODDEF)) {
JCMethodDecl md = (JCMethodDecl)t;
if ((md.getModifiers().flags & GENERATEDCONSTR) == 0) {
decls.add(md);
}
} else {
decls.add(t);
}
}
return decls.toList();
}
@Override
List<JCLambda> rewrite(JCNewClass oldTree){
JCMethodDecl md = (JCMethodDecl)copier.copy(decls(oldTree.def).head);
List<JCVariableDecl> params = md.params;
JCBlock body = md.body;
JCLambda newTree = make.at(oldTree).Lambda(params, body);
return List.of(newTree);
}
@Override
void process (JCNewClass oldTree, JCLambda newTree, boolean hasErrors){
if (!hasErrors) {
log.warning(oldTree.def, Warnings.PotentialLambdaFound);
}
}
}
class RedundantTypeArgAnalyzer extends StatementAnalyzer<JCMethodInvocation, JCMethodInvocation> {
RedundantTypeArgAnalyzer() {
super(AnalyzerMode.METHOD, APPLY);
}
@Override
boolean match (JCMethodInvocation tree){
return tree.typeargs != null &&
tree.typeargs.nonEmpty();
}
@Override
List<JCMethodInvocation> rewrite(JCMethodInvocation oldTree){
JCMethodInvocation app = copier.copy(oldTree);
app.typeargs = List.nil();
return List.of(app);
}
@Override
void process (JCMethodInvocation oldTree, JCMethodInvocation newTree, boolean hasErrors){
if (!hasErrors) {
log.warning(oldTree, Warnings.MethodRedundantTypeargs);
}
}
}
abstract class RedundantLocalVarTypeAnalyzerBase<X extends JCStatement> extends StatementAnalyzer<X, X> {
RedundantLocalVarTypeAnalyzerBase(JCTree.Tag tag) {
super(AnalyzerMode.LOCAL, tag);
}
boolean isImplicitlyTyped(JCVariableDecl decl) {
return decl.vartype.pos == Position.NOPOS;
}
JCVariableDecl rewriteVarType(JCVariableDecl oldTree) {
JCVariableDecl newTree = copier.copy(oldTree);
newTree.vartype = null;
return newTree;
}
void processVar(JCVariableDecl oldTree, JCVariableDecl newTree, boolean hasErrors) {
if (!hasErrors) {
if (types.isSameType(oldTree.type, newTree.type)) {
log.warning(oldTree, Warnings.LocalRedundantType);
}
}
}
}
class RedundantLocalVarTypeAnalyzer extends RedundantLocalVarTypeAnalyzerBase<JCVariableDecl> {
RedundantLocalVarTypeAnalyzer() {
super(VARDEF);
}
boolean match(JCVariableDecl tree){
return tree.sym.owner.kind == Kind.MTH &&
tree.init != null && !isImplicitlyTyped(tree) &&
attr.canInferLocalVarType(tree) == null;
}
@Override
List<JCVariableDecl> rewrite(JCVariableDecl oldTree) {
return List.of(rewriteVarType(oldTree));
}
@Override
void process(JCVariableDecl oldTree, JCVariableDecl newTree, boolean hasErrors){
processVar(oldTree, newTree, hasErrors);
}
}
class RedundantLocalVarTypeAnalyzerForEach extends RedundantLocalVarTypeAnalyzerBase<JCEnhancedForLoop> {
RedundantLocalVarTypeAnalyzerForEach() {
super(FOREACHLOOP);
}
@Override
boolean match(JCEnhancedForLoop tree){
return !isImplicitlyTyped(tree.var);
}
@Override
List<JCEnhancedForLoop> rewrite(JCEnhancedForLoop oldTree) {
JCEnhancedForLoop newTree = copier.copy(oldTree);
newTree.var = rewriteVarType(oldTree.var);
newTree.body = make.at(oldTree.body).Block(0, List.nil());
return List.of(newTree);
}
@Override
void process(JCEnhancedForLoop oldTree, JCEnhancedForLoop newTree, boolean hasErrors){
processVar(oldTree.var, newTree.var, hasErrors);
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
StatementAnalyzer<JCTree, JCTree>[] analyzers = new StatementAnalyzer[] {
new DiamondInitializer(),
new LambdaAnalyzer(),
new RedundantTypeArgAnalyzer(),
new RedundantLocalVarTypeAnalyzer(),
new RedundantLocalVarTypeAnalyzerForEach()
};
Env<AttrContext> copyEnvIfNeeded(JCTree tree, Env<AttrContext> env) {
if (!analyzerModes.isEmpty() &&
!env.info.isSpeculative &&
TreeInfo.isStatement(tree) &&
!tree.hasTag(LABELLED)) {
Env<AttrContext> analyzeEnv =
env.dup(env.tree, env.info.dup(env.info.scope.dupUnshared(env.info.scope.owner)));
analyzeEnv.info.returnResult = analyzeEnv.info.returnResult != null ?
attr.new ResultInfo(analyzeEnv.info.returnResult.pkind,
analyzeEnv.info.returnResult.pt) : null;
return analyzeEnv;
} else {
return null;
}
}
void analyzeIfNeeded(JCTree tree, Env<AttrContext> env) {
if (env != null) {
JCStatement stmt = (JCStatement)tree;
analyze(stmt, env);
}
}
protected void analyze(JCStatement statement, Env<AttrContext> env) {
StatementScanner statementScanner = new StatementScanner(statement, env);
statementScanner.scan();
if (!statementScanner.rewritings.isEmpty()) {
for (RewritingContext rewriting : statementScanner.rewritings) {
deferredAnalysisHelper.queue(rewriting);
}
}
}
interface DeferredAnalysisHelper {
void queue(RewritingContext rewriting);
void flush(Env<AttrContext> flushEnv);
}
DeferredAnalysisHelper flushDeferredHelper = new DeferredAnalysisHelper() {
@Override
public void queue(RewritingContext rewriting) {
}
@Override
public void flush(Env<AttrContext> flushEnv) {
}
};
DeferredAnalysisHelper queueDeferredHelper = new DeferredAnalysisHelper() {
Map<ClassSymbol, Queue<RewritingContext>> Q = new HashMap<>();
@Override
public void queue(RewritingContext rewriting) {
Queue<RewritingContext> s = Q.computeIfAbsent(rewriting.env.enclClass.sym.outermostClass(), k -> new ArrayDeque<>());
s.add(rewriting);
}
@Override
public void flush(Env<AttrContext> flushEnv) {
if (!Q.isEmpty()) {
DeferredAnalysisHelper prevHelper = deferredAnalysisHelper;
try {
deferredAnalysisHelper = flushDeferredHelper;
Queue<RewritingContext> rewritings = Q.get(flushEnv.enclClass.sym.outermostClass());
while (rewritings != null && !rewritings.isEmpty()) {
doAnalysis(rewritings.remove());
}
} finally {
deferredAnalysisHelper = prevHelper;
}
}
}
};
DeferredAnalysisHelper deferredAnalysisHelper = queueDeferredHelper;
void doAnalysis(RewritingContext rewriting) {
DiagnosticSource prevSource = log.currentSource();
LocalCacheContext localCacheContext = argumentAttr.withLocalCacheContext();
try {
log.useSource(rewriting.env.toplevel.getSourceFile());
JCStatement treeToAnalyze = (JCStatement)rewriting.originalTree;
if (rewriting.env.info.scope.owner.kind == Kind.TYP) {
treeToAnalyze = make.at(Position.NOPOS)
.Block(Flags.SYNTHETIC, List.of((JCStatement)rewriting.originalTree));
}
deferredAttr.attribSpeculative(treeToAnalyze, rewriting.env, attr.statInfo, new TreeRewriter(rewriting),
t -> rewriting.diagHandler(), argumentAttr.withLocalCacheContext());
rewriting.analyzer.process(rewriting.oldTree, rewriting.replacement, rewriting.erroneous);
} catch (Throwable ex) {
Assert.error("Analyzer error when processing: " + rewriting.originalTree);
} finally {
log.useSource(prevSource.getFile());
localCacheContext.leave();
}
}
public void flush(Env<AttrContext> flushEnv) {
deferredAnalysisHelper.flush(flushEnv);
}
class StatementScanner extends TreeScanner {
ListBuffer<RewritingContext> rewritings = new ListBuffer<>();
JCTree originalTree;
Env<AttrContext> env;
StatementScanner(JCTree originalTree, Env<AttrContext> env) {
this.originalTree = originalTree;
this.env = attr.copyEnv(env);
}
public void scan() {
scan(originalTree);
}
@Override
@SuppressWarnings("unchecked")
public void scan(JCTree tree) {
if (tree != null) {
for (StatementAnalyzer<JCTree, JCTree> analyzer : analyzers) {
if (analyzer.isEnabled() &&
tree.hasTag(analyzer.tag) &&
analyzer.match(tree)) {
for (JCTree t : analyzer.rewrite(tree)) {
rewritings.add(new RewritingContext(originalTree, tree, t, analyzer, env));
}
break;
}
}
}
super.scan(tree);
}
@Override
public void visitClassDef(JCClassDecl tree) {
}
@Override
public void visitMethodDef(JCMethodDecl tree) {
}
@Override
public void visitBlock(JCBlock tree) {
}
@Override
public void visitSwitch(JCSwitch tree) {
scan(tree.getExpression());
}
@Override
public void visitForLoop(JCForLoop tree) {
scan(tree.getCondition());
scan(tree.getUpdate());
}
@Override
public void visitTry(JCTry tree) {
scan(tree.getBlock());
scan(tree.getCatches());
scan(tree.getFinallyBlock());
}
@Override
public void visitForeachLoop(JCEnhancedForLoop tree) {
scan(tree.getExpression());
}
@Override
public void visitWhileLoop(JCWhileLoop tree) {
scan(tree.getCondition());
}
@Override
public void visitDoLoop(JCDoWhileLoop tree) {
scan(tree.getCondition());
}
@Override
public void visitIf(JCIf tree) {
scan(tree.getCondition());
}
}
class RewritingContext {
JCTree originalTree;
JCTree oldTree;
JCTree replacement;
boolean erroneous;
Env<AttrContext> env;
StatementAnalyzer<JCTree, JCTree> analyzer;
RewritingContext(
JCTree originalTree,
JCTree oldTree,
JCTree replacement,
StatementAnalyzer<JCTree, JCTree> analyzer,
Env<AttrContext> env) {
this.originalTree = originalTree;
this.oldTree = oldTree;
this.replacement = replacement;
this.analyzer = analyzer;
this.env = attr.copyEnv(env);
if (originalTree.hasTag(VARDEF)) {
this.env.info.scope.remove(((JCVariableDecl)originalTree).sym);
}
}
Log.DeferredDiagnosticHandler diagHandler() {
return new Log.DeferredDiagnosticHandler(log, d -> {
if (d.getType() == DiagnosticType.ERROR) {
erroneous = true;
}
return true;
});
}
}
class AnalyzerCopier extends TreeCopier<Void> {
public AnalyzerCopier() {
super(make);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public JCTree visitLambdaExpression(LambdaExpressionTree node, Void _unused) {
JCLambda oldLambda = (JCLambda)node;
JCLambda newLambda = (JCLambda)super.visitLambdaExpression(node, _unused);
if (oldLambda.paramKind == ParameterKind.IMPLICIT) {
newLambda.paramKind = ParameterKind.IMPLICIT;
newLambda.params.forEach(p -> p.vartype = null);
}
return newLambda;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public JCTree visitNewClass(NewClassTree node, Void aVoid) {
JCNewClass oldNewClazz = (JCNewClass)node;
JCNewClass newNewClazz = (JCNewClass)super.visitNewClass(node, aVoid);
if (!oldNewClazz.args.isEmpty() && oldNewClazz.args.head.hasTag(NULLCHK)) {
newNewClazz.encl = ((JCUnary)newNewClazz.args.head).arg;
newNewClazz.args = newNewClazz.args.tail;
}
return newNewClazz;
}
}
class TreeRewriter extends AnalyzerCopier {
RewritingContext rewriting;
TreeRewriter(RewritingContext rewriting) {
this.rewriting = rewriting;
}
@Override
@SuppressWarnings("unchecked")
public <Z extends JCTree> Z copy(Z tree, Void _unused) {
Z newTree = super.copy(tree, null);
if (tree != null && tree == rewriting.oldTree) {
Assert.checkNonNull(rewriting.replacement);
newTree = (Z)rewriting.replacement;
}
return newTree;
}
}
}