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

src.main.java.com.mebigfatguy.fbcontrib.detect.InvalidConstantArgument Maven / Gradle / Ivy

Go to download

An auxiliary findbugs.sourceforge.net plugin for java bug detectors that fall outside the narrow scope of detectors to be packaged with the product itself.

The newest version!
/*
 * fb-contrib - Auxiliary detectors for Java programs
 * Copyright (C) 2005-2019 Dave Brosius
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.mebigfatguy.fbcontrib.detect;

import java.awt.Adjustable;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JOptionPane;
import javax.swing.border.BevelBorder;
import javax.swing.border.EtchedBorder;

import org.apache.bcel.Const;
import org.apache.bcel.classfile.Method;

import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.SignatureUtils;
import com.mebigfatguy.fbcontrib.utils.ToString;
import com.mebigfatguy.fbcontrib.utils.UnmodifiableList;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.ba.ClassContext;

/**
 * Looks for jdk method calls where a parameter expects a constant value,
 * because the api was created before enums. Reports values that are not
 * considered valid values, and may cause problems with use.
 */
public class InvalidConstantArgument extends BytecodeScanningDetector {

    private static final List PATTERNS = UnmodifiableList.create(
            // @formatter:off
            new InvalidPattern(
                    "javax/swing/JOptionPane#showMessageDialog\\(Ljava/awt/Component;Ljava/lang/Object;Ljava/lang/String;I\\)V",
                    ParameterInfo.createIntegerParameterInfo(0, false, JOptionPane.ERROR_MESSAGE,
                            JOptionPane.INFORMATION_MESSAGE, JOptionPane.PLAIN_MESSAGE, JOptionPane.WARNING_MESSAGE)),
            new InvalidPattern("javax/swing/BorderFactory#createBevelBorder\\(I.*\\)Ljavax/swing/border/Border;",
                    ParameterInfo.createIntegerParameterInfo(0, true, BevelBorder.LOWERED, BevelBorder.RAISED)),
            new InvalidPattern("javax/swing/BorderFactory#createEtchedBorder\\(I.*\\)Ljavax/swing/border/Border;",
                    ParameterInfo.createIntegerParameterInfo(0, true, EtchedBorder.LOWERED, EtchedBorder.RAISED)),
            new InvalidPattern("javax/swing/JScrollBar#\\\\(I.*\\)V",
                    ParameterInfo.createIntegerParameterInfo(0, true, Adjustable.HORIZONTAL, Adjustable.VERTICAL)),
            // TODO: Until travis-ci issue uncovered, add type param as integer on next
            // three.
            new InvalidPattern("java/lang/Thread#setPriority\\(I\\)V",
                    new ParameterInfo(0, true,
                            Range.createIntegerRange(Thread.MIN_PRIORITY, Thread.MAX_PRIORITY))),
            new InvalidPattern("java/math/BigDecimal#divide\\(Ljava/math/BigDecimal;.*I\\)Ljava/math/BigDecimal;",
                    new ParameterInfo(0, false,
                            Range.createIntegerRange(BigDecimal.ROUND_UP, BigDecimal.ROUND_UNNECESSARY))),
            new InvalidPattern("java/math/BigDecimal#setScale\\(II\\)Ljava/math/BigDecimal;",
                    new ParameterInfo(0, false,
                            Range.createIntegerRange(BigDecimal.ROUND_UP, BigDecimal.ROUND_UNNECESSARY))),
            new InvalidPattern("java/sql/Connection#createStatement\\(II\\)Ljava/sql/Statement;",
                    ParameterInfo.createIntegerParameterInfo(0, true, ResultSet.TYPE_FORWARD_ONLY,
                            ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.TYPE_SCROLL_SENSITIVE)),
            new InvalidPattern("java/sql/Connection#createStatement\\(III?\\)Ljava/sql/Statement;",
                    ParameterInfo.createIntegerParameterInfo(0, true, ResultSet.TYPE_FORWARD_ONLY,
                            ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.TYPE_SCROLL_SENSITIVE),
                    ParameterInfo.createIntegerParameterInfo(1, true, ResultSet.CONCUR_READ_ONLY,
                            ResultSet.CONCUR_UPDATABLE)),

            new InvalidPattern(
                    "java/sql/Connection#prepare[^\\(]+\\(Ljava/lang/String;III?\\)Ljava/sql/PreparedStatement;",
                    ParameterInfo.createIntegerParameterInfo(1, true, ResultSet.TYPE_FORWARD_ONLY,
                            ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.TYPE_SCROLL_SENSITIVE),
                    ParameterInfo.createIntegerParameterInfo(2, true, ResultSet.CONCUR_READ_ONLY,
                            ResultSet.CONCUR_UPDATABLE))
    // @formatter:on
    );

