2 Replies Latest reply on Dec 5, 2008 11:11 AM by chiba

    serialVersionUID issue

    spampete

      Hi all,

      I have released a small memory leak detector on http://www.qarks.com web site, which is using javassist. Despite using the serialVersionUID helper, recent tests showed a remaining issue with some serializable classes during unmarshalling, with InvalidClassException due to inconsistent serialVersionUID.

      I've had a look to Sun's ObjectStreamClass implementation (JDK 1.6) and found indeed some differences explaining the situation. Actually, I do not know if the issue is on Sun's side, not respecting its own spec, or not, but the fix I brough does work on my test bench.

      I'd be interested in having your feedback, all of you guys !!

      Meanwhile, here is the method I changed within javassist serialVersionUID helper, which I will also send to Shigeru Chiba by email:

      
       static long calculateDefault(CtClass clazz) throws CannotCompileException {
       ByteArrayOutputStream bout;
       try {
       bout = new ByteArrayOutputStream();
       DataOutputStream out = new DataOutputStream(bout);
       ClassFile classFile = clazz.getClassFile();
      
       String javaName = javaName(clazz);
       out.writeUTF(javaName);
      
       CtMethod[] methods = clazz.getDeclaredMethods();
      
       int classMods = clazz.getModifiers();
       if ((classMods & Modifier.INTERFACE) != 0) {
       classMods = (methods.length > 0) ?
       (classMods | Modifier.ABSTRACT) :
       (classMods & ~Modifier.ABSTRACT);
       }
       out.writeInt(classMods);
      
       if (!clazz.isArray()){
       String[] interfaces = classFile.getInterfaces();
       for (int i = 0; i < interfaces.length; ++i)
       interfaces = javaName(interfaces);
      
       Arrays.sort(interfaces);
       for (int i = 0; i < interfaces.length; ++i) {
       out.writeUTF(interfaces);
       }
       }
      
       CtField[] fields = clazz.getDeclaredFields();
       Arrays.sort(fields, new Comparator() {
       public int compare(Object o1, Object o2) {
       CtField field1 = (CtField) o1;
       CtField field2 = (CtField) o2;
       return field1.getName().compareTo(field2.getName());
       }
      
       });
       for (int i = 0; i < fields.length; ++i) {
       CtField field = fields;
       int mods = field.getModifiers();
       if (((mods & 0x2) == 0) || ((mods & 0x88) == 0)) {
       out.writeUTF(field.getName());
       out.writeInt(mods);
       out.writeUTF(field.getFieldInfo2().getDescriptor());
       }
      
       }
      
       if (classFile.getStaticInitializer() != null) {
       out.writeUTF("<clinit>");
       out.writeInt(8);
       out.writeUTF("()V");
       }
      
       CtConstructor[] constructors = clazz.getDeclaredConstructors();
       Arrays.sort(constructors, new Comparator() {
       public int compare(Object o1, Object o2) {
       CtConstructor c1 = (CtConstructor) o1;
       CtConstructor c2 = (CtConstructor) o2;
       return c1.getMethodInfo2().getDescriptor().compareTo(
       c2.getMethodInfo2().getDescriptor());
       }
      
       });
       for (int i = 0; i < constructors.length; ++i) {
       CtConstructor constructor = constructors;
       int mods = constructor.getModifiers();
       if ((mods & 0x2) == 0) {
       out.writeUTF("<init>");
       out.writeInt(mods);
       out.writeUTF(constructor.getMethodInfo2().getDescriptor()
       .replace('/', '.'));
       }
      
       }
      
       //CtMethod[] methods = clazz.getDeclaredMethods();
       Arrays.sort(methods, new Comparator() {
       public int compare(Object o1, Object o2) {
       CtMethod m1 = (CtMethod) o1;
       CtMethod m2 = (CtMethod) o2;
       int value = m1.getName().compareTo(m2.getName());
       if (value == 0) {
       value = m1.getMethodInfo2().getDescriptor().compareTo(
       m2.getMethodInfo2().getDescriptor());
       }
      
       return value;
       }
      
       });
       for (int i = 0; i < methods.length; ++i) {
       CtMethod method = methods;
       int mods = method.getModifiers() &
       (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
       Modifier.STATIC | Modifier.FINAL |
       Modifier.SYNCHRONIZED | Modifier.NATIVE |
       Modifier.ABSTRACT | Modifier.STRICT);
      
       if ((mods & Modifier.PRIVATE) == 0) {
       out.writeUTF(method.getName());
       out.writeInt(mods);
       out.writeUTF(method.getMethodInfo2().getDescriptor()
       .replace('/', '.'));
       }
      
       }
      
       out.flush();
      
       MessageDigest digest = MessageDigest.getInstance("SHA");
       byte[] digested = digest.digest(bout.toByteArray());
       long hash = 0L;
       for (int i = Math.min(digested.length, 8) - 1; i >= 0; --i)
       hash = hash << 8 | digested & 0xFF;
      
       return hash;
       } catch (IOException e) {
       throw new CannotCompileException(e);
       } catch (NoSuchAlgorithmException e) {
       throw new CannotCompileException(e);
       }
       }
      
      


      kind regards,

      Pierre

        • 1. Re: serialVersionUID issue
          spampete

          just to be a little more precise, jleak can be found under http://www.qarks.com/sources.html

          this project was a sort of challenge over javassist, to see if it could help building such instrumentation through the java agent option, very funny to develop actually. Feel free to use jleak as a tutorial anywhere you like.

          Javassist is easy to use, simple, clear, does not require bytecode knowledge, it is really a great contribution to java world, thank you Shigeru and co!

          • 2. Re: serialVersionUID issue
            chiba

            Thank you, I modified SerialVersionUID according to your post.