{"id":26075995,"url":"https://github.com/lightbend-labs/jardiff","last_synced_at":"2025-04-06T05:15:17.693Z","repository":{"id":21515601,"uuid":"92914368","full_name":"lightbend-labs/jardiff","owner":"lightbend-labs","description":"A tool for comparing JAR files, including method bodies and Scala 2 pickled signatures","archived":false,"fork":false,"pushed_at":"2025-03-12T22:35:27.000Z","size":216,"stargazers_count":176,"open_issues_count":7,"forks_count":26,"subscribers_count":19,"default_branch":"main","last_synced_at":"2025-03-30T04:08:22.738Z","etag":null,"topics":["command-line-tool","diff","jvm-bytecode","scala"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lightbend-labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-05-31T06:50:55.000Z","updated_at":"2025-03-12T22:35:31.000Z","dependencies_parsed_at":"2023-02-11T21:16:21.664Z","dependency_job_id":"a92df1da-f004-47eb-b77d-99385d9c1025","html_url":"https://github.com/lightbend-labs/jardiff","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend-labs%2Fjardiff","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend-labs%2Fjardiff/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend-labs%2Fjardiff/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lightbend-labs%2Fjardiff/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lightbend-labs","download_url":"https://codeload.github.com/lightbend-labs/jardiff/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247436284,"owners_count":20938533,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["command-line-tool","diff","jvm-bytecode","scala"],"created_at":"2025-03-09T01:41:35.314Z","updated_at":"2025-04-06T05:15:17.671Z","avatar_url":"https://github.com/lightbend-labs.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JAR differ\n\nA tool for generating bytecode diffs\n\nRequires JDK 8+\n\n## About\n\nJarDiff is a tool for generating detailed but comprehensible diffs of sets (JAR or directory) of Java\nclassfiles.\n\nClass files are rendered with ASM's [`Textifier`](https://asm.ow2.io/javadoc/org/objectweb/asm/util/Textifier.html)\nand with `scalap`. Other files are rendered as-is.\n\nThe rendered files are committed into a Git repository, one commit per provided command line argument.\n\nThe diffs between these are rendered to standard out (unless `--quiet` is provided). If only a single\nargument is provided, the initial commit is rendered.\n\nBy default, a temporary git repository is used and deleted on exit. Use `--git` to provide a custom\nlocation for the repository that can be inspected after the tool has run.\n\n## Installing\n\n### macOS\n\n`macOS` users may install with:\n\n```\nbrew install retronym/formulas/jardiff\n```\n\n### Other Platforms\n\n - Download `jardiff.jar` from https://github.com/scala/jardiff/releases/latest\n - Wrap in a shell function or script to run `java -jar jardiff.jar \"$@\"`\n\nUsage\n----------\n\n```\n% jardiff -h\nusage: jardiff [-c] [-g \u003cdir\u003e] [-h] [-i \u003carg\u003e] [-p] [-q] [-r] [-U \u003cn\u003e] VERSION1 [VERSION2 ...]\n\nEach VERSION may designate a single file, a directory, JAR file or a\n`:`-delimited classpath\n\n -c,--suppress-code       Suppress method bodies\n -g,--git \u003cdir\u003e           Directory to output a git repository containing the\n                          diff\n -h,--help                Display this message\n -i,--ignore \u003carg\u003e        File pattern to ignore rendered files in gitignore\n                          format\n -p,--suppress-privates   Display only non-private members\n -q,--quiet               Don't output diffs to standard out\n -r,--raw                 Disable sorting and filtering of classfile contents\n -U,--unified \u003cn\u003e         Number of context lines in diff\n\n% jardiff dir1 dir2\n\n% jardiff v1/A.class v2/A.class\n\n% jardiff --git /tmp/diff-repo --quiet v1.jar v2.jar v3.jar\n```\n\n## Building\n\nAfter cloning this project, use `sbt clean core/assembly`.\n\n## Sample Output\n\n### Scala 2.11 vs 2.12 trait encoding changes\n\n```scala\n// test.scala\ntrait T { def foo = 4 }\nclass C extends T\n```\n\n```\n% ~/scala/2.11/bin/scalac -d /tmp/v1 test.scala \u0026\u0026 ~/scala/2.12/bin/scalac -d /tmp/v2 test.scala\n\n% jardiff /tmp/v1 /tmp/v2\n```\n```diff\ndiff --git a/C.class.asm b/C.class.asm\nindex f3a33f1..33b9282 100644\n--- a/C.class.asm\n+++ b/C.class.asm\n@@ -1,4 +1,4 @@\n-// class version 50.0 (50)\n+// class version 52.0 (52)\n // access flags 0x21\n public class C implements T  {\n \n@@ -8,7 +8,7 @@\n     ALOAD 0\n     INVOKESPECIAL java/lang/Object.\u003cinit\u003e ()V\n     ALOAD 0\n-    INVOKESTATIC T$class.$init$ (LT;)V\n+    INVOKESTATIC T.$init$ (LT;)V\n     RETURN\n     MAXSTACK = 1\n     MAXLOCALS = 1\n@@ -16,7 +16,7 @@\n   // access flags 0x1\n   public foo()I\n     ALOAD 0\n-    INVOKESTATIC T$class.foo (LT;)I\n+    INVOKESTATIC T.foo$ (LT;)I\n     IRETURN\n     MAXSTACK = 1\n     MAXLOCALS = 1\ndiff --git a/T.class.asm b/T.class.asm\nindex 9180093..fcac19f 100644\n--- a/T.class.asm\n+++ b/T.class.asm\n@@ -1,8 +1,28 @@\n-// class version 50.0 (50)\n+// class version 52.0 (52)\n // access flags 0x601\n public abstract interface T {\n \n \n-  // access flags 0x401\n-  public abstract foo()I\n+  // access flags 0x9\n+  public static $init$(LT;)V\n+    // parameter final synthetic  $this\n+    RETURN\n+    MAXSTACK = 0\n+    MAXLOCALS = 1\n+\n+  // access flags 0x1\n+  public default foo()I\n+    ICONST_4\n+    IRETURN\n+    MAXSTACK = 1\n+    MAXLOCALS = 1\n+\n+  // access flags 0x1009\n+  public static synthetic foo$(LT;)I\n+    // parameter final synthetic  $this\n+    ALOAD 0\n+    INVOKESPECIAL T.foo ()I\n+    IRETURN\n+    MAXSTACK = 1\n+    MAXLOCALS = 1\n }\n```\n\n### Scala standard library changes during 2.11 minor releases\n\n```\n% jardiff --quiet --git /tmp/scala-library-diff /Users/jz/scala/2.11.*/lib/scala-library.jar\n\n```\n\nBrowsable Repo: [scala-library-diff](https://github.com/retronym/scala-library-diff/commits/master)\n\n### API changes visible in bytecode descriptors / generic signature / scalap output\n\n```scala\n// V1.scala\ntrait C {\n  def m1(a: String)\n  def m2(a: Option[String])\n  def m3(a: String)\n}\n```\n```scala\n// V2.scala\ntrait C {\n  def m1(a: AnyRef)\n  def m2(a: Option[AnyRef])\n  def m3(a: scala.collection.immutable.StringOps) // value class\n}\n```\n\n```diff\ndiff --git a/C.class.asm b/C.class.asm\nindex 52a43d5..bdb0541 100644\n--- a/C.class.asm\n+++ b/C.class.asm\n@@ -4,12 +4,12 @@\n \n \n   // access flags 0x401\n-  public abstract m1(Ljava/lang/String;)V\n+  public abstract m1(Ljava/lang/Object;)V\n     // parameter final  a\n \n   // access flags 0x401\n-  // signature (Lscala/Option\u003cLjava/lang/String;\u003e;)V\n-  // declaration: void m2(scala.Option\u003cjava.lang.String\u003e)\n+  // signature (Lscala/Option\u003cLjava/lang/Object;\u003e;)V\n+  // declaration: void m2(scala.Option\u003cjava.lang.Object\u003e)\n   public abstract m2(Lscala/Option;)V\n     // parameter final  a\n \ndiff --git a/C.class.scalap b/C.class.scalap\nindex 78f7d91..637cd98 100644\n--- a/C.class.scalap\n+++ b/C.class.scalap\n@@ -1,5 +1,5 @@\n trait C extends scala.AnyRef {\n-  def m1(a: scala.Predef.String): scala.Unit\n-  def m2(a: scala.Option[scala.Predef.String]): scala.Unit\n-  def m3(a: scala.Predef.String): scala.Unit\n+  def m1(a: scala.AnyRef): scala.Unit\n+  def m2(a: scala.Option[scala.AnyRef]): scala.Unit\n+  def m3(a: scala.collection.immutable.StringOps): scala.Unit\n }\n```\n\n### Validating a Scala compiler nightly\n\n```\n% git clone --quiet --depth 1 akka/akka; cd akka\n\n% cat ~/.sbt/0.13/resolver.sbt \nresolvers ++= (\n  if (scalaVersion.value.contains(\"-bin\"))\n     List(\"scala-integration\" at \"https://scala-ci.typesafe.com/artifactory/scala-integration/\")\n  else Nil\n)\n\n% for v in 2.12.2 2.12.3-bin-bd6294d; do \\\n  echo $v; \\\n  mkdir -p /tmp/akka-$v; \\\n  time sbt ++$v clean package \u0026\u003e/dev/null; \\\n  for f in $(find . -name '*.jar'); do cp $f /tmp/akka-$v/; done; \\\ndone\n\n2.12.2\n\nreal\t3m19.468s\nuser\t8m56.947s\nsys\t0m17.343s\n2.12.3-bin-bd6294d\n\nreal\t2m57.631s\nuser\t8m40.168s\nsys\t0m16.172s\n\n% 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 : -)\n```\n\n\nThe resulting diff shows that the only change in the generated bytecode is due\nto [scala/scala#5857](https://github.com/scala/scala/pull/5857), \"Fix lambda deserialization in classes with 252+ lambdas\"\n\n```diff\n% git --git-dir /tmp/akka-diff/.git log -p -1\ncommit 83f0e0a5dabbdf1b7677363bf238d81e4c5b032e (HEAD -\u003e master)\nAuthor: Jason Zaugg \u003cjzaugg@gmail.com\u003e\nDate:   Mon Jun 5 17:08:17 2017 +1000\n\n    jardiff textified output of: /tmp/akka-2.12.3-bin-bd6294d:/tmp/akka-2.12.3-bin-bd6294d/akka-2.5-SNAPSHOT.jar:...\n\ndiff --git a/akka/stream/scaladsl/GraphApply.class.asm b/akka/stream/scaladsl/GraphApply.class.asm\nindex 5672560..4d1fafb 100644\n--- a/akka/stream/scaladsl/GraphApply.class.asm\n+++ b/akka/stream/scaladsl/GraphApply.class.asm\n@@ -2802,6 +2802,8 @@ public abstract interface akka/stream/scaladsl/GraphApply {\n \n   // access flags 0x100A\n   private static synthetic $deserializeLambda$(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;\n+    TRYCATCHBLOCK L0 L1 L1 java/lang/IllegalArgumentException\n+   L0\n     ALOAD 0\n     INVOKEDYNAMIC lambdaDeserialize(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object; [\n       // handle kind 0x6 : INVOKESTATIC\n@@ -3308,12 +3310,20 @@ public abstract interface akka/stream/scaladsl/GraphApply {\n       // handle kind 0x6 : INVOKESTATIC\n       akka/stream/scaladsl/GraphApply.$anonfun$create$250(Lscala/Function1;Ljava/lang/Object;)Ljava/lang/Object;, \n       // handle kind 0x6 : INVOKESTATIC\n-      akka/stream/scaladsl/GraphApply.$anonfun$create$251(Lscala/Function1;Ljava/lang/Object;)Ljava/lang/Object;, \n+      akka/stream/scaladsl/GraphApply.$anonfun$create$251(Lscala/Function1;Ljava/lang/Object;)Ljava/lang/Object;\n+    ]\n+    ARETURN\n+   L1\n+    ALOAD 0\n+    INVOKEDYNAMIC lambdaDeserialize(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object; [\n+      // handle kind 0x6 : INVOKESTATIC\n+      scala/runtime/LambdaDeserialize.bootstrap(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite;\n+      // arguments:\n       // handle kind 0x6 : INVOKESTATIC\n       akka/stream/scaladsl/GraphApply.$anonfun$create$252(Lscala/Function1;Ljava/lang/Object;)Ljava/lang/Object;\n     ]\n     ARETURN\n-    MAXSTACK = 1\n+    MAXSTACK = 2\n     MAXLOCALS = 1\n \n   // access flags 0x9\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightbend-labs%2Fjardiff","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flightbend-labs%2Fjardiff","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flightbend-labs%2Fjardiff/lists"}