src.main.java.com.mebigfatguy.fbcontrib.detect.InvalidConstantArgument Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fb-contrib Show documentation
Show all versions of fb-contrib Show documentation
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.
/*
* fb-contrib - Auxiliary detectors for Java programs
* Copyright (C) 2005-2018 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.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 INVOKESPECIAL:
case INVOKESTATIC:
case INVOKEINTERFACE:
case 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> {
private int parameterOffset;
private 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);
}
}
}