diff --git a/etc/bugrank.txt b/etc/bugrank.txt index 3ffc3b79..00fbeff3 100644 --- a/etc/bugrank.txt +++ b/etc/bugrank.txt @@ -61,6 +61,7 @@ +0 BugPattern FII_AVOID_SIZE_ON_COLLECTED_STREAM +0 BugPattern FII_COMBINE_FILTERS +1 BugPattern FII_USE_ANY_MATCH ++0 BugPattern FII_USE_COPYCONSTRUCTOR +0 BugPattern FII_USE_FIND_FIRST +0 BugPattern FII_USE_FUNCTION_IDENTITY +0 BugPattern FII_USE_METHOD_REFERENCE diff --git a/etc/findbugs.xml b/etc/findbugs.xml index 2d3006da..44ec552d 100755 --- a/etc/findbugs.xml +++ b/etc/findbugs.xml @@ -326,7 +326,7 @@ - + @@ -650,6 +650,7 @@ + diff --git a/etc/messages.xml b/etc/messages.xml index ef6de83a..1bbbd3a8 100755 --- a/etc/messages.xml +++ b/etc/messages.xml @@ -6496,6 +6496,25 @@ if (shouldCalcHalting && (calculateHaltingProbability() > 0) { } + + Method calls collect() directly on a stream + Method {1} calls collect() directly on a stream + + + Set s = new HashSet + + + Set s = myArrayList.stream().collect(Collectors.toSet); + + ]]> + + + Method checks for an item in a set with contains, before using add() Method {1} checks for an item in a set with contains, before using add() diff --git a/src/main/java/com/mebigfatguy/fbcontrib/detect/FunctionalInterfaceIssues.java b/src/main/java/com/mebigfatguy/fbcontrib/detect/FunctionalInterfaceIssues.java index 32cbca0d..da7f91a0 100644 --- a/src/main/java/com/mebigfatguy/fbcontrib/detect/FunctionalInterfaceIssues.java +++ b/src/main/java/com/mebigfatguy/fbcontrib/detect/FunctionalInterfaceIssues.java @@ -68,6 +68,7 @@ public class FunctionalInterfaceIssues extends BytecodeScanningDetector { private static final QMethod CONTAINS = new QMethod("contains", SignatureBuilder.SIG_OBJECT_TO_BOOLEAN); private static final QMethod SIZE = new QMethod("size", SignatureBuilder.SIG_VOID_TO_INT); + private static final QMethod STREAM = new QMethod("stream", "()Ljava/util/stream/Stream;"); private static final FQMethod COLLECT = new FQMethod("java/util/stream/Stream", "collect", "(Ljava/util/stream/Collector;)Ljava/lang/Object;"); @@ -77,6 +78,9 @@ public class FunctionalInterfaceIssues extends BytecodeScanningDetector { "()Ljava/util/Optional;"); private static final FQMethod ISPRESENT = new FQMethod("java/util/Optional", "isPresent", SignatureBuilder.SIG_VOID_TO_BOOLEAN); + private static final FQMethod TOLIST = new FQMethod("java/util/stream/Collectors", "toList", "()Ljava/util/stream/Collector;"); + private static final FQMethod TOSET = new FQMethod("java/util/stream/Collectors", "toSet", "()Ljava/util/stream/Collector;"); + private static final FQMethod GET = new FQMethod("java/util/List", "get", SignatureBuilder.SIG_INT_TO_OBJECT); enum ParseState { @@ -88,7 +92,7 @@ enum AnonState { } enum FIIUserValue { - COLLECT_ITEM, FILTER_ITEM, FINDFIRST_ITEM; + STREAM_ITEM, COLLECT_ITEM, FILTER_ITEM, FINDFIRST_ITEM; } private BugReporter bugReporter; @@ -290,7 +294,7 @@ public void sawOpcode(int seen) { } } else { switch (seen) { - case Const.INVOKEDYNAMIC: + case Const.INVOKEDYNAMIC: { ConstantInvokeDynamic cid = (ConstantInvokeDynamic) getConstantRefOperand(); ConstantMethodHandle cmh = getMethodHandle(cid.getBootstrapMethodAttrIndex()); @@ -310,8 +314,9 @@ public void sawOpcode(int seen) { fiis.add(fii); } break; + } - case Const.INVOKEINTERFACE: + case Const.INVOKEINTERFACE: { QMethod m = new QMethod(getNameConstantOperand(), getSigConstantOperand()); if (CONTAINS.equals(m)) { @@ -332,6 +337,10 @@ public void sawOpcode(int seen) { NORMAL_PRIORITY).addClass(this).addMethod(this).addSourceLine(this)); } } + } else if (STREAM.equals(m)) { + if (stack.getStackDepth() >= 1) { + userValue = FIIUserValue.STREAM_ITEM; + } } else { FQMethod fqm = new FQMethod(getClassConstantOperand(), getNameConstantOperand(), getSigConstantOperand()); @@ -369,8 +378,9 @@ public void sawOpcode(int seen) { } } break; + } - case Const.INVOKEVIRTUAL: + case Const.INVOKEVIRTUAL: { FQMethod fqm = new FQMethod(getClassConstantOperand(), getNameConstantOperand(), getSigConstantOperand()); if (ISPRESENT.equals(fqm)) { @@ -385,6 +395,24 @@ public void sawOpcode(int seen) { } break; } + + case Const.INVOKESTATIC: { + if (stack.getStackDepth() > 0) { + OpcodeStack.Item itm = stack.getStackItem(0); + FIIUserValue uv = (FIIUserValue) itm.getUserValue(); + if (uv == FIIUserValue.STREAM_ITEM) { + FQMethod fqm = new FQMethod(getClassConstantOperand(), getNameConstantOperand(), + getSigConstantOperand()); + if (TOLIST.equals(fqm) || TOSET.equals(fqm)) { + bugReporter + .reportBug(new BugInstance(this, BugType.FII_USE_COPYCONSTRUCTOR.name(), NORMAL_PRIORITY) + .addClass(this).addMethod(this).addSourceLine(this)); + } + } + } + break; + } + } } } finally { stack.sawOpcode(this, seen); diff --git a/src/main/java/com/mebigfatguy/fbcontrib/utils/BugType.java b/src/main/java/com/mebigfatguy/fbcontrib/utils/BugType.java index 26174bda..9e9f3878 100644 --- a/src/main/java/com/mebigfatguy/fbcontrib/utils/BugType.java +++ b/src/main/java/com/mebigfatguy/fbcontrib/utils/BugType.java @@ -90,6 +90,7 @@ public enum BugType { FII_AVOID_SIZE_ON_COLLECTED_STREAM, FII_COMBINE_FILTERS, FII_USE_ANY_MATCH, + FII_USE_COPYCONSTRUCTOR, FII_USE_FIND_FIRST, FII_USE_FUNCTION_IDENTITY, FII_USE_METHOD_REFERENCE, diff --git a/src/samples/java/ex/FII_Sample.java b/src/samples/java/ex/FII_Sample.java index bf44276b..45ecd684 100644 --- a/src/samples/java/ex/FII_Sample.java +++ b/src/samples/java/ex/FII_Sample.java @@ -69,6 +69,10 @@ public Map mapIdentity(List baubles) { public int sizeOnACollect(List baubles, String name) { return baubles.stream().filter(b -> b.getName().equals(name)).collect(Collectors.toSet()).size(); } + + public List streamingRatherThanCC(Set s) { + return s.stream().collect(Collectors.toList()); + } public void fpUnrelatedLambdaValue282(Map map, BaubleFactory factory) { map.computeIfAbsent("pixie dust", _unused -> factory.getBauble());
+ Set s = new HashSet + + + Set s = myArrayList.stream().collect(Collectors.toSet); + + ]]> +
+ Set s = myArrayList.stream().collect(Collectors.toSet); +