All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.caffinitas.bcverify.BCVerifier Maven / Gradle / Ivy

The newest version!
/*
 *      Copyright (C) 2014 Robert Stupp, Koeln, Germany, robert-stupp.de
 *
 *   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 org.caffinitas.bcverify;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BCVerifier
{
    static final Logger LOGGER = LoggerFactory.getLogger(BCVerifier.class);

    private final List allow = new ArrayList();
    private final List reject = new ArrayList();
    private boolean acceptDefault = true;

    private static final Pattern DOT = Pattern.compile("\\.");

    public void setAcceptDefault(boolean acceptDefault)
    {
        this.acceptDefault = acceptDefault;
    }

    public void addAllow(Pattern... patterns)
    {
        Collections.addAll(allow, patterns);
    }

    public void addAllow(String... patterns)
    {
        for (String pattern : patterns)
        {
            allow.add(Pattern.compile(pattern));
        }
    }

    public void addAllowPackages(String... packages)
    {
        for (String pkg : packages)
        {
            allow.add(Pattern.compile(DOT.matcher(pkg).replaceAll("\\.")+"\\..+"));
        }
    }

    public void addAllowClasses(String... classes)
    {
        for (String cls : classes)
        {
            allow.add(Pattern.compile(DOT.matcher(cls).replaceAll("\\.")));
        }
    }

    public void addReject(Pattern... patterns)
    {
        Collections.addAll(reject, patterns);
    }

    public void addReject(String... patterns)
    {
        for (String pattern : patterns)
        {
            reject.add(Pattern.compile(pattern));
        }
    }

    public void addRejectPackages(String... packages)
    {
        for (String pkg : packages)
        {
            reject.add(Pattern.compile(DOT.matcher(pkg).replaceAll("\\\\.")+"\\..+"));
        }
    }

    public void addRejectClasses(String... classes)
    {
        for (String cls : classes)
        {
            reject.add(Pattern.compile(DOT.matcher(cls).replaceAll("\\\\.")));
        }
    }

    public VerifyResult scan(File file) throws IOException
    {
        if (file == null)
        {
            return null;
        }
        LOGGER.debug("scan({})", file);
        VerifyResult result = new VerifyResult();
        scan(result, new StringBuilder(), file);
        return result;
    }

    private void scan(VerifyResult result, StringBuilder path, File file) throws IOException
    {
        if (file == null)
        {
            return;
        }
        int l = path.length();
        if (path.length() > 0)
        {
            path.append('.');
        }
        if (file.isDirectory())
        {
            path.append(file.getName());
            File[] files = file.listFiles();
            if (files != null)
            {
                for (File f : files)
                {
                    scan(result, path, f);
                }
            }
        } else if (file.isFile())
        {
            if (file.getName().endsWith(".class"))
            {
                path.append(file.getName(), 0, file.getName().length() - 6);
                scanClass(result, path.toString(), file);
            }
        }
        path.setLength(l);
    }

    public VerifyResult scan(URL url) throws IOException
    {
        if (url == null)
        {
            return null;
        }
        LOGGER.debug("scan({})", url);
        VerifyResult result = new VerifyResult();
        try (InputStream input = url.openConnection().getInputStream())
        {
            JarInputStream jarInputStream = new JarInputStream(input);
            for (JarEntry entry; (entry = jarInputStream.getNextJarEntry()) != null; )
            {
                String name = entry.getName();
                if (name.endsWith(".class"))
                {
                    scanClass(result, name.substring(0, name.length() - 6).replace('/', '.'), jarInputStream);
                }
            }
        }
        return result;
    }

    public void scanClass(VerifyResult result, String clazz, File file) throws IOException
    {
        if (file == null)
        {
            return;
        }
        LOGGER.debug("scanClass({}, {})", clazz, file);
        try (FileInputStream fis = new FileInputStream(file))
        {
            scanClass(result, clazz, fis);
        }
    }

    public void scanClass(VerifyResult result, String clazz, URL url) throws IOException
    {
        if (url == null)
        {
            return;
        }
        LOGGER.debug("scanClass({}, {})", clazz, url);
        try (InputStream input = url.openConnection().getInputStream())
        {
            scanClass(result, clazz, input);
        }
    }

    public void scanClass(VerifyResult result, String clazz, byte[] classData)
    {
        if (classData == null)
        {
            return;
        }
        LOGGER.debug("scanClass(clazz, byte[])", clazz);
        ClassReader classReader = new ClassReader(classData);
        scanClass(result, clazz, classReader);
    }

    public void scanClass(VerifyResult result, String clazz, InputStream classData) throws IOException
    {
        if (classData == null)
        {
            return;
        }
        LOGGER.debug("scanClass({}, InputStream)", clazz);
        ClassReader classReader = new ClassReader(classData);
        scanClass(result, clazz, classReader);
    }

    public void scanClass(final VerifyResult result, final String clazz, ClassReader classReader)
    {
        if (classReader == null)
        {
            return;
        }
        LOGGER.debug("scanClass({}, ClassReader)", clazz);

        final StringBuilder currentSource = new StringBuilder();
        final StringBuilder currentClass = new StringBuilder();
        final StringBuilder currentMethod = new StringBuilder();
        final AtomicInteger currentLine = new AtomicInteger();

        final MethodVisitor methodVisitor = new MethodVisitor(Opcodes.ASM5)
        {

            @Override public void visitLineNumber(int line, Label start)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitLineNumber line={} start={}", line, start);
                }
                currentLine.set(line);
                super.visitLineNumber(line, start);
            }

            @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitMethodInsn opcode={} owner={} name={} desc={} itf={}", opcode, owner, name, desc, itf);
                }
                Type t = Type.getObjectType(owner);
                checkType(result, clazz, t, "method call against %s.%s%s in method %s.%s (%s:%s)", t.getClassName(), name, desc, currentClass, currentMethod,
                    currentSource, currentLine);
                super.visitMethodInsn(opcode, owner, name, desc, itf);
            }

            @Override public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitInvokeDynamicInsn name={} desc={} bsm={} bsmArgs={}", name, desc, bsm, bsmArgs);
                }
                Type t = Type.getType(desc);
                checkType(result, clazz, t, "method call against %s.%s%s in method %s.%s (%s:%s)", t.getClassName(), name, desc, currentClass, currentMethod,
                    currentSource, currentLine);
                super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
            }

            @Override public void visitTypeInsn(int opcode, String type)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitTypeInsn opcode={} type={}", opcode, type);
                }
                Type t = Type.getObjectType(type);
                checkType(result, clazz, t, "type in method %s.%s (%s:%s)", currentClass, currentMethod, currentSource, currentLine);
                super.visitTypeInsn(opcode, type);
            }

            @Override public void visitFieldInsn(int opcode, String owner, String name, String desc)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitFieldInsn opcode={} owner={} name={} desc={}", opcode, owner, name, desc);
                }
                Type type = Type.getType(desc);
                checkType(result, clazz, type, "field %s in method %s.%s (%s:%s)", name, currentClass, currentMethod, currentSource, currentLine);
                super.visitFieldInsn(opcode, owner, name, desc);
            }

            @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER
                        .trace("visitLocalVariable name={} desc={} signature={} start={} end={} index={}", name, desc, signature, start, end, index);
                }
                Type type = Type.getType(desc);
                checkType(result, clazz, type, "local variable %s in method %s.%s (%s:%s)", name, currentClass, currentMethod, currentSource, currentLine);
                super.visitLocalVariable(name, desc, signature, start, end, index);
            }

            @Override public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index,
                                                                            String desc, boolean visible)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitLocalVariableAnnotation typeRef={} typePath={} start={} end={} index={} desc={} visible={}", typeRef, typePath,
                        start, end, index, desc, visible);
                }
                return super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, desc, visible);
            }

        };

        ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM5)
        {
            @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visit version={} access={} name={} signature={} superName={} interfaces={}", version, access, name, signature,
                        superName, interfaces);
                }
                currentClass.setLength(0);
                currentClass.append(Type.getObjectType(name).getClassName());
                super.visit(version, access, name, signature, superName, interfaces);
            }

            @Override public void visitSource(String source, String debug)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitSource source={} debug={}", source, debug);
                }
                currentSource.setLength(0);
                currentSource.append(source);
                super.visitSource(source, debug);
            }

            @Override public void visitOuterClass(String owner, String name, String desc)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitOuterClass owner={} name={} desc={}", owner, name, desc);
                }
                super.visitOuterClass(owner, name, desc);
                // TODO maybe resolve outer class and scan it starting here
            }

            @Override public void visitInnerClass(String name, String outerName, String innerName, int access)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitInnerClass name={} outerName={} innerName={} access={}", name, outerName, innerName, access);
                }
                super.visitInnerClass(name, outerName, innerName, access);
                // TODO maybe resolve inner class and scan it starting here
            }

            @Override public void visitAttribute(Attribute attr)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitAttribute attr={}", attr);
                }
                Type type = Type.getType(attr.type);
                checkType(result, clazz, type, "attr type %s", currentClass);
                super.visitAttribute(attr);
            }

            @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitField access={} name={} desc={} signature={} value={}", access, name, desc, signature, value);
                }
                Type type = Type.getType(desc);
                checkType(result, clazz, type, "field type %s.%s", currentClass, name);
                return super.visitField(access, name, desc, signature, value);
            }

            @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitMethod access={} name={} desc={} signature={} exceptions={}", access, name, desc, signature, exceptions);
                }
                Type returnType = Type.getReturnType(desc);
                Type[] argTypes = Type.getArgumentTypes(desc);
                checkType(result, clazz, returnType, "return type %s.%s%s", currentClass, name, desc);
                for (Type argType : argTypes)
                {
                    checkType(result, clazz, argType, "method parameter type %s.%s%s", currentClass, name, desc);
                }
                super.visitMethod(access, name, desc, signature, exceptions);
                currentMethod.setLength(0);
                currentMethod.append(name);
                currentMethod.append(desc);
                return methodVisitor;
            }

            @Override public void visitEnd()
            {
                if (LOGGER.isTraceEnabled())
                {
                    LOGGER.trace("visitEnd");
                }
                super.visitEnd();
            }
        };
        classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
    }

    private void checkType(VerifyResult result, String clazz, Type type, String fmt, Object... params)
    {
        if (type.getSort() == Type.ARRAY)
        {
            type = type.getElementType();
        }
        if (type.getSort() == Type.OBJECT)
        {
            checkClass(result, type.getClassName(), type, fmt, params);
        }
    }

    public void checkClass(VerifyResult result, String className, Type type, String fmt, Object... params)
    {
        if (LOGGER.isDebugEnabled())
        {
            LOGGER.debug("{}: checkClass {}: {}", className, type.getClassName(), String.format(fmt, params));
        }

        VerifyResult.ClassResult r = result.checked(className);
        if (r != null)
        {
            if (!r.isAccept())
                r.addReason(String.format(fmt, params));
            return;
        }

        Pattern bestMatch = null;
        int bestMatchLen = 0;
        Boolean accept = null;
        for (Pattern pattern : allow)
        {
            Matcher m = pattern.matcher(className);
            if (m.matches())
            {
                int ml = pattern.toString().length();
                if (ml > bestMatchLen)
                {
                    bestMatch = pattern;
                    bestMatchLen = ml;
                    accept = true;
                }
            }
        }
        for (Pattern pattern : reject)
        {
            Matcher m = pattern.matcher(className);
            if (m.matches())
            {
                int ml = pattern.toString().length();
                if (ml > bestMatchLen)
                {
                    bestMatch = pattern;
                    bestMatchLen = ml;
                    accept = false;
                }
            }
        }
        if (accept == null)
        {
            accept = acceptDefault;
        }

        String reason = accept ? "" : String.format(fmt, params);
        result.add(new VerifyResult.ClassResult(className, accept, reason));

        LOGGER.debug("{} => {} {}", className, accept, bestMatch);
    }

    private static int matchLength(Matcher m)
    {
        return m.end() - m.start();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy