Package de.thetaphi.forbiddenapis

Source Code of de.thetaphi.forbiddenapis.Checker

package de.thetaphi.forbiddenapis;

/*
* (C) Copyright 2013 Uwe Schindler (Generics Policeman) and others.
* Parts of this work are licensed to the Apache Software Foundation (ASF)
* under one or more contributor license agreements.
*
* 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.
*/

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Label;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
import org.objectweb.asm.commons.Method;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PushbackInputStream;
import java.io.Reader;
import java.io.File;
import java.io.StringReader;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.Collections;
import java.util.Formatter;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;

/**
* Task to check if a set of class files contains calls to forbidden APIs
* from a given classpath and list of API signatures (either inline or as pointer to files).
* In contrast to other ANT tasks, this tool does only visit the given classpath
* and the system classloader. It uses the local classpath in preference to the system classpath
* (which violates the spec).
*/
public abstract class Checker {

  public final boolean isSupportedJDK;
 
  private final long start;
 
  final Set<File> bootClassPathJars;
  final Set<String> bootClassPathDirs;
  final ClassLoader loader;
  final boolean internalRuntimeForbidden, failOnMissingClasses, failOnUnresolvableSignatures;
 
  // key is the internal name (slashed):
  final Map<String,ClassSignatureLookup> classesToCheck = new HashMap<String,ClassSignatureLookup>();
  // key is the binary name (dotted):
  final Map<String,ClassSignatureLookup> classpathClassCache = new HashMap<String,ClassSignatureLookup>();
 
  // key is the internal name (slashed), followed by \000 and the field name:
  final Map<String,String> forbiddenFields = new HashMap<String,String>();
  // key is the internal name (slashed), followed by \000 and the method signature:
  final Map<String,String> forbiddenMethods = new HashMap<String,String>();
  // key is the internal name (slashed):
  final Map<String,String> forbiddenClasses = new HashMap<String,String>();
 
  protected abstract void logError(String msg);
  protected abstract void logWarn(String msg);
  protected abstract void logInfo(String msg);
 
  public Checker(ClassLoader loader, boolean internalRuntimeForbidden, boolean failOnMissingClasses, boolean failOnUnresolvableSignatures) {
    this.loader = loader;
    this.internalRuntimeForbidden = internalRuntimeForbidden;
    this.failOnMissingClasses = failOnMissingClasses;
    this.failOnUnresolvableSignatures = failOnUnresolvableSignatures;
    this.start = System.currentTimeMillis();
   
    boolean isSupportedJDK = false;
    final Set<File> bootClassPathJars = new LinkedHashSet<File>();
    final Set<String> bootClassPathDirs = new LinkedHashSet<String>();
    try {
      final RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean();
      if (rb.isBootClassPathSupported()) {
        final String cp = rb.getBootClassPath();
        final StringTokenizer st = new StringTokenizer(cp, File.pathSeparator);
        while (st.hasMoreTokens()) {
          final File f = new File(st.nextToken());
          if (f.isFile()) {
            bootClassPathJars.add(f.getCanonicalFile());
          } else if (f.isDirectory()) {
            String fp = f.getCanonicalPath();
            if (!fp.endsWith(File.separator)) {
              fp += File.separator;
            }
            bootClassPathDirs.add(fp);
          }
        }
      }
      isSupportedJDK = !(bootClassPathJars.isEmpty() && bootClassPathDirs.isEmpty());
      // logInfo("JARs in boot-classpath: " + bootClassPathJars + "; dirs in boot-classpath: " + bootClassPathDirs);
    } catch (IOException ioe) {
      isSupportedJDK = false;
      bootClassPathJars.clear();
      bootClassPathDirs.clear();
    }
    this.bootClassPathJars = Collections.unmodifiableSet(bootClassPathJars);
    this.bootClassPathDirs = Collections.unmodifiableSet(bootClassPathDirs);
   
    if (isSupportedJDK) {
      // check if we can load runtime classes (e.g. java.lang.Object).
      // If this fails, we have a newer Java version than ASM supports:
      try {
        isSupportedJDK = getClassFromClassLoader(Object.class.getName(), true).isRuntimeClass;
      } catch (IllegalArgumentException iae) {
        isSupportedJDK = false;
      } catch (ClassNotFoundException cnfe) {
        isSupportedJDK = false;
      }
    }
   
    // finally set the latest value to final field:
    this.isSupportedJDK = isSupportedJDK;
  }
 
