A future version of Java will most likely introduce value types. They will behave very similar to primitives, which includes that reference identity will have no meaning to them.
Java 8 already introduced classes which might become such types - they are called value-based - and of course they are regular classes and have a referential character. But this might change when value types are eventually introduced and loosing the meaning of reference identity could break things implemented until then. So in order to minimize/prevent that (or at least be able to blame the user), the Javadoc contains an explicit warning.
It would be very helpful (and awesome! :) ) if FindBugs could identify such uses and warn the programmer about them.
So if I understand correctly, you want the following:
1. Warn if somebody uses == for value type objects
2. Warn if somebody uses System.identityHashCode passing value type object as parameter
3. Warn if somebody uses value type object as the key in IdentityHashMap (might be not very easy to catch all these cases though)
4. Warn if somebody tries to synchronize on value type object
Anything else?
Hi Tagir,
the list is almost complete. The documentation also states not to serialize instances of those classes. (This is somewhat strange as some classes of the new DateTime API [like LocalDateTime] are marked as value-based classes but are also serializable. ... Wat?)
Anyways, another idea occurred to me while thinking about this: Instead of just applying these rules to the classes marked by the JDK as value-based, it would be really nice to provide an annotation with which the user can mark her own classes as such. This annotation should also trigger FindBugs to apply checks to such classes which make sure that the contract stated in the documentation (e.g. immutability) is upheld.
I would like to contribute on this! If you don't deem this ticket to be very important and can stand that it might take a while until I worked myself into the FindBugs codebase and can make useful commits, I would like to give it a shot. Of course it would be nice to get some pointers about where to start. :)
Last edit: Nicolai Parlog 2014-12-14
The serialization case should be already covered by SE_BAD_FIELD pattern. You may check by yourself whether it works for Optional.
== comparison check can be added to edu.umd.cs.findbugs.detect.FindRefComparison detector (see checkRefComparison method). It already checks for == comparison of strings and boxed primitives. As for System.identityHashCode and synchronization you can extend something existing (like FindPuzzlers) or write a separate OpcodeStack detector. You should just check what is in the stack before MONITORENTER instruction or INVOKESTATIC instruction with System.identityHashCode operand. This is quite easy.
Hi Tagir,
as you recommended on the mailing list, I'm continuing the discussion here.
You were right about the SE_BAD_FIELD pattern catching the serialization of
Optional. And after finding my way in, the reference comparison was also straight forward. At least as long as I stick to identifying value-based classes by their fully qualified class name.But I'm planning to provide the user with an annotation with which she can mark her own value-based classes as such in order to have FindBugs execute the same checks on them. To be able to identify such classes I have to access annotations from the information that get passed into
FindRefComparison.checkRefComparison, most likely fromorg.apache.bcel.generic.Type(the type for the left and right hand side of the comparison). I didn't find a way to do so, though. Can you help me out?so long ... Nicolai
Last edit: Nicolai Parlog 2014-12-10
Hello! It's always possible to get the XClass object for the given class (for example, using Global.getAnalysisCache().getClassAnalysis(XClass.class, classDescriptor) where classDescriptor can be created using DescriptorFactory.createClassDescriptor(className) or DescriptorFactory.createClassDescriptorFromDottedClassName(className) if you don't have one already). If you are inside BytecodeScanningDetector#sawOpcode method, you can use getXClassOperand() to get the XClass object for current opcode if applicable. Having XClass you can extract any of its annotations using XClass.getAnnotation(annotationClassDescriptor) method.
Hi Tagir,
thank you, that worked out fine. I created an annotation
@ValueBasedand uniformly process value-based classes from the JDK and those marked with it the annotation.Reference comparison works. I'll be back with questions when I start working on the rest. :)
so long ... Nicolai
Hi Tagir,
I found some time today and made headway. I'm generally interested in a review to assure that I'm not going into the totally wrong direction. But I've also got a concrete question:
While inside
OpcodeStackDetector.sawOpcodeI don't know how to identify the type of an instance on which a method is called. (For details see here.)thank you ... Nicolai
Hello!
I will look in details later. For tests it's better to create single file named sfBugsNew/Feature313jdk8.java. We are doing testing using both Java 7 and Java 8, so your tests should be excluded in Java 7. This is currently done using by file name suffix jdk8 inside sfBugsNew package. You may create nested static classes like SerializingValueBasedClass to structurize your test.
getDottedClassNameFromSignature is practically the same as ClassName.toDottedClassName(ClassName.fromFieldSignature(...)) and it's better to use ClassName.toSignature(ClassName.toSlashedClassName(...)) instead of "L" + lhs.replace('.', '/') + ";". In general it's preferred to use slashed class name internally where possible (for example in isValueBasedClass, isJdkValueBasedClass, isAnnotatedAsValueBasedClass of ValueBasedClassIdentifier), this should reduce number of slashed-dotted conversions. Try to use @SlashedClassName, @DottedClassName annotations where applicable.
Probably it's reasonable to add com.google.common.base.Optional as predefined value-based class.
As for instance type you may use getStack().getStackItem(getNumberArguments(getMethodDescriptorOperand().getSignature()))).getSignature(). This type is more precise than variable/field type. For example, here you'll get Optional type, not object:
Hi,
I implemented all of your ideas (and updated the pull request). I have one question, though: What exactly does the slashed class name look like? Does it include the descriptor and the semicolon? E.g. "Ljava/util/Optional;" or "java/util/Optional"? At first I went with the former but it turns out that
DescriptorFactory.createClassDescriptordoesn't work that way so I picked the latter instead.Looks like almost everything works, now. The next pattern is
System.identityHashCode(I'm confident to get that to work easily). The final pattern will then be value-based classes as keys in anIdentityHashMap. I see two possibilities here. We could either look for declarations and instantiations of that class with such a key type or we warn when, e.g.,putgets called. Any preferences? I prefer the first option. Here I'd need an idea how to start.so long ...
Nicolai, there are two different things: type signature (like "Ljava/util/Optional;") and slashed class name (like "java/util/Optional"). Type signatures may include primitive types (like I for int) and arrays (like [[Ljava/lang/Object; for Object[][]) whereas slashed class name may represent only classes.
We are preparing 3.0.1 release, so we aren't adding new features right now. We will review your pull request later.
As for IdentityHashMap you just need to parse generic signature of the field/variable where it's stored. Get the signature string from LocalVariableTable (for variables) and from XField (for fields) and use GenericSignatureParser.
Hi Tagir,
thanks again for your help and explanations. Unfortunately I couldn't make headway with
IdentityHashMap. Should I extend theOpcodeStackDetectorto detect its creation and then do the checks you describe? Or is this a totally different approach?If this is too difficult/lengthy to explain, you could point me toward an example in FindBugs where something similar is done. I'm also not clinging to this feature so if you prefer, you could implement it yourself.
so long ...