2

I am currently trying to rebuild an old application from a tag and unfortunately I get jar files containing "non binary equals" .class files.

I've tried to compare the decompiled version of these .class files and they seem to be equals but is there a secure and automated way to diagnose such .class equality ?

It's important for me to know if my generated jars are equal to the old ones, even if the .class files inside are not binary equals, but functionally equals (certainly due to a different javac version).

Thx

PS.

  • Both are compiled with the same major version (Major: 52)
  • If I compare the output of "old" and "new" using javap -c command I have no differences
  • If I compare the output using javap -v command I find a few lines shift (eg: #480 in old becomes #478 in new for the same instruction) and some missing errors declarations (eg: 365 = Utf8 Lorg/eclipse/ui/PartInitException; only in old)
Jerome
  • 1,225
  • 2
  • 12
  • 23
  • Have you checked you're compiling with the right Java version (6, 7, 8, .. : https://stackoverflow.com/questions/1096148/how-to-check-the-jdk-version-used-to-compile-a-class-file )? That version is part of the binary. The conversion from .java to .class should be identical between minor versions – zapl May 17 '18 at 10:09
  • Both are compiled with the same java version (Major: 52) but when I compare "javap -v old new" I get a shift of most numbers two lines less in the new version (eg: #482 becomes #480 for the same "line"). Besides I have two of three more Exception clauses declared in the old version... (eg: ''#365 = Utf8 Lorg/eclipse/ui/PartInitException;'' ) – Jerome May 17 '18 at 13:36

2 Answers2

0

It depends on what you mean by "equals".

If you mean byte for byte equality, then just use the cmp utility.

You appear to mean something else. But here's the problem: there is sufficient potential for variability in ".class" files that accurate comparison may be difficult:

  • The content of a .class file will depend on the compiler used; e.g. the Oracle compiler, the Eclipse compiler and others such as Jikes will most likely emit different byte codes.

  • The content will depends on compiler options; e.g. -source and -target, -g settings, and so on.

  • The content of a .class file may depend on the precise compiler major / minor / patch version numbers. And build platform.

  • Trivial changes such as adding / removing source code blank lines or comments can alter source line numbers resulting in different .class files

  • Some Java compiler store the compiler version and/or timestamp as non-standard attributes in the .class files.

  • Differences in libraries can change lead to differences in code compiled against them.


I would suggest two approaches:

  • compare javap output, ignoring things that don't affect code, signatures and constants in the constant pool

  • identify the compiler, version and options used to compile the preexisting JARs, and use the exactly the same when recompiling.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Thanks for your comment! By "equals" I mean "functionally equals" which stands for "the code will do the same thing at a functional level". I managed to create a script doing the following: compare jar files using "zipcmp", if differences are found between ".class" files, diff the result of a procyon decompiler on the old and new version. If no diffs between both decompilations are found, I assume the .class are functionally equivalent, do you think this is a good approach? – Jerome May 18 '18 at 11:07
0

I finally found an approach which is acceptable from my point of view

  1. Compare "generated" and "old" equivalent jar files using zipcmp
  2. If differences are found in ".class" files, diff a decompilation of "generated" and "old" .class and print this diff
  3. If no decompilation diffs are found consider the .class are equivalent

I wrote this script to help with the work

#!/bin/bash

GENERATED="<changeme>/application_5.2.0_generated"
OLD="<changeme>/application_5.2.0_old"
#DECOMPILER="javap -c"
DECOMPILER="java -jar <changeme>/procyon-decompiler-0.5.30.jar"

for plugin in $GENERATED/plugins/*; do
    echo "$plugin"
    base=$(basename "$plugin")
    old_plugin="$OLD/plugins/$base"

    zipcmp $plugin $old_plugin

    if [ $? -ne 0 ]; then
        mkdir -p "$GENERATED/unzip/$base" && cd "$GENERATED/unzip/$base" && jar xf $plugin
        mkdir -p "$OLD/unzip/$base" && cd "$OLD/unzip/$base" && jar xf $old_plugin

        for class in $(zipcmp $plugin $old_plugin | grep ".class" | awk '{print $4;}' | uniq); do
            diff <($DECOMPILER "$GENERATED/unzip/$base/$class") <($DECOMPILER "$OLD/unzip/$base/$class") > /tmp/output
            if [ $? -ne 0 ]; then
                echo "diff <($DECOMPILER $GENERATED/unzip/$base/$class) <($DECOMPILER $OLD/unzip/$base/$class)"
                cat /tmp/output
            fi
        done
    fi
done
Jerome
  • 1,225
  • 2
  • 12
  • 23