  /** Reads a class (binary name) from the given {@link ClassLoader}. */
  private ClassSignatureLookup getClassFromClassLoader(final String clazz, boolean throwCNFE) throws ClassNotFoundException {
    final ClassSignatureLookup c;
    if (classpathClassCache.containsKey(clazz)) {
      c = classpathClassCache.get(clazz);
      if (throwCNFE && c == null) {
        throw new ClassNotFoundException("Loading of class " + clazz + " failed: Not found");
      }
    } else {
      try {
        final URL url = loader.getResource(clazz.replace('.', '/') + ".class");
        if (url == null) {
          classpathClassCache.put(clazz, null);
          if (throwCNFE) {
            throw new ClassNotFoundException("Loading of class " + clazz + " failed: Not found");
          } else {
            return null;
          }
        }
        boolean isRuntimeClass = false;
        final URLConnection conn = url.openConnection();
        if ("file".equalsIgnoreCase(url.getProtocol())) {
          try {
            final String path = new File(url.toURI()).getCanonicalPath();
            for (final String bcpDir : bootClassPathDirs) {
              if (path.startsWith(bcpDir)) {
                isRuntimeClass = true;
                break;
              }
            }
          } catch (URISyntaxException use) {
            // ignore (should not happen, but if it's happening, it's definitely not a runtime class)
          }
        } else {
          if (conn instanceof JarURLConnection) {
            final URL jarUrl = ((JarURLConnection) conn).getJarFileURL();
            if ("file".equalsIgnoreCase(jarUrl.getProtocol())) try {
              final File jarFile = new File(jarUrl.toURI()).getCanonicalFile();
              isRuntimeClass = bootClassPathJars.contains(jarFile);
            } catch (URISyntaxException use) {
              // ignore (should not happen, but if it's happening, it's definitely not a runtime class)
            }
          }
        }
        final InputStream in = conn.getInputStream();
        try {
          classpathClassCache.put(clazz, c = new ClassSignatureLookup(new ClassReader(in), isRuntimeClass, false));
        } finally {
          in.close();
        }
      } catch (IOException ioe) {
        classpathClassCache.put(clazz, null);
        if (throwCNFE) {
          throw new ClassNotFoundException("Loading of class " + clazz + " failed.", ioe);
        } else {
          return null;
        }
      }
    }
    return c;
  }
  /** Adds the method signature to the list of disallowed methods. The Signature is checked against the given ClassLoader. */
  private void addSignature(final String line, final String defaultMessage) throws ParseException {
    final String clazz, field, signature, message;
    final Method method;
    int p = line.indexOf('@');
    if (p >= 0) {
      signature = line.substring(0, p).trim();
      message = line.substring(p + 1).trim();
    } else {
      signature = line;
      message = defaultMessage;
    }
    p = signature.indexOf('#');
    if (p >= 0) {
      clazz = signature.substring(0, p);
      final String s = signature.substring(p + 1);
      p = s.indexOf('(');
      if (p >= 0) {
        if (p == 0) {
          throw new ParseException("Invalid method signature (method name missing): " + signature);
        }
        // we ignore the return type, its just to match easier (so return type is void):
        try {
          method = Method.getMethod("void " + s, true);
        } catch (IllegalArgumentException iae) {
          throw new ParseException("Invalid method signature: " + signature);
        }
        field = null;
      } else {
        field = s;
        method = null;
      }
    } else {
      clazz = signature;
      method = null;
      field = null;
    }
    // create printout message:
    final String printout = (message != null && message.length() > 0) ?
      (signature + " [" + message + "]") : signature;
    // check class & method/field signature, if it is really existent (in classpath), but we don't really load the class into JVM:
    final ClassSignatureLookup c;
    try {
      c = getClassFromClassLoader(clazz, failOnUnresolvableSignatures);
      if (c == null) {
        logWarn(String.format(Locale.ENGLISH,
          "The class '%s' referenced in a signature cannot be loaded, ignoring signature: %s",
          clazz, signature
        ));
        return;
      }
    } catch (ClassNotFoundException cnfe) {
      throw new ParseException(cnfe.getMessage());
    }
    if (method != null) {
      assert field == null;
      // list all methods with this signature:
      boolean found = false;
      for (final Method m : c.methods) {
        if (m.getName().equals(method.getName()) && Arrays.equals(m.getArgumentTypes(), method.getArgumentTypes())) {
          found = true;
          forbiddenMethods.put(c.className + '\000' + m, printout);
          // don't break when found, as there may be more covariant overrides!
        }
      }
      if (!found) {
        throw new ParseException("No method found with following signature: " + signature);
      }
    } else if (field != null) {
      assert method == null;
      if (!c.fields.contains(field)) {
        throw new ParseException("No field found with following name: " + signature);
      }
      forbiddenFields.put(c.className + '\000' + field, printout);
    } else {
      assert field == null && method == null;
      // only add the signature as class name
      forbiddenClasses.put(c.className, printout);
    }
  }

  /** Reads a list of bundled API signatures from classpath. */
  public final void parseBundledSignatures(String name, String jdkTargetVersion) throws IOException,ParseException {
    if (!name.matches("[A-Za-z0-9\\-\\.]+")) {
      throw new ParseException("Invalid bundled signature reference: " + name);
    }
    InputStream in = this.getClass().getResourceAsStream("signatures/" + name + ".txt");
    // automatically expand the compiler version in here (for jdk-* signatures without version):
    if (in == null && jdkTargetVersion != null && name.startsWith("jdk-") && !name.matches(".*?\\-\\d\\.\\d")) {
      name = name + "-" + jdkTargetVersion;
      in = this.getClass().getResourceAsStream("signatures/" + name + ".txt");
    }
    if (in == null) {
      throw new FileNotFoundException("Bundled signatures resource not found: " + name);
    }
    parseSignaturesFile(in, true);
  }
 
  /** Reads a list of API signatures. Closes the Reader when done (on Exception, too)! */
  public final void parseSignaturesFile(InputStream in) throws IOException,ParseException {
    parseSignaturesFile(in, false);
  }
 
  /** Reads a list of API signatures from a String. */
  public final void parseSignaturesString(String signatures) throws IOException,ParseException {
    parseSignaturesFile(new StringReader(signatures), false);
  }
 
  private void parseSignaturesFile(InputStream in, boolean allowBundled) throws IOException,ParseException {
    parseSignaturesFile(new InputStreamReader(in, "UTF-8"), allowBundled);
  }

  private static final String BUNDLED_PREFIX = "@includeBundled ";
  private static final String DEFAULT_MESSAGE_PREFIX = "@defaultMessage ";

  private void parseSignaturesFile(Reader reader, boolean allowBundled) throws IOException,ParseException {
    final BufferedReader r = new BufferedReader(reader);
    try {
      String line, defaultMessage = null;
      while ((line = r.readLine()) != null) {
        line = line.trim();
        if (line.length() == 0 || line.startsWith("#"))
          continue;
        if (line.startsWith("@")) {
          if (allowBundled && line.startsWith(BUNDLED_PREFIX)) {
            final String name = line.substring(BUNDLED_PREFIX.length()).trim();
            parseBundledSignatures(name, null);
          } else if (line.startsWith(DEFAULT_MESSAGE_PREFIX)) {
            defaultMessage = line.substring(DEFAULT_MESSAGE_PREFIX.length()).trim();
            if (defaultMessage.length() == 0) defaultMessage = null;
          } else {
            throw new ParseException("Invalid line in signature file: " + line);
          }
        } else {
          addSignature(line, defaultMessage);
        }
      }
    } finally {
      r.close();
    }
  }
 
  /** Parses and adds a class from the given stream to the list of classes to check. Closes the stream when parsed (on Exception, too)! */
  public final void addClassToCheck(final InputStream in) throws IOException {
    final ClassReader reader;
    try {
      reader = new ClassReader(in);
    } finally {
      in.close();
    }
    classesToCheck.put(reader.getClassName(), new ClassSignatureLookup(reader, false, true));
  }
 
  public final boolean hasNoSignatures() {
    return forbiddenMethods.isEmpty() && forbiddenClasses.isEmpty() && forbiddenFields.isEmpty() && (!internalRuntimeForbidden);
  }
 
