# jardiff **Repository Path**: sun2477/jardiff ## Basic Information - **Project Name**: jardiff - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2024-08-07 - **Last Updated**: 2025-01-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # JAR differ A tool for generating bytecode diffs Requires JDK 8+ ## About JarDiff is a tool for generating detailed but comprehensible diffs of sets (JAR or directory) of Java classfiles. Class files are rendered with ASM's [`Textifier`](https://asm.ow2.io/javadoc/org/objectweb/asm/util/Textifier.html) and with `scalap`. Other files are rendered as-is. The rendered files are committed into a Git repository, one commit per provided command line argument. The diffs between these are rendered to standard out (unless `--quiet` is provided). If only a single argument is provided, the initial commit is rendered. By default, a temporary git repository is used and deleted on exit. Use `--git` to provide a custom location for the repository that can be inspected after the tool has run. ## Installing ### macOS `macOS` users may install with: ``` brew install retronym/formulas/jardiff ``` ### Other Platforms - Download `jardiff.jar` from https://github.com/scala/jardiff/releases/latest - Wrap in a shell function or script to run `java -jar jardiff.jar "$@"` Usage ---------- ``` % jardiff -h usage: jardiff [-c] [-g ] [-h] [-i ] [-p] [-q] [-r] [-U ] VERSION1 [VERSION2 ...] Each VERSION may designate a single file, a directory, JAR file or a `:`-delimited classpath -c,--suppress-code Suppress method bodies -g,--git Directory to output a git repository containing the diff -h,--help Display this message -i,--ignore File pattern to ignore rendered files in gitignore format -p,--suppress-privates Display only non-private members -q,--quiet Don't output diffs to standard out -r,--raw Disable sorting and filtering of classfile contents -U,--unified Number of context lines in diff % jardiff dir1 dir2 % jardiff v1/A.class v2/A.class % jardiff --git /tmp/diff-repo --quiet v1.jar v2.jar v3.jar ``` ## Building After cloning this project, use `sbt clean core/assembly`. ## Sample Output ### Scala 2.11 vs 2.12 trait encoding changes ```scala // test.scala trait T { def foo = 4 } class C extends T ``` ``` % ~/scala/2.11/bin/scalac -d /tmp/v1 test.scala && ~/scala/2.12/bin/scalac -d /tmp/v2 test.scala % jardiff /tmp/v1 /tmp/v2 ``` ```diff diff --git a/C.class.asm b/C.class.asm index f3a33f1..33b9282 100644 --- a/C.class.asm +++ b/C.class.asm @@ -1,4 +1,4 @@ -// class version 50.0 (50) +// class version 52.0 (52) // access flags 0x21 public class C implements T { @@ -8,7 +8,7 @@ ALOAD 0 INVOKESPECIAL java/lang/Object. ()V ALOAD 0 - INVOKESTATIC T$class.$init$ (LT;)V + INVOKESTATIC T.$init$ (LT;)V RETURN MAXSTACK = 1 MAXLOCALS = 1 @@ -16,7 +16,7 @@ // access flags 0x1 public foo()I ALOAD 0 - INVOKESTATIC T$class.foo (LT;)I + INVOKESTATIC T.foo$ (LT;)I IRETURN MAXSTACK = 1 MAXLOCALS = 1 diff --git a/T.class.asm b/T.class.asm index 9180093..fcac19f 100644 --- a/T.class.asm +++ b/T.class.asm @@ -1,8 +1,28 @@ -// class version 50.0 (50) +// class version 52.0 (52) // access flags 0x601 public abstract interface T { - // access flags 0x401 - public abstract foo()I + // access flags 0x9 + public static $init$(LT;)V + // parameter final synthetic $this + RETURN + MAXSTACK = 0 + MAXLOCALS = 1 + + // access flags 0x1 + public default foo()I + ICONST_4 + IRETURN + MAXSTACK = 1 + MAXLOCALS = 1 + + // access flags 0x1009 + public static synthetic foo$(LT;)I + // parameter final synthetic $this + ALOAD 0 + INVOKESPECIAL T.foo ()I + IRETURN + MAXSTACK = 1 + MAXLOCALS = 1 } ``` ### Scala standard library changes during 2.11 minor releases ``` % jardiff --quiet --git /tmp/scala-library-diff /Users/jz/scala/2.11.*/lib/scala-library.jar ``` Browsable Repo: [scala-library-diff](https://github.com/retronym/scala-library-diff/commits/master) ### API changes visible in bytecode descriptors / generic signature / scalap output ```scala // V1.scala trait C { def m1(a: String) def m2(a: Option[String]) def m3(a: String) } ``` ```scala // V2.scala trait C { def m1(a: AnyRef) def m2(a: Option[AnyRef]) def m3(a: scala.collection.immutable.StringOps) // value class } ``` ```diff diff --git a/C.class.asm b/C.class.asm index 52a43d5..bdb0541 100644 --- a/C.class.asm +++ b/C.class.asm @@ -4,12 +4,12 @@ // access flags 0x401 - public abstract m1(Ljava/lang/String;)V + public abstract m1(Ljava/lang/Object;)V // parameter final a // access flags 0x401 - // signature (Lscala/Option;)V - // declaration: void m2(scala.Option) + // signature (Lscala/Option;)V + // declaration: void m2(scala.Option) public abstract m2(Lscala/Option;)V // parameter final a diff --git a/C.class.scalap b/C.class.scalap index 78f7d91..637cd98 100644 --- a/C.class.scalap +++ b/C.class.scalap @@ -1,5 +1,5 @@ trait C extends scala.AnyRef { - def m1(a: scala.Predef.String): scala.Unit - def m2(a: scala.Option[scala.Predef.String]): scala.Unit - def m3(a: scala.Predef.String): scala.Unit + def m1(a: scala.AnyRef): scala.Unit + def m2(a: scala.Option[scala.AnyRef]): scala.Unit + def m3(a: scala.collection.immutable.StringOps): scala.Unit } ``` ### Validating a Scala compiler nightly ``` % git clone --quiet --depth 1 akka/akka; cd akka % cat ~/.sbt/0.13/resolver.sbt resolvers ++= ( if (scalaVersion.value.contains("-bin")) List("scala-integration" at "https://scala-ci.typesafe.com/artifactory/scala-integration/") else Nil ) % for v in 2.12.2 2.12.3-bin-bd6294d; do \ echo $v; \ mkdir -p /tmp/akka-$v; \ time sbt ++$v clean package &>/dev/null; \ for f in $(find . -name '*.jar'); do cp $f /tmp/akka-$v/; done; \ done 2.12.2 real 3m19.468s user 8m56.947s sys 0m17.343s 2.12.3-bin-bd6294d real 2m57.631s user 8m40.168s sys 0m16.172s % jardiff -q --git /tmp/akka-diff $(find /tmp/akka-2.12.2 | paste -s -d : -) $(find /tmp/akka-2.12.3-bin-bd6294d | paste -s -d : -) ``` The resulting diff shows that the only change in the generated bytecode is due to [scala/scala#5857](https://github.com/scala/scala/pull/5857), "Fix lambda deserialization in classes with 252+ lambdas" ```diff % git --git-dir /tmp/akka-diff/.git log -p -1 commit 83f0e0a5dabbdf1b7677363bf238d81e4c5b032e (HEAD -> master) Author: Jason Zaugg Date: Mon Jun 5 17:08:17 2017 +1000 jardiff textified output of: /tmp/akka-2.12.3-bin-bd6294d:/tmp/akka-2.12.3-bin-bd6294d/akka-2.5-SNAPSHOT.jar:... diff --git a/akka/stream/scaladsl/GraphApply.class.asm b/akka/stream/scaladsl/GraphApply.class.asm index 5672560..4d1fafb 100644 --- a/akka/stream/scaladsl/GraphApply.class.asm +++ b/akka/stream/scaladsl/GraphApply.class.asm @@ -2802,6 +2802,8 @@ public abstract interface akka/stream/scaladsl/GraphApply { // access flags 0x100A private static synthetic $deserializeLambda$(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object; + TRYCATCHBLOCK L0 L1 L1 java/lang/IllegalArgumentException + L0 ALOAD 0 INVOKEDYNAMIC lambdaDeserialize(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object; [ // handle kind 0x6 : INVOKESTATIC @@ -3308,12 +3310,20 @@ public abstract interface akka/stream/scaladsl/GraphApply { // handle kind 0x6 : INVOKESTATIC akka/stream/scaladsl/GraphApply.$anonfun$create$250(Lscala/Function1;Ljava/lang/Object;)Ljava/lang/Object;, // handle kind 0x6 : INVOKESTATIC - akka/stream/scaladsl/GraphApply.$anonfun$create$251(Lscala/Function1;Ljava/lang/Object;)Ljava/lang/Object;, + akka/stream/scaladsl/GraphApply.$anonfun$create$251(Lscala/Function1;Ljava/lang/Object;)Ljava/lang/Object; + ] + ARETURN + L1 + ALOAD 0 + INVOKEDYNAMIC lambdaDeserialize(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object; [ + // handle kind 0x6 : INVOKESTATIC + scala/runtime/LambdaDeserialize.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite; + // arguments: // handle kind 0x6 : INVOKESTATIC akka/stream/scaladsl/GraphApply.$anonfun$create$252(Lscala/Function1;Ljava/lang/Object;)Ljava/lang/Object; ] ARETURN - MAXSTACK = 1 + MAXSTACK = 2 MAXLOCALS = 1 // access flags 0x9 ```