/* * Copyright 2012 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.errorprone.bugpatterns; import com.google.errorprone.VisitorState; import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.Tree; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.TypeSymbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.TreeScanner; import com.sun.tools.javac.util.Names; /** Analyzes trees for references to their enclosing instance. */ public class CanBeStaticAnalyzer extends TreeScanner { /** Returns true if the tree references its enclosing class. */ public static boolean referencesOuter(Tree tree, Symbol owner, VisitorState state) { CanBeStaticAnalyzer scanner = new CanBeStaticAnalyzer(owner, state); ((JCTree) tree).accept(scanner); return scanner.referencesOuter; } private final Names names; private final Symbol owner; private final VisitorState state; private boolean referencesOuter = false; private CanBeStaticAnalyzer(Symbol owner, VisitorState state) { this.owner = owner; this.state = state; this.names = Names.instance(state.context); } @Override public void visitIdent(JCTree.JCIdent tree) { // check for unqualified references to instance members (fields and methods) declared // in an enclosing scope if (tree.sym.isStatic()) { return; } switch (tree.sym.getKind()) { case TYPE_PARAMETER: // declaring a class as non-static just to access a type parameter is silly - // why not just re-declare the type parameter instead of capturing it? // TODO(cushon): consider making the suggestion anyways, maybe with a fix? // fall through case FIELD: case METHOD: if (!isOwnedBy(tree.sym, owner, state.getTypes())) { referencesOuter = true; } break; case CLASS: Type enclosing = tree.type.getEnclosingType(); if (enclosing != null) { enclosing.accept(new TypeVariableScanner(), null); } break; default: break; } } private boolean isOwnedBy(Symbol sym, Symbol owner, Types types) { if (sym.owner == owner) { return true; } if (owner instanceof TypeSymbol) { return sym.isMemberOf((TypeSymbol) owner, types); } return false; } // check for implicit references to type parameters of the enclosing // class in unqualified references to sibling types, e.g.: // // class Test<T> { // class One {} // class Two { // One one; // implicit Test<T>.One // } // } private class TypeVariableScanner extends Types.SimpleVisitor<Void, Void> { @Override public Void visitTypeVar(Type.TypeVar t, Void aVoid) { referencesOuter = true; return null; } @Override public Void visitClassType(Type.ClassType t, Void aVoid) { for (Type a : t.getTypeArguments()) { a.accept(this, null); } if (t.getEnclosingType() != null) { t.getEnclosingType().accept(this, null); } return null; } @Override public Void visitType(Type type, Void unused) { return null; } } @Override public void visitSelect(JCTree.JCFieldAccess tree) { super.visitSelect(tree); // check for qualified this/super references if (tree.name == names._this || tree.name == names._super) { referencesOuter = true; } } @Override public void visitNewClass(JCTree.JCNewClass tree) { super.visitNewClass(tree); // check for constructor invocations where the type is a member of an enclosing class, // the enclosing instance is passed as an explicit argument Type type = ASTHelpers.getType(tree.clazz); if (type == null) { return; } if (memberOfEnclosing(owner, state, type.tsym)) { referencesOuter = true; } } /** Is sym a non-static member of an enclosing class of currentClass? */ static boolean memberOfEnclosing(Symbol owner, VisitorState state, Symbol sym) { if (sym == null || !sym.hasOuterInstance()) { return false; } for (ClassSymbol encl = owner.owner.enclClass(); encl != null; encl = encl.owner != null ? encl.owner.enclClass() : null) { if (sym.isMemberOf(encl, state.getTypes())) { return true; } } return false; } @Override public void visitAnnotation(JCAnnotation tree) { // skip annotations; the keys of key/value pairs look like unqualified method invocations } }