Menu

#313 Warn about identity-sensitive operations on value based classes

3.x
closed-rejected
nobody
5
2017-10-22
2014-10-15
No

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.

Discussion

  • Tagir Valeev

    Tagir Valeev - 2014-11-10

    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?

     
  • Nicolai Parlog

    Nicolai Parlog - 2014-11-10

    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
  • Tagir Valeev

    Tagir Valeev - 2014-11-11

    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.

     
  • Nicolai Parlog

    Nicolai Parlog - 2014-12-10

    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 from org.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
  • Tagir Valeev

    Tagir Valeev - 2014-12-11

    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.

     
  • Nicolai Parlog

    Nicolai Parlog - 2014-12-14

    Hi Tagir,

    thank you, that worked out fine. I created an annotation @ValueBased and 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

     
  • Nicolai Parlog

    Nicolai Parlog - 2015-01-18

    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.sawOpcode I don't know how to identify the type of an instance on which a method is called. (For details see here.)

    thank you ... Nicolai

     
  • Tagir Valeev

    Tagir Valeev - 2015-01-22

    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:

    Object x = Optional.of(blahblah);
    x.wait();
    
     
  • Nicolai Parlog

    Nicolai Parlog - 2015-02-15

    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.createClassDescriptor doesn'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 an IdentityHashMap. 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., put gets called. Any preferences? I prefer the first option. Here I'd need an idea how to start.

    so long ...

     
  • Tagir Valeev

    Tagir Valeev - 2015-02-17

    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.

     
  • Nicolai Parlog

    Nicolai Parlog - 2015-03-20

    Hi Tagir,

    thanks again for your help and explanations. Unfortunately I couldn't make headway with IdentityHashMap. Should I extend the OpcodeStackDetector to 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 ...

     
  • Andrey Loskutov

    Andrey Loskutov - 2017-10-22
    • Labels: Java 1.8 --> report on spotbugs, Java 1.8
    • Status: open --> closed-rejected
     

Log in to post a comment.