edu.umd.cs.findbugs.detect.FindRoughConstants Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spotbugs Show documentation
Show all versions of spotbugs Show documentation
SpotBugs: Because it's easy!
/*
* FindBugs - Find Bugs in Java programs
* Copyright (C) 2003-2007 University of Maryland
*
* 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 edu.umd.cs.findbugs.detect;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.HashSet;
import java.util.Set;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantDouble;
import org.apache.bcel.classfile.ConstantFloat;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.JavaClass;
import edu.umd.cs.findbugs.BugAccumulator;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import edu.umd.cs.findbugs.ba.ClassContext;
public class FindRoughConstants extends BytecodeScanningDetector {
static class BadConstant {
double base;
double factor;
String replacement;
double value;
int basePriority;
Set approxSet = new HashSet<>();
BadConstant(double base, double factor, String replacement, int basePriority) {
this.base = base;
this.factor = factor;
this.value = this.base * this.factor;
this.replacement = replacement;
this.basePriority = basePriority;
BigDecimal valueBig = BigDecimal.valueOf(value);
BigDecimal baseBig = BigDecimal.valueOf(base);
BigDecimal factorBig = BigDecimal.valueOf(factor);
for (int prec = 0; prec < 14; prec++) {
addApprox(baseBig.round(new MathContext(prec, RoundingMode.FLOOR)).multiply(factorBig));
addApprox(baseBig.round(new MathContext(prec, RoundingMode.CEILING)).multiply(factorBig));
addApprox(valueBig.round(new MathContext(prec, RoundingMode.FLOOR)));
addApprox(valueBig.round(new MathContext(prec, RoundingMode.CEILING)));
}
}
@SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY")
public boolean exact(Number candidate) {
if (candidate instanceof Double) {
return candidate.doubleValue() == value;
}
return candidate.floatValue() == (float) value;
}
public double diff(double candidate) {
return Math.abs(value - candidate) / value;
}
public boolean equalPrefix(Number candidate) {
return approxSet.contains(candidate);
}
@SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY")
private void addApprox(BigDecimal roundFloor) {
double approxDouble = roundFloor.doubleValue();
if (approxDouble != value && Math.abs(approxDouble - value) / value < 0.001) {
approxSet.add(approxDouble);
}
float approxFloat = roundFloor.floatValue();
if (Math.abs(approxFloat - value) / value < 0.001) {
approxSet.add(approxFloat);
approxSet.add((double) approxFloat);
}
}
}
private static final BadConstant[] badConstants = new BadConstant[] {
new BadConstant(Math.PI, 1, "Math.PI", HIGH_PRIORITY),
new BadConstant(Math.PI, 1 / 2.0, "Math.PI/2", NORMAL_PRIORITY),
new BadConstant(Math.PI, 1 / 3.0, "Math.PI/3", LOW_PRIORITY),
new BadConstant(Math.PI, 1 / 4.0, "Math.PI/4", LOW_PRIORITY),
new BadConstant(Math.PI, 2, "2*Math.PI", NORMAL_PRIORITY),
new BadConstant(Math.E, 1, "Math.E", LOW_PRIORITY)
};
private final BugAccumulator bugAccumulator;
private BugInstance lastBug;
private int lastPriority;
public FindRoughConstants(BugReporter bugReporter) {
this.bugAccumulator = new BugAccumulator(bugReporter);
}
@Override
public void visitClassContext(ClassContext classContext) {
if (hasInterestingConstant(classContext.getJavaClass().getConstantPool())) {
super.visitClassContext(classContext);
}
}
@Override
public void visitAfter(JavaClass obj) {
bugAccumulator.reportAccumulatedBugs();
}
@Override
public void sawOpcode(int seen) {
if (seen == Const.LDC || seen == Const.LDC_W || seen == Const.LDC2_W) {
Constant c = getConstantRefOperand();
if (c instanceof ConstantFloat) {
checkConst(((ConstantFloat) c).getBytes());
} else if (c instanceof ConstantDouble) {
checkConst(((ConstantDouble) c).getBytes());
}
return;
}
// Lower priority if the constant is put into array immediately or after the boxing:
// this is likely to be just similar number in some predefined dataset (like lookup table)
if (seen == Const.INVOKESTATIC && lastBug != null) {
if (getNextOpcode() == Const.AASTORE
&& getNameConstantOperand().equals("valueOf")
&& (getClassConstantOperand().equals("java/lang/Double") || getClassConstantOperand().equals(
"java/lang/Float"))) {
lastBug = ((BugInstance) lastBug.clone());
lastBug.setPriority(lastPriority + 1);
bugAccumulator.forgetLastBug();
bugAccumulator.accumulateBug(lastBug, this);
}
}
lastBug = null;
}
private boolean hasInterestingConstant(ConstantPool cp) {
for (Constant constant : cp.getConstantPool()) {
if (constant instanceof ConstantFloat) {
float val = ((ConstantFloat) constant).getBytes();
if (isInteresting(val, val)) {
return true;
}
}
if (constant instanceof ConstantDouble) {
double val = ((ConstantDouble) constant).getBytes();
if (isInteresting(val, val)) {
return true;
}
}
}
return false;
}
private boolean isInteresting(Number constValue, double candidate) {
for (BadConstant badConstant : badConstants) {
if (getPriority(badConstant, constValue, candidate) < IGNORE_PRIORITY) {
return true;
}
}
return false;
}
private int getPriority(BadConstant badConstant, Number constValue, double candidate) {
if (badConstant.exact(constValue)) {
return IGNORE_PRIORITY;
}
double diff = badConstant.diff(candidate);
if (diff > 1e-3) {
return IGNORE_PRIORITY;
}
if (badConstant.equalPrefix(constValue)) {
return diff > 1e-4 ? badConstant.basePriority + 1 : diff < 1e-6 ? badConstant.basePriority - 1 : badConstant.basePriority;
}
if (diff > 1e-7) {
return IGNORE_PRIORITY;
}
return badConstant.basePriority + 1;
}
private void checkConst(Number constValue) {
double candidate = constValue.doubleValue();
if (Double.isNaN(candidate) || Double.isInfinite(candidate)) {
return;
}
for (BadConstant badConstant : badConstants) {
int priority = getPriority(badConstant, constValue, candidate);
if (getNextOpcode() == Const.FASTORE || getNextOpcode() == Const.DASTORE) {
priority++;
}
if (priority < IGNORE_PRIORITY) {
lastPriority = priority;
lastBug = new BugInstance(this, "CNT_ROUGH_CONSTANT_VALUE", priority).addClassAndMethod(this)
.addString(constValue.toString()).addString(badConstant.replacement);
bugAccumulator.accumulateBug(lastBug, this);
return;
}
}
}
}