There is this feature request I once wrote for "checkstyle". But checkstype is about style and not so much about finding bugs. Maybe this is something for FindBugs?
Before I post this I have tested to see if FindBugs doesn't already find it but to my surprise this is not marked as a problem:
Number[] numbers = new Integer[] { 1, 2, 3 };
numbers[0] = 3.141; // this fails at runtime
Here's the link to the original request for checkstyle:
http://sourceforge.net/p/checkstyle/feature-requests/612/
Below the line is a copy of the original request:
Disallow arrays to be passed to fields that are declared as a supertype (super class or interface). Only arrays of the exact same type as declared are to be allowed.
Example:
final Number[] integers = new Integer[1];
integers[0] = Double.valueOf(1.2d);
(Note that those two lines of code would not have to be consecutive, they could even be in different units/methods.)
I would prefer a check that would mark the first line since the types do not match.
An alternative would be to mark the second line, to have the same as with generic collections. But that could also be achieved with generic arrays, but those are often just a lot of trouble and would just be replaces with Lists.
(Note that this check would have absolutely nothing to do with generics or collections, this is just to explain the problem! The check would only apply to "normal" arrays.)
The same thing would be done like this:
final List<? extends Number> integers2 = new LinkedList<Integer>();
integers2.add(Double.valueOf(1.2d));
Double is an instance of Number, but the actual type of the collection is not known. Therefore it is not allowed to add a Double.
Another example is this function:
public static void set(final int index, final Number value, final Number[] array) {
assert value != null && array != null;
assert array.getClass().getComponentType().isAssignableFrom(value.getClass());
array[index] = value;
}
// possible invocation that will fail:
set(0, Double.valueOf(1.2d), new Integer[1]);
In this case it would be better to define a generic type.
But the check would mark the unsafe assignment is inside that function. This would be just like the same function with a generic list:
Parameter is List<? extends Number> and then list.set(index, value).
The generic version of the method would not solve the problem and still only throw an exception at runtime:
public static <N extends Number> void set(final int index, final N value, final N[] array)
Only assignments would be checked (and method calls, when the array is assigned to a parameter). The check is simple: The Types must be equal:
X[] x = new X[size]; // OK
X[] x = new Y[size]; // Not OK!
Then there are assignments to array fields (arr[42] = foo):
I already have given the check in the function above as an assertion (using isAssignableFrom).
This can only be done at runtime but becomes irrelevant if Checkstyle already asserts that the type of the arrays are correct.
Functions that should not declare a concrete type can still use generics:
public static <N extends Number> N get(final int index, final N[] array) {
assert array != null;
return array[index];
}
Helps for better type safety. There would be a guarantee that any Array of type Foo[] would actually be of exactly that type and not any subtype.
1: Use (misuse?) of generics:
public static <X extends Number> void main(final String args[]) {
final X[] integers = (X[]) new Integer[1];
integers[0] = Double.valueOf(1.2d);
// Type mismatch: cannot convert from Double to X
}
2: Use of (generic) collections (e.g. List<? extends Number>).
http://www.ibm.com/developerworks/java/library/j-jtp01255/index.html
http://c2.com/cgi/wiki?JavaArraysBreakTypeSafety
I wrote some experimental code to find the creation of such arrays and tested on Eclipse JDT code (~8300 classes). 24 bugs were reported. Most of them look like this:
There are several cases:
http://grepcode.com/file/repository.grepcode.com/java/eclipse.org/4.4.0/org.eclipse.jdt/core/3.10.0/org/eclipse/jdt/core/dom/PackageBinding.java#113
http://grepcode.com/file/repository.grepcode.com/java/eclipse.org/4.4.0/org.eclipse.jdt/core/3.10.0/org/eclipse/jdt/core/dom/AnnotationBinding.java#60
http://grepcode.com/file/repository.grepcode.com/java/eclipse.org/4.4.0/org.eclipse.jdt/core/3.10.0/org/eclipse/jdt/internal/core/ClasspathChange.java#314
Sometimes array is stored into a field which has more general type, but in local context type narrowing seems to be ok:
http://grepcode.com/file/repository.grepcode.com/java/eclipse.org/4.4.0/org.eclipse.jdt/core/3.10.0/org/eclipse/jdt/internal/compiler/parser/JavadocParser.java#269
Here expressions is created of type JavadocArgumentExpression[], but then (line#273) assigned to allocation.arguments, which has Expression[] type.
Another case:
http://grepcode.com/file/repository.grepcode.com/java/eclipse.org/4.4.0/org.eclipse.jdt/core/3.10.0/org/eclipse/jdt/internal/compiler/parser/JavadocParser.java#746
Sometimes variable type is Object[], because this variable will be later passed to the method which accepts Object[]:
http://grepcode.com/file/repository.grepcode.com/java/eclipse.org/4.4.0/org.eclipse.jdt/ui/3.10.0/org/eclipse/jdt/internal/corext/refactoring/structure/MoveInnerToTopRefactoring.java#715
No dangerous writing was detected. The only dubious place I found is here:
http://grepcode.com/file/repository.grepcode.com/java/eclipse.org/4.4.0/org.eclipse.jdt/core/3.10.0/org/eclipse/jdt/internal/compiler/parser/JavadocParser.java#718
Line #755 looks like
The nameRef variable has ASTNode type, but invalidParamReferencesStack is likely to be JavadocSingleNameReference[] array. On the other hand nameRef always contains JavadocSingleNameReference object, so ArrayStoreException will never happen. Probably types have to be changed here to avoid confusion, but it's not a critical problem.
In general I can conclude that writing such bug pattern is not difficult, but I have some doubts whether FindBugs should report this. Probably some people might consider this as a matter of style. At least if you create the array, assign it to the variable and then never modify it you will have no problems. Andrey, Bill, what do you think? Should we report such cases? If yes, which category, priority and rank should be assigned?
I would see this is as "correctness", "low priority" rank 20 warning in case no write happens after array creation or array is of type Object[].
If at least one write happens and array is not of type Object[] I would see this as "correctness", "medium priority" rank 10 warning.
If additionally we know the written type is incompatible (and we know the real array type and this is not Object[]) I would see this as "correctness", "high priority" rank 1 warning.
"Correctness" because it is neither "dodgy code" nor "bad practice" but "don't know better".
Does it make sense?
Sounds reasonable, though need to think about details. For example, covariant array assignment to a field and array element writing may occur in different classes, so we may need to analyze the whole project before reporting bugs. Ok, I'll take this task.
I implemented this, though it reports in somewhat different way. Everything in new detector named CovariantArrayAssignment. There's a special check method allImplementationsDerivedFromSubclass which checks whether all known non-abstract subclasses of declared array type are also subclasses of actual array type. For example if you have an interface ISomething and only implementation class Something and your array is defined as ISomething[] something = new Something[10]. In this case ArrayStoreException will never occur unless you add new implementation class.
There are 4 new bug patterns:
CAA_COVARIANT_ARRAY_LOCAL - when covariant array is assigned to a local variable. It's always Low priority/20
CAA_COVARIANT_ARRAY_FIELD - when covariant array is assigned to a field. It's Low/20 if the field is private or package protected or class is private or allImplementationsDerivedFromSubclass is true. Otherwise it's Normal/17.
CAA_COVARIANT_ARRAY_RETURN - when covariant array is returned from the method. It's Low/20 if the method is private or package protected or class is private or allImplementationsDerivedFromSubclass is true. Otherwise it's Normal/17.
I decided to put all of these patterns in "Dodgy code" as such assignments don't cause the problems directly, but make the code error-prone.
One more pattern is CAA_COVARIANT_ARRAY_ELEMENT_STORE (correctness category) - when an element is stored into covariant array. There are three cases here:
I decided not to merge covariant array assignment and array element store to the single warning, because this is a little bit harder to implement. Current report looks ok for me.
The only 100% ArrayStoreException I found in tested projects is here:
http://grepcode.com/file/repository.grepcode.com/java/eclipse.org/4.4.0/org.eclipse.core/resources/3.9.0/org/eclipse/core/internal/resources/MarkerSet.java#232
But impossible instanceof is reported in the previous line, thus ArrayStoreException is also impossible.
The code along with the testcase is committed. Feel free to comment if you have any feedback.
Last edit: Tagir Valeev 2014-11-18
Not seen as much of a problem in practice, generates a clear exception when it does cause a problem, there is code that works perfectly fine that uses this pattern.
Not enabling by default in 3.0.1.