Package com.google.gwt.dev.javac

Source Code of com.google.gwt.dev.javac.JsniChecker

/*
* Copyright 2008 Google Inc.
*
* 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.gwt.dev.javac;

import com.google.gwt.core.client.UnsafeNativeLong;
import com.google.gwt.dev.jdt.FindJsniRefVisitor;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.util.InstalledHelpInfo;
import com.google.gwt.dev.util.JsniRef;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

/**
* Tests for access to Java from JSNI. Issues a warning for:
* <ul>
* <li>JSNI methods with a parameter or return type of long.</li>
* <li>Access from JSNI to a field whose type is long.</li>
* <li>Access from JSNI to a method with a parameter or return type of long.</li>
* <li>JSNI references to anonymous classes.</li>
* </ul>
* All tests also apply for arrays of longs, arrays of arrays of longs, etc.
*/
public class JsniChecker {
  private class CheckingVisitor extends ASTVisitor implements
      ClassFileConstants {
    @Override
    public void endVisit(MethodDeclaration meth, ClassScope scope) {
      if (meth.isNative() && !hasUnsafeLongsAnnotation(meth, scope)) {
        checkDecl(meth, scope);
        checkRefs(meth, scope);
      }
    }

    private void checkDecl(MethodDeclaration meth, ClassScope scope) {
      TypeReference returnType = meth.returnType;
      if (containsLong(returnType, scope)) {
        longAccessError(meth, "Type '" + typeString(returnType)
            + "' may not be returned from a JSNI method");
      }

      if (meth.arguments != null) {
        for (Argument arg : meth.arguments) {
          if (containsLong(arg.type, scope)) {
            longAccessError(arg, "Parameter '" + String.valueOf(arg.name)
                + "': type '" + typeString(arg.type)
                + "' is not safe to access in JSNI code");
          }
        }
      }
    }

    private void checkFieldRef(ReferenceBinding clazz, JsniRef jsniRef,
        Set<String> errors, Map<String, Set<String>> warnings) {
      assert jsniRef.isField();
      FieldBinding target = getField(clazz, jsniRef);
      if (target == null) {
        return;
      }
      if (containsLong(target.type)) {
        errors.add("Referencing field '" + jsniRef.className() + "."
            + jsniRef.memberName() + "': type '" + typeString(target.type)
            + "' is not safe to access in JSNI code");
      }
      if (target.isDeprecated()) {
        add(warnings, "deprecation", "Referencing deprecated field '"
            + jsniRef.className() + "." + jsniRef.memberName() + "'");
      }
    }

    private void checkMethodRef(ReferenceBinding clazz, JsniRef jsniRef,
        Set<String> errors, Map<String, Set<String>> warnings) {
      assert jsniRef.isMethod();
      MethodBinding target = getMethod(clazz, jsniRef);
      if (target == null) {
        return;
      }
      if (containsLong(target.returnType)) {
        errors.add("Referencing method '" + jsniRef.className() + "."
            + jsniRef.memberName() + "': return type '"
            + typeString(target.returnType)
            + "' is not safe to access in JSNI code");
      }

      if (target.parameters != null) {
        int i = 0;
        for (TypeBinding paramType : target.parameters) {
          ++i;
          if (containsLong(paramType)) {
            // It would be nice to print the parameter name, but how to find it?
            errors.add("Parameter " + i + " of method '" + jsniRef.className()
                + "." + jsniRef.memberName() + "': type '"
                + typeString(paramType)
                + "' may not be passed out of JSNI code");
          }
        }
      }

      if (target.isDeprecated()) {
        add(warnings, "deprecation", "Referencing deprecated method '"
            + jsniRef.className() + "." + jsniRef.memberName() + "'");
      }
    }

    private void checkRefs(MethodDeclaration meth, ClassScope scope) {
      Map<String, Set<String>> errors = new LinkedHashMap<String, Set<String>>();

      // first do a sloppy parse, for speed

      FindJsniRefVisitor sloppyRefsVisitor = new FindJsniRefVisitor();
      sloppyRefsVisitor.beSloppy();
      meth.traverse(sloppyRefsVisitor, scope);

      for (String jsniRefString : sloppyRefsVisitor.getJsniRefs()) {
        JsniRef jsniRef = JsniRef.parse(jsniRefString);
        Map<String, Set<String>> warnings = new LinkedHashMap<String, Set<String>>();

        if (jsniRef != null) {
          ReferenceBinding clazz = findClass(jsniRef);
          if (looksLikeAnonymousClass(jsniRef)
              || (clazz != null && clazz.isAnonymousType())) {
            add(warnings, "deprecation", "Referencing class '"
                + jsniRef.className()
                + ": JSNI references to anonymous classes are deprecated");

          } else if (clazz != null) {
            if (clazz.isDeprecated()) {
              add(warnings, "deprecation", "Referencing deprecated class '"
                  + jsniRef.className() + "'");
            }

            Set<String> refErrors = new LinkedHashSet<String>();
            if (jsniRef.isMethod()) {
              checkMethodRef(clazz, jsniRef, refErrors, warnings);
            } else {
              checkFieldRef(clazz, jsniRef, refErrors, warnings);
            }
            if (!refErrors.isEmpty()) {
              errors.put(jsniRefString, refErrors);
            }
          } else if (!jsniRef.className().equals("null")) {
            /*
             * TODO(scottb): re-enable this when we no longer get a bunch of
             * false failures. Currently we can't resolve top level types (like
             * boolean_Array_Rank_1_FieldSerializer), and we also don't resolve
             * array and primitive refs, like @Z[]::class.
             */
            // GWTProblem.recordInCud(ProblemSeverities.Warning, meth, cud,
            // "Referencing class '" + jsniRef.className()
            // + ": unable to resolve class, expect subsequent failures",
            // null);
          }
        }

        filterWarnings(meth, warnings);
        for (Set<String> set : warnings.values()) {
          for (String warning : set) {
            GWTProblem.recordInCud(ProblemSeverities.Warning, meth, cud,
                warning, null);
          }
        }
      }

      if (!errors.isEmpty()) {
        // do a strict parse to find out which JSNI refs are real
        FindJsniRefVisitor jsniRefsVisitor = new FindJsniRefVisitor();
        meth.traverse(jsniRefsVisitor, scope);
        for (String jsniRefString : jsniRefsVisitor.getJsniRefs()) {
          if (errors.containsKey(jsniRefString)) {
            for (String err : errors.get(jsniRefString)) {
              longAccessError(meth, err);
            }
          }
        }
      }
    }

    /**
     * Check whether the argument type is the <code>long</code> primitive type.
     * If the argument is <code>null</code>, returns <code>false</code>.
     */
    private boolean containsLong(TypeBinding type) {
      if (type instanceof BaseTypeBinding) {
        BaseTypeBinding btb = (BaseTypeBinding) type;
        if (btb.id == TypeIds.T_long) {
          return true;
        }
      }

      return false;
    }

    private boolean containsLong(final TypeReference returnType,
        ClassScope scope) {
      return returnType != null && containsLong(returnType.resolveType(scope));
    }

    /**
     * Given a MethodDeclaration and a map of warnings, filter those warning
     * messages that are keyed with a suppressed type.
     */
    private void filterWarnings(MethodDeclaration method,
        Map<String, Set<String>> warnings) {
      Annotation[] annotations = method.annotations;
      if (annotations == null) {
        return;
      }

      for (Annotation a : annotations) {
        if (SuppressWarnings.class.getName().equals(
            CharOperation.toString(((ReferenceBinding) a.resolvedType).compoundName))) {
          for (MemberValuePair pair : a.memberValuePairs()) {
            if (String.valueOf(pair.name).equals("value")) {
              String[] values;
              Expression valueExpr = pair.value;

              if (valueExpr instanceof StringLiteral) {
                // @SuppressWarnings("Foo")
                values = new String[] {((StringLiteral) valueExpr).constant.stringValue()};

              } else if (valueExpr instanceof ArrayInitializer) {
                // @SuppressWarnings({ "Foo", "Bar"})
                ArrayInitializer ai = (ArrayInitializer) valueExpr;
                values = new String[ai.expressions.length];
                for (int i = 0, j = values.length; i < j; i++) {
                  values[i] = ((StringLiteral) ai.expressions[i]).constant.stringValue();
                }
              } else {
                throw new InternalCompilerException(
                    "Unable to analyze SuppressWarnings annotation");
              }

              for (String value : values) {
                for (Iterator<String> it = warnings.keySet().iterator(); it.hasNext();) {
                  if (it.next().toLowerCase().equals(value.toLowerCase())) {
                    it.remove();
                  }
                }
              }
              return;
            }
          }
        }
      }
    }

    private ReferenceBinding findClass(JsniRef jsniRef) {
      char[][] compoundName = getCompoundName(jsniRef);
      TypeBinding binding = cud.scope.getType(compoundName, compoundName.length);

      /*
       * TODO(scottb): we cannot currently resolve top-level types; here's some
       * experimental code that will let us do this.
       */
      // ReferenceBinding binding = cud.scope.environment().askForType(
      // compoundName);
      // while (binding == null && compoundName.length > 1) {
      // int newLen = compoundName.length - 1;
      // char[][] next = new char[newLen][];
      // System.arraycopy(compoundName, 0, next, 0, newLen - 1);
      // next[newLen - 1] = CharOperation.concat(compoundName[newLen - 1],
      // compoundName[newLen], '$');
      // compoundName = next;
      // binding = cud.scope.environment().askForType(compoundName);
      // }
      if (binding instanceof ProblemReferenceBinding) {
        ProblemReferenceBinding prb = (ProblemReferenceBinding) binding;
        if (prb.problemId() == ProblemReasons.NotVisible) {
          // It's just a visibility problem, so try drilling down manually
          ReferenceBinding drilling = prb.closestReferenceMatch();
          for (int i = prb.compoundName.length; i < compoundName.length; i++) {
            drilling = drilling.getMemberType(compoundName[i]);
          }
          binding = drilling;
        }
      }

      if (binding instanceof ReferenceBinding
          && !(binding instanceof ProblemReferenceBinding)) {
        return (ReferenceBinding) binding;
      }
      return null;
    }

    private char[][] getCompoundName(JsniRef jsniRef) {
      String className = jsniRef.className().replace('$', '.');
      char[][] compoundName = CharOperation.splitOn('.',
          className.toCharArray());
      return compoundName;
    }

    private FieldBinding getField(ReferenceBinding clazz, JsniRef jsniRef) {
      assert jsniRef.isField();
      return clazz.getField(jsniRef.memberName().toCharArray(), false);
    }

    private MethodBinding getMethod(ReferenceBinding clazz, JsniRef jsniRef) {
      assert jsniRef.isMethod();
      for (MethodBinding method : clazz.getMethods(jsniRef.memberName().toCharArray())) {
        if (paramTypesMatch(method, jsniRef)) {
          return method;
        }
      }
      return null;
    }

    private boolean hasUnsafeLongsAnnotation(MethodDeclaration meth,
        ClassScope scope) {
      if (meth.annotations != null) {
        for (Annotation annot : meth.annotations) {
          if (isUnsafeLongAnnotation(annot, scope)) {
            return true;
          }
        }
      }
      return false;
    }

    private boolean isUnsafeLongAnnotation(Annotation annot, ClassScope scope) {
      if (annot.type != null) {
        TypeBinding resolved = annot.type.resolveType(scope);
        if (resolved != null) {
          if (resolved instanceof ReferenceBinding) {
            ReferenceBinding rb = (ReferenceBinding) resolved;
            if (CharOperation.equals(rb.compoundName,
                UNSAFE_LONG_ANNOTATION_CHARS)) {
              return true;
            }
          }
        }
      }
      return false;
    }

    private void longAccessError(ASTNode node, String message) {
      GWTProblem.recordInCud(node, cud, message, new InstalledHelpInfo(
          "longJsniRestriction.html"));
    }

    private boolean looksLikeAnonymousClass(JsniRef jsniRef) {
      char[][] compoundName = getCompoundName(jsniRef);
      for (char[] part : compoundName) {
        if (Character.isDigit(part[0])) {
          return true;
        }
      }
      return false;
    }

    private boolean paramTypesMatch(MethodBinding method, JsniRef jsniRef) {
      StringBuilder methodSig = new StringBuilder();
      if (method.parameters != null) {
        for (TypeBinding binding : method.parameters) {
          methodSig.append(binding.signature());
        }
      }
      return methodSig.toString().equals(jsniRef.paramTypesString());
    }

    private String typeString(TypeBinding type) {
      return String.valueOf(type.shortReadableName());
    }

    private String typeString(TypeReference type) {
      return type.toString();
    }
  }

  private static final char[][] UNSAFE_LONG_ANNOTATION_CHARS = CharOperation.splitOn(
      '.', UnsafeNativeLong.class.getName().toCharArray());

  /**
   * Checks an entire
   * {@link org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration}.
   *
   */
  public static void check(CompilationUnitDeclaration cud) {
    JsniChecker checker = new JsniChecker(cud);
    checker.check();
  }

  /**
   * Adds an entry to a map of sets.
   */
  private static <K, V> void add(Map<K, Set<V>> map, K key, V value) {
    Set<V> set = map.get(key);
    if (set == null) {
      set = new LinkedHashSet<V>();
      map.put(key, set);
    }
    set.add(value);
  }

  private final CompilationUnitDeclaration cud;

  private JsniChecker(CompilationUnitDeclaration cud) {
    this.cud = cud;
  }

  private void check() {
    cud.traverse(new CheckingVisitor(), cud.scope);
  }
}
TOP

Related Classes of com.google.gwt.dev.javac.JsniChecker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.