  /** Parses a class and checks for valid method invocations */
  private int checkClass(final ClassReader reader) {
    final int[] violations = new int[1];
    reader.accept(new ClassVisitor(Opcodes.ASM5) {
      final String className = Type.getObjectType(reader.getClassName()).getClassName();
      String source = null;
     
      ClassSignatureLookup lookupRelatedClass(String internalName) {
        final Type type = Type.getObjectType(internalName);
        if (type.getSort() != Type.OBJECT) {
          return null;
        }
        ClassSignatureLookup c = classesToCheck.get(internalName);
        if (c == null) try {
          // use binary name, so we need to convert:
          c = getClassFromClassLoader(type.getClassName(), failOnMissingClasses);
          if (c == null) {
            logWarn(String.format(Locale.ENGLISH,
              "The referenced class '%s' cannot be loaded. Please fix the classpath!",
              type.getClassName()
            ));
          }
        } catch (ClassNotFoundException cnfe) {
          throw new WrapperRuntimeException(cnfe);
        }
        return c;
      }
     
      private boolean isInternalClass(String className) {
        return className.startsWith("sun.") || className.startsWith("com.sun.") || className.startsWith("com.oracle.") || className.startsWith("jdk."|| className.startsWith("sunw.");
      }
     
      boolean checkClassUse(String internalName) {
        final String printout = forbiddenClasses.get(internalName);
        if (printout != null) {
          logError("Forbidden class/interface/annotation use: " + printout);
          return true;
        }
        if (internalRuntimeForbidden) {
          final String referencedClassName = Type.getObjectType(internalName).getClassName();
          if (isInternalClass(referencedClassName)) {
            final ClassSignatureLookup c = lookupRelatedClass(internalName);
            if (c == null || c.isRuntimeClass) {
              logError(String.format(Locale.ENGLISH,
                "Forbidden class/interface/annotation use: %s [non-public internal runtime class]",
                referencedClassName
              ));
              return true;
            }
          }
        }
        return false;
      }

      private boolean checkClassDefinition(String superName, String[] interfaces) {
        if (superName != null) {
          if (checkClassUse(superName)) {
            return true;
          }
          final ClassSignatureLookup c = lookupRelatedClass(superName);
          if (c != null && checkClassDefinition(c.superName, c.interfaces)) {
            return true;
          }
        }
        if (interfaces != null) {
          for (String intf : interfaces) {
            if (checkClassUse(intf)) {
              return true;
            }
            final ClassSignatureLookup c = lookupRelatedClass(intf);
            if (c != null && checkClassDefinition(c.superName, c.interfaces)) {
              return true;
            }
          }
        }
        return false;
      }
     
      boolean checkType(Type type) {
        while (type != null) {
          switch (type.getSort()) {
            case Type.OBJECT:
              if (checkClassUse(type.getInternalName())) {
                return true;
              }
              final ClassSignatureLookup c = lookupRelatedClass(type.getInternalName());
              return (c != null && checkClassDefinition(c.superName, c.interfaces));
            case Type.ARRAY:
              type = type.getElementType();
              break;
            case Type.METHOD:
              boolean violation = checkType(type.getReturnType());
              for (final Type t : type.getArgumentTypes()) {
                violation |= checkType(t);
              }
              return violation;
            default:
              return false;
          }
        }
        return false;
      }

      boolean checkDescriptor(String desc) {
        return checkType(Type.getType(desc));
      }

      private void reportClassViolation(boolean violation, String where) {
        if (violation) {
          violations[0]++;
          final StringBuilder sb = new StringBuilder("  in ").append(className);
          if (source != null) {
            new Formatter(sb, Locale.ENGLISH).format(" (%s, %s)", source, where).flush();
          } else {
            new Formatter(sb, Locale.ENGLISH).format(" (%s)", where).flush();
          }
          logError(sb.toString());
        }
      }

      @Override
      public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        reportClassViolation(checkClassDefinition(superName, interfaces), "class declaration");
      }

      @Override
      public void visitSource(String source, String debug) {
        this.source = source;
      }
     
      @Override
      public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        if (visible) reportClassViolation(checkDescriptor(desc), "annotation on class declaration");
        return null;
      }

      @Override
      public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
        if (visible) reportClassViolation(checkDescriptor(desc), "type annotation on class declaration");
        return null;
      }
     
      @Override
      public FieldVisitor visitField(final int access, final String name, final String desc, String signature, Object value) {
        return new FieldVisitor(Opcodes.ASM5) {
          {
            // only check signature, if field is not synthetic
            if ((access & Opcodes.ACC_SYNTHETIC) == 0) {
              reportFieldViolation(checkDescriptor(desc), "field declaration");
            }
          }
         
          @Override
          public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            if (visible) reportFieldViolation(checkDescriptor(desc), "annotation on field declaration");
            return null;
          }

          @Override
          public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
            if (visible) reportFieldViolation(checkDescriptor(desc), "type annotation on field declaration");
            return null;
          }
         
          private void reportFieldViolation(boolean violation, String where) {
            if (violation) {
              violations[0]++;
              final StringBuilder sb = new StringBuilder("  in ").append(className);
              if (source != null) {
                new Formatter(sb, Locale.ENGLISH).format(" (%s, %s of '%s')", source, where, name).flush();
              } else {
                new Formatter(sb, Locale.ENGLISH).format(" (%s of '%s')", where, name).flush();
              }
              logError(sb.toString());
            }
          }
        };
      }

      @Override
      public MethodVisitor visitMethod(final int access, final String name, final String desc, String signature, String[] exceptions) {
        return new MethodVisitor(Opcodes.ASM5) {
          private int lineNo = -1;
         
          {
            // only check signature, if method is not synthetic
            if ((access & Opcodes.ACC_SYNTHETIC) == 0) {
              reportMethodViolation(checkDescriptor(desc), "method declaration");
            }
          }
         
          private boolean checkMethodAccess(String owner, Method method) {
            if (checkClassUse(owner)) {
              return true;
            }
            final String printout = forbiddenMethods.get(owner + '\000' + method);
            if (printout != null) {
              logError("Forbidden method invocation: " + printout);
              return true;
            }
            final ClassSignatureLookup c = lookupRelatedClass(owner);
            if (c != null && !c.methods.contains(method)) {
              if (c.superName != null && checkMethodAccess(c.superName, method)) {
                return true;
              }
              // JVM spec says: interfaces after superclasses
              if (c.interfaces != null) {
                for (String intf : c.interfaces) {
                  if (intf != null && checkMethodAccess(intf, method)) {
                    return true;
                  }
                }
              }
            }
            return false;
          }
         
          private boolean checkFieldAccess(String owner, String field) {
            if (checkClassUse(owner)) {
              return true;
            }
            final String printout = forbiddenFields.get(owner + '\000' + field);
            if (printout != null) {
              logError("Forbidden field access: " + printout);
              return true;
            }
            final ClassSignatureLookup c = lookupRelatedClass(owner);
            if (c != null && !c.fields.contains(field)) {
              if (c.interfaces != null) {
                for (String intf : c.interfaces) {
                  if (intf != null && checkFieldAccess(intf, field)) {
                    return true;
                  }
                }
              }
              // JVM spec says: superclasses after interfaces
              if (c.superName != null && checkFieldAccess(c.superName, field)) {
                return true;
              }
            }
            return false;
          }

          private boolean checkHandle(Handle handle) {
            switch (handle.getTag()) {
              case Opcodes.H_GETFIELD:
              case Opcodes.H_PUTFIELD:
              case Opcodes.H_GETSTATIC:
              case Opcodes.H_PUTSTATIC:
                return checkFieldAccess(handle.getOwner(), handle.getName());
              case Opcodes.H_INVOKEVIRTUAL:
              case Opcodes.H_INVOKESTATIC:
              case Opcodes.H_INVOKESPECIAL:
              case Opcodes.H_NEWINVOKESPECIAL:
              case Opcodes.H_INVOKEINTERFACE:
                return checkMethodAccess(handle.getOwner(), new Method(handle.getName(), handle.getDesc()));
            }
            return false;
          }
         
          private boolean checkConstant(Object cst) {
            if (cst instanceof Type) {
              if (checkType((Type) cst)) {
                return true;
              }
            } else if (cst instanceof Handle) {
              if (checkHandle((Handle) cst)) {
                return true;
              }
            }
            return false;
          }

          @Override
          public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
            reportMethodViolation(checkMethodAccess(owner, new Method(name, desc)), "method body");
          }
         
          @Override
          public void visitFieldInsn(int opcode, String owner, String name, String desc) {
            reportMethodViolation(checkFieldAccess(owner, name), "method body");
          }
         
          @Override
          public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            if (visible) reportMethodViolation(checkDescriptor(desc), "annotation on method declaration");
            return null;
          }

          @Override
          public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
            if (visible) reportMethodViolation(checkDescriptor(desc), "parameter annotation on method declaration");
            return null;
          }

          @Override
          public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
            if (visible) reportMethodViolation(checkDescriptor(desc), "type annotation on method declaration");
            return null;
          }

          @Override
          public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
            if (visible) reportMethodViolation(checkDescriptor(desc), "annotation in method body");
            return null;
          }

          @Override
          public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) {
            if (visible) reportMethodViolation(checkDescriptor(desc), "annotation in method body");
            return null;
          }
         
          @Override
          public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
            if (visible) reportMethodViolation(checkDescriptor(desc), "annotation in method body");
            return null;
          }
         
          @Override
          public void visitTypeInsn(int opcode, String type) {
            if (opcode == Opcodes.ANEWARRAY) {
              reportMethodViolation(checkType(Type.getObjectType(type)), "method body");
            }
          }
         
          @Override
          public void visitMultiANewArrayInsn(String desc, int dims) {
            reportMethodViolation(checkDescriptor(desc), "method body");
          }
         
          @Override
          public void visitLdcInsn(Object cst) {
            reportMethodViolation(checkConstant(cst), "method body");
          }
         
          @Override
          public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
            reportMethodViolation(checkHandle(bsm), "method body");
            for (final Object cst : bsmArgs) {
              reportMethodViolation(checkConstant(cst), "method body");
            }
          }
         
          private String getHumanReadableMethodSignature() {
            final Type[] args = Type.getType(desc).getArgumentTypes();
            final StringBuilder sb = new StringBuilder(name).append('(');
            boolean comma = false;
            for (final Type t : args) {
              if (comma) sb.append(',');
              sb.append(t.getClassName());
              comma = true;
            }
            sb.append(')');
            return sb.toString();
          }

          private void reportMethodViolation(boolean violation, String where) {
            if (violation) {
              violations[0]++;
              final StringBuilder sb = new StringBuilder("  in ").append(className);
              if (source != null) {
                if (lineNo >= 0) {
                  new Formatter(sb, Locale.ENGLISH).format(" (%s:%d)", source, lineNo).flush();
                } else {
                  new Formatter(sb, Locale.ENGLISH).format(" (%s, %s of '%s')", source, where, getHumanReadableMethodSignature()).flush();
                }
              } else {
                new Formatter(sb, Locale.ENGLISH).format(" (%s of '%s')", where, getHumanReadableMethodSignature()).flush();
              }
              logError(sb.toString());
            }
          }
         
          @Override
          public void visitLineNumber(int lineNo, Label start) {
            this.lineNo = lineNo;
          }
        };
      }
    }, ClassReader.SKIP_FRAMES);
    return violations[0];
  }
 
  public final void run() throws ForbiddenApiException {
    int errors = 0;
    try {
      for (final ClassSignatureLookup c : classesToCheck.values()) {
        errors += checkClass(c.getReader());
      }
    } catch (WrapperRuntimeException wre) {
      Throwable cause = wre.getCause();
      throw new ForbiddenApiException("Check for forbidden API calls failed: " + cause.toString());
    }
   
    final String message = String.format(Locale.ENGLISH,
        "Scanned %d (and %d related) class file(s) for forbidden API invocations (in %.2fs), %d error(s).",
        classesToCheck.size(), classesToCheck.isEmpty() ? 0 : classpathClassCache.size(), (System.currentTimeMillis() - start) / 1000.0, errors);
    if (errors > 0) {
      logError(message);
      throw new ForbiddenApiException("Check for forbidden API calls failed, see log.");
    } else {
      logInfo(message);
    }
  }
 
}
TOP

Related Classes of de.thetaphi.forbiddenapis.Checker

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.