    private BugReporter bugReporter;
    private OpcodeStack stack;

    /**
     * constructs a ICA detector given the reporter to report bugs on
     *
     * @param bugReporter the sync of bug reports
     */
    public InvalidConstantArgument(BugReporter bugReporter) {
        this.bugReporter = bugReporter;
    }

    /**
     * overrides the visitor to initialize the opcode stack
     *
     * @param classContext the context of the currently parsed class
     */
    @Override
    public void visitClassContext(ClassContext classContext) {
        try {
            stack = new OpcodeStack();
            super.visitClassContext(classContext);
        } finally {
            stack = null;
        }
    }

    /**
     * overrides the visitor to reset the opcode stack
     *
     * @param obj the currently parsed method
     */
    @Override
    public void visitMethod(Method obj) {
        stack.resetForMethodEntry(this);
        super.visitMethod(obj);
    }

    @Override
    public void sawOpcode(int seen) {
        try {
            switch (seen) {
            case Const.INVOKESPECIAL:
            case Const.INVOKESTATIC:
            case Const.INVOKEINTERFACE:
            case Const.INVOKEVIRTUAL:
                String sig = getSigConstantOperand();
                String mInfo = getClassConstantOperand() + '#' + getNameConstantOperand() + sig;
                for (InvalidPattern entry : PATTERNS) {
                    Matcher m = entry.getPattern().matcher(mInfo);
                    if (m.matches()) {
                        for (ParameterInfo info : entry.getParmInfo()) {
                            int parmOffset = info.fromStart
                                    ? SignatureUtils.getNumParameters(sig) - info.parameterOffset - 1
                                    : info.parameterOffset;
                            if (stack.getStackDepth() > parmOffset) {
                                OpcodeStack.Item item = stack.getStackItem(parmOffset);

                                Comparable cons = (Comparable) item.getConstant();
                                if (!info.isValid(cons)) {
                                    int badParm = 1 + (info.fromStart ? info.parameterOffset
                                            : SignatureUtils.getNumParameters(sig) - info.parameterOffset - 1);
                                    bugReporter.reportBug(
                                            new BugInstance(this, BugType.ICA_INVALID_CONSTANT_ARGUMENT.name(),
                                                    NORMAL_PRIORITY).addClass(this).addMethod(this).addSourceLine(this)
                                                            .addString("Parameter " + badParm));
                                    break;
                                }
                            }
                        }
                        break;
                    }
                }
                break;
            }
        } finally {
            stack.sawOpcode(this, seen);
        }
    }

    static class InvalidPattern {
        private Pattern pattern;
        List> parmInfo;

        public InvalidPattern(String invalidPattern, ParameterInfo... parameterInfo) {
            pattern = Pattern.compile(invalidPattern);
            parmInfo = Arrays.asList(parameterInfo);
        }

        public Pattern getPattern() {
            return pattern;
        }

        public List> getParmInfo() {
            return parmInfo;
        }

        @Override
        public String toString() {
            return ToString.build(this);
        }
    }

    /**
     * holds information about parameters that expect constant values that should
     * have been enums but were created pre enums. It specifies the legal values,
     * and what offset from the start or end of the method the parm is
     */
    static class ParameterInfo> {
        final int parameterOffset;
        final boolean fromStart;
        private Set validValues;
        private Range range;

        @SafeVarargs
        public ParameterInfo(int offset, boolean start, T... values) {
            parameterOffset = offset;
            fromStart = start;
            validValues = new HashSet<>(Arrays.asList(values));
            range = null;
        }

        public ParameterInfo(int offset, boolean start, Range rng) {
            parameterOffset = offset;
            fromStart = start;
            validValues = null;
            range = rng;
        }

        public static ParameterInfo createIntegerParameterInfo(int offset, boolean start, int... values) {
            ParameterInfo info = new ParameterInfo<>(offset, start);
            for (int v : values) {
                info.validValues.add(Integer.valueOf(v));
            }

            return info;
        }

        public boolean isValid(Comparable o) {
            if (o == null) {
                return true;
            }

            if (validValues != null) {
                return validValues.contains(o);
            }

            return (o.compareTo(range.getFrom()) >= 0) && (o.compareTo(range.getTo()) <= 0);
        }

        @Override
        public String toString() {
            return ToString.build(this);
        }
    }

    static class Range> {
        T from;
        T to;

        public Range(T f, T t) {
            from = f;
            to = t;
        }

        public static Range createIntegerRange(int f, int t) {
            return new Range<>(Integer.valueOf(f), Integer.valueOf(t));
        }

        public T getFrom() {
            return from;
        }

        public T getTo() {
            return to;
        }

        @Override
        public String toString() {
            return ToString.build(this);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy