1 /*
   2  * Copyright (c) 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 
  26 package com.sun.tools.javac.comp;
  27 
  28 import com.sun.tools.javac.code.*;
  29 
  30 import static com.sun.tools.javac.code.Flags.asFlagSet;
  31 import static com.sun.tools.javac.code.Kinds.Kind.*;
  32 import com.sun.tools.javac.code.Lint.LintCategory;
  33 import com.sun.tools.javac.code.Symbol.MethodSymbol;
  34 import com.sun.tools.javac.code.Symbol.OperatorSymbol;
  35 import com.sun.tools.javac.code.Symbol.TypeSymbol;
  36 import com.sun.tools.javac.jvm.ByteCodes;
  37 import com.sun.tools.javac.tree.*;
  38 import com.sun.tools.javac.tree.JCTree.*;
  39 import com.sun.tools.javac.util.*;
  40 
  41 /**
  42  * Support for "Minimal Value Types" : Process classes annotated with @DeriveValueType -
  43  * All semantic checks are centralized in this place so that we can blow them all away when
  44  * moving to "Maximal Value Types".
  45  *
  46  * see: http://cr.openjdk.java.net/~jrose/values/shady-values.html
  47  *
  48  */
  49 
  50 public class ValueCapableClassAttr extends TreeTranslator {
  51 
  52     protected static final Context.Key<ValueCapableClassAttr> valueCapableClassAttr = new Context.Key<>();
  53     private JCMethodDecl currentMethod;
  54     private Log log;
  55     private Names names;
  56     private Symtab syms;
  57     private final JCDiagnostic.Factory diags;
  58     private final Types types;
  59     private final Check chk;
  60     private boolean inValue = false;
  61 
  62     public static ValueCapableClassAttr instance(Context context) {
  63         ValueCapableClassAttr instance = context.get(valueCapableClassAttr);
  64         if (instance == null)
  65             instance = new ValueCapableClassAttr(context);
  66         return instance;
  67     }
  68 
  69     protected ValueCapableClassAttr(Context context) {
  70         context.put(valueCapableClassAttr, this);
  71         log = Log.instance(context);
  72         names = Names.instance(context);
  73         syms = Symtab.instance(context);
  74         diags = JCDiagnostic.Factory.instance(context);
  75         types = Types.instance(context);
  76         chk = Check.instance(context);
  77     }
  78 
  79     public void visitClassDef(JCClassDecl tree) {
  80         boolean oldInValue = inValue;
  81         try {
  82             inValue = false;
  83             for (List<JCAnnotation> al = tree.mods.annotations; !al.isEmpty(); al = al.tail) {
  84                 JCAnnotation a = al.head;
  85                 if (a.annotationType.type == syms.deriveValueType && a.args.isEmpty()) {
  86                     inValue = true;
  87                     tree.sym.flags_field |= Flags.VALUE_CAPABLE;
  88                     break;
  89                 }
  90             }
  91             if (inValue) {
  92                 if (tree.extending != null) {
  93                     log.warning(LintCategory.VALUES, tree.pos(), "value.may.not.extend");
  94                 }
  95                 if ((tree.mods.flags & Flags.FINAL) == 0) {
  96                     log.warning(LintCategory.VALUES, tree.pos(), "value.must.be.final");
  97                 }
  98                 chk.checkNonCyclicMembership(tree);
  99             }
 100             super.visitClassDef(tree);
 101         } finally {
 102             inValue = oldInValue;
 103         }
 104     }
 105 
 106     public void visitMethodDef(JCMethodDecl tree) {
 107         JCMethodDecl previousMethod = currentMethod;
 108         try {
 109             currentMethod = tree;
 110             if (tree.sym != null && tree.sym.owner.isValueCapable()) {
 111                 if ((tree.sym.flags() & (Flags.SYNCHRONIZED | Flags.STATIC)) == Flags.SYNCHRONIZED) {
 112                     log.warning(LintCategory.VALUES, tree.pos(), "mod.not.allowed.here", asFlagSet(Flags.SYNCHRONIZED));
 113                 }
 114                 if (tree.sym.attribute(syms.overrideType.tsym) != null) {
 115                     MethodSymbol m = tree.sym;
 116                     TypeSymbol owner = (TypeSymbol)m.owner;
 117                     for (Type sup : types.closure(owner.type)) {
 118                         if (sup == owner.type)
 119                             continue; // skip "this"
 120                         Scope scope = sup.tsym.members();
 121                         for (Symbol sym : scope.getSymbolsByName(m.name)) {
 122                             if (!sym.isStatic() && m.overrides(sym, owner, types, true)) {
 123                                 switch (sym.name.toString()) {
 124                                     case "hashCode":
 125                                     case "equals":
 126                                     case "toString":
 127                                         break;
 128                                     default:
 129                                         log.warning(LintCategory.VALUES, tree.pos(), "value.does.not.support", "overriding java.lang.Object's method: " + sym.name.toString());
 130                                         break;
 131                                 }
 132                             }
 133                         }
 134                     }
 135                 }
 136             }
 137             super.visitMethodDef(tree);
 138         } finally {
 139             currentMethod = previousMethod;
 140         }
 141 
 142     }
 143 
 144     public void visitVarDef(JCVariableDecl tree) {
 145         if (tree.sym != null && tree.sym.owner.kind == TYP && tree.sym.owner.isValueCapable()) {
 146             if ((tree.mods.flags & (Flags.FINAL | Flags.STATIC)) == 0) {
 147                 log.warning(LintCategory.VALUES, tree.pos(), "value.field.must.be.final");
 148             }
 149         }
 150         if (tree.init != null && tree.init.type != null && tree.init.type.hasTag(TypeTag.BOT)) {
 151             if (types.isValueCapable(tree.vartype.type))
 152                 log.warning(LintCategory.VALUES, tree.init.pos(), "prob.found.req", diags.fragment("inconvertible.types", syms.botType, tree.vartype.type));
 153         }
 154         super.visitVarDef(tree);
 155     }
 156 
 157     @Override
 158     public void visitAssign(JCAssign tree) {
 159         if (tree.rhs.type != null && tree.rhs.type.hasTag(TypeTag.BOT)) {
 160             Type lType = tree.lhs.type;
 161             if (lType != null && types.isValueCapable(lType)) {
 162                 log.warning(LintCategory.VALUES, tree.rhs.pos(), "prob.found.req", diags.fragment("inconvertible.types", syms.botType, lType));
 163             }
 164         }
 165         super.visitAssign(tree);
 166     }
 167 
 168     @Override
 169     public void visitReturn(JCReturn tree) {
 170         if (currentMethod != null && tree.expr != null && tree.expr.type != null && tree.expr.type.hasTag(TypeTag.BOT)) {
 171             if (currentMethod.restype != null && types.isValueCapable(currentMethod.restype.type)) {
 172                 log.warning(LintCategory.VALUES, tree.expr.pos(), "prob.found.req", diags.fragment("inconvertible.types", syms.botType, currentMethod.restype.type));
 173             }
 174         }
 175         super.visitReturn(tree);
 176     }
 177 
 178     @Override
 179     public void visitTypeTest(JCInstanceOf tree) {
 180         if (tree.expr.type.hasTag(TypeTag.BOT)) {
 181             if (types.isValueCapable(tree.clazz.type)) {
 182                 log.warning(LintCategory.VALUES, tree.expr.pos(), "prob.found.req", diags.fragment("inconvertible.types", syms.botType, tree.clazz.type));
 183             }
 184         }
 185         super.visitTypeTest(tree);
 186     }
 187 
 188     @Override
 189     public void visitTypeCast(JCTypeCast tree) {
 190         if (tree.expr.type != null && tree.expr.type.hasTag(TypeTag.BOT) &&
 191                 tree.clazz.type != null && types.isValueCapable(tree.clazz.type)) {
 192             log.warning(LintCategory.VALUES, tree.expr.pos(), "prob.found.req", diags.fragment("inconvertible.types", syms.botType, tree.clazz.type));
 193         }
 194         super.visitTypeCast(tree);
 195     }
 196 
 197     public void visitBinary(JCBinary tree) {
 198         Type left = tree.lhs.type;
 199         Type right = tree.rhs.type;
 200         Symbol operator = tree.operator;
 201 
 202         if (operator != null && operator.kind == MTH &&
 203                 left != null && !left.isErroneous() &&
 204                 right != null && !right.isErroneous()) {
 205             int opc = ((OperatorSymbol)operator).opcode;
 206             if ((opc == ByteCodes.if_acmpeq || opc == ByteCodes.if_acmpne)) {
 207                 if ((left.hasTag(TypeTag.BOT) && types.isValueCapable(right)) ||
 208                         (right.hasTag(TypeTag.BOT) && types.isValueCapable(left))) {
 209                     log.warning(LintCategory.VALUES, tree.pos(), "incomparable.types", left, right);
 210                 }
 211             }
 212             // this is likely to change.
 213             if (operator.name.contentEquals("==") || operator.name.contentEquals("!=")) {
 214                 if (types.isValueCapable(tree.lhs.type) ||
 215                         types.isValueCapable(tree.rhs.type))
 216                     log.warning(LintCategory.VALUES, tree.pos(), "value.does.not.support", tree.operator.name.toString());
 217             }
 218         }
 219         super.visitBinary(tree);
 220     }
 221 
 222     @Override
 223     public void visitSynchronized(JCSynchronized tree) {
 224         if (types.isValueCapable(tree.lock.type)) {
 225             log.warning(LintCategory.VALUES, tree.pos(), "type.found.req", tree.lock.type, diags.fragment("type.req.ref"));
 226         }
 227         super.visitSynchronized(tree);
 228     }
 229 
 230     public void visitApply(JCMethodInvocation tree) {
 231         final Symbol method = TreeInfo.symbolFor(tree);
 232         if (method != null && method.kind != ERR) {
 233             if (method.name.contentEquals("identityHashCode") && method.owner.type == syms.systemType) {
 234                 if ((tree.args.length() == 1) && types.isValueCapable(tree.args.head.type))
 235                     log.warning(LintCategory.VALUES, tree.pos(), "value.does.not.support", "identityHashCode");
 236             }
 237 
 238             if (method.name != names.init && method.name != names.getClass && method.owner.type == syms.objectType) {
 239                 boolean receiverIsValue = false;
 240                 switch (tree.meth.getTag()) {
 241                     case IDENT:
 242                         receiverIsValue = inValue;
 243                         break;
 244                     case SELECT:
 245                         final Symbol symbol = TreeInfo.symbol(((JCFieldAccess)tree.meth).selected);
 246                         receiverIsValue = symbol != null &&
 247                                 (symbol.name == names._super ? inValue : types.isValueCapable(symbol.type));
 248                         break;
 249                 }
 250                 if (receiverIsValue) {
 251                     log.warning(LintCategory.VALUES, tree.pos(), "value.does.not.support", "calling java.lang.Object's method: " + method.name.toString());
 252                 }
 253             }
 254             final List<Type> parameterTypes = method.type.getParameterTypes();
 255             for (int i = 0; i < tree.args.size(); i++) {
 256                 final JCExpression arg = tree.args.get(i);
 257                 if (arg.type != null && arg.type.hasTag(TypeTag.BOT)) {
 258                     Type param = i < parameterTypes.size() ? parameterTypes.get(i) :
 259                             types.elemtype(parameterTypes.get(parameterTypes.size() - 1));
 260                     if (types.isValueCapable(param))
 261                         log.warning(LintCategory.VALUES, arg.pos(), "prob.found.req", diags.fragment("inconvertible.types", syms.botType, param));
 262                 }
 263             }
 264         }
 265         super.visitApply(tree);
 266     }
 267 }