If A extends B extends C, why can I cast to A but get a ClassCastException casting to C?
Asked Answered
M

2

7

I am trying to read an ASN1 object using Bouncycastle on Android. I expect it to be a DERSequence, which in Bouncycastle is a subclass of ASN1Sequence, which is a subclass of ASN1Object.

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
...

ASN1InputStream ais = ...;
Object o = ais.readObject();
// Eclipse's debugger now says o is a DERSequence, as expected.

DERSequence o2 = (DERSequence)o;
ASN1Sequence o3 = o2;
ASN1Object o4 = o3;
// And o4 is now exactly what I want.

ASN1Object o5 = (ASN1Object)o;
// But this throws:
///    java.lang.ClassCastException: org.bouncycastle.asn1.DERSequence

Based on feedback from the answers, I have constructed another, shorter example:

Object o = new DERSequence();
ASN1Object o1 = new DERSequence(); // This behaves fine.
ASN1Object o2 = (ASN1Object)o; // Throws ClassCastException.

What causes this cast to fail?

Marlo answered 12/4, 2011 at 11:33 Comment(6)
Can you cast to ASN1Sequence?Mungo
That's...weird. How about (ASN1Object)(ASN1Sequence)(DERSequence)o? :pBlare
@John: Is there anything relevant in it? That o5 = ... line of code alone causes the exception - everything above it in the stack is just Android noise. That line I posted is the first line of the stack trace.Marlo
@Joe: are you sure that the Object o is pointing to the same reference?. That's why i asked.Fieldpiece
The same reference as what? I did not elide any code in between assigning it and casting it. And the first line of the stack trace, which I did post, agrees it is a DERSequence.Marlo
Classloader problem? Two identical classes loaded by a different classloader are considered incompatible. Or maybe classes from your own bcprov-jdk15-146.jar are being used in combination with something on the classpath or extensions? Actually, I've got no idea. This is totally baffling.Attorn
L
2

Android has a modified class hierarchy here, see comment in http://www.netmite.com/android/mydroid/1.5/dalvik/libcore/security/src/main/java/org/bouncycastle/asn1/ASN1Sequence.java Are you absolutely sure the version you are using that a DERSequence is a subtype of ASN1Object?

e.g it is here http://www.eecs.berkeley.edu/~jonah/bc/org/bouncycastle/asn1/DERSequence.html

but not here http://www.androidjavadoc.com/m3-rc37a/org/bouncycastle/asn1/DERSequence.html

Lazuli answered 12/4, 2011 at 11:48 Comment(10)
@vickirk: As far as I can tell I am using my own bcprov-jdk15-146.jar, in which I am sure the inheritance hierarchy is as I described. Even if it was not, wouldn't the longer chain of casts cause a ClassCastException at some point then?Marlo
Not sure what you mean by longer chain of casts, the android modified one removes ASN1Object from the hierarchy. Try adding some code to get the class and it's parents classes to verify the class hierarchy, if it is there then somehow you have ended up with two different instance of the ASN1Object class, can't think how this could happen in this case.Lazuli
@vickirk: I mean what my example code does. Casting to DERSequence then implicitly upcasting back to ASN1Object worked fine.Marlo
Sorry, I thought you were getting a ClassCastException when you try doing that or did I misunderstand the question?Lazuli
@vickirk: o4 is assigned fine. o5 is not. See my update for a simpler case that fails in a similar way.Marlo
But o4 is assigned by an implicit cast (try using that object or one of the methods provided by ANS1Object), o5 is an explicit one and will perform a check using the actual class objects at runtime, rather than at compile time. i.e eclipse and the compiler are seeing one set of classes and your runtime another, or mixtureLazuli
(Great, I've only been using Java two weeks and I've already got the equivalent of a dynamic symbol conflict, which I didn't even know was possible.) This does appear to be the problem - the DERSequence I am instantiating trails backwards through ASN1Sequence, ASN1Collection, DERObject, ASN1Encodable, Object, never hitting ASN1Object. Unfortunately knowing that does nothing to solve my actual problem - some internal code in Bouncycastle won't parse a PEM because the class hierarchy doesn't match its expectations. Do you have any suggestions for how to fix it?Marlo
It's an uncommon error, and changing the class hierarchy in this manner is not something I've seen often between major version releases, it can get even more fun when the are the same class but sourced by different classloaders ;-) You will have to remove one of the defining jars from the equation, try to get eclipse to use the runtime one, from what you said this would be bcprov-jdk15-146.jar.Lazuli
After some googling, the other jar is apparently an Android system one, which cannot be removed, and also doesn't contain many of the parts of BC I need. The standard solution seems to be including the BC source renamed as a different package.Marlo
There are probably people with better experience and time than me to give a better answer. Depending on your usage you may be able to deploy the BC code you need with your app without clashing with the system provided one, possibly even refactoring the code to change the package, thereby renaming all the classes. Or it may be possible to provide a classloader that prefers your bundled code, not something I've ever looked into on android. BTW I'm off on holiday very soon for a few days so will have to hand this over to anyone who wants to continue.Lazuli
M
0

Can you try executing this:

package classtest;

import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;

public class A {

    public static void main(String[] args) {
        testCast(new DERSequence());
    }

    private static void testCast(Object o) {
        DERSequence o2 = (DERSequence) o;
        ASN1Sequence o3 = o2;
        ASN1Object o4 = o3;

        ASN1Object o5 = (ASN1Object) o;
    }

}

(for me, this does not throw any exception)

If this does not work, you should check the answer of vickirk

Mungo answered 12/4, 2011 at 11:51 Comment(1)
Object o6 = new DERSequence(); ASN1Object o7 = (ASN1Object)o6; causes the same exception.Marlo

© 2022 - 2024 — McMap. All rights reserved.