Make ijar support not stripping the class data.

This is done by adding a --[no]strip_classes flag to ijar.

PiperOrigin-RevId: 191184258
diff --git a/third_party/ijar/ijar.cc b/third_party/ijar/ijar.cc
index 160645d..a50b970 100644
--- a/third_party/ijar/ijar.cc
+++ b/third_party/ijar/ijar.cc
@@ -15,11 +15,11 @@
 // ijar.cpp -- .jar -> _interface.jar tool.
 //
 
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <limits.h>
 #include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include <memory>
 
 #include "third_party/ijar/zip.h"
@@ -31,9 +31,9 @@
 // Reads a JVM class from classdata_in (of the specified length), and
 // writes out a simplified class to classdata_out, advancing the
 // pointer. Returns true if the class should be kept.
-bool StripClass(u1*& classdata_out, const u1* classdata_in, size_t in_length);
+bool StripClass(u1 *&classdata_out, const u1 *classdata_in, size_t in_length);
 
-const char* CLASS_EXTENSION = ".class";
+const char *CLASS_EXTENSION = ".class";
 const size_t CLASS_EXTENSION_LENGTH = strlen(CLASS_EXTENSION);
 
 const char *MANIFEST_DIR_PATH = "META-INF/";
@@ -51,29 +51,34 @@
 const char *INJECTING_RULE_KIND_KEY = "Injecting-Rule-Kind: ";
 const size_t INJECTING_RULE_KIND_KEY_LENGTH = strlen(INJECTING_RULE_KIND_KEY);
 
-// ZipExtractorProcessor that select only .class file and use
-// StripClass to generate an interface class, storing as a new file
-// in the specified ZipBuilder.
-class JarStripperProcessor : public ZipExtractorProcessor {
- public:
-  JarStripperProcessor() {}
-  virtual ~JarStripperProcessor() {}
-
-  virtual void Process(const char* filename, const u4 attr,
-                       const u1* data, const size_t size);
-  virtual bool Accept(const char* filename, const u4 attr);
-
- private:
-  // Not owned by JarStripperProcessor, see SetZipBuilder().
-  ZipBuilder* builder;
-
+class JarExtractorProcessor : public ZipExtractorProcessor {
  public:
   // Set the ZipBuilder to add the ijar class to the output zip file.
   // This pointer should not be deleted while this class is still in use and
   // it should be set before any call to the Process() method.
-  void SetZipBuilder(ZipBuilder* builder) {
-    this->builder = builder;
-  }
+  void SetZipBuilder(ZipBuilder *builder) { this->builder_ = builder; }
+  virtual void WriteManifest(const char *target_label,
+                             const char *injecting_rule_kind) = 0;
+
+ protected:
+  // Not owned by JarStripperProcessor, see SetZipBuilder().
+  ZipBuilder *builder_;
+};
+
+// ZipExtractorProcessor that select only .class file and use
+// StripClass to generate an interface class, storing as a new file
+// in the specified ZipBuilder.
+class JarStripperProcessor : public JarExtractorProcessor {
+ public:
+  JarStripperProcessor() {}
+  virtual ~JarStripperProcessor() {}
+
+  virtual void Process(const char *filename, const u4 attr, const u1 *data,
+                       const size_t size);
+  virtual bool Accept(const char *filename, const u4 attr);
+
+  virtual void WriteManifest(const char *target_label,
+                             const char *injecting_rule_kind);
 };
 
 bool JarStripperProcessor::Accept(const char *filename, const u4 /*attr*/) {
@@ -86,8 +91,8 @@
   return true;
 }
 
-static bool IsModuleInfo(const char* filename) {
-  const char* slash = strrchr(filename, '/');
+static bool IsModuleInfo(const char *filename) {
+  const char *slash = strrchr(filename, '/');
   if (slash == NULL) {
     slash = filename;
   } else {
@@ -102,36 +107,195 @@
     fprintf(stderr, "INFO: StripClass: %s\n", filename);
   }
   if (IsModuleInfo(filename)) {
-    u1* q = builder->NewFile(filename, 0);
+    u1 *q = builder_->NewFile(filename, 0);
     memcpy(q, data, size);
-    builder->FinishFile(size, false, true);
+    builder_->FinishFile(size, /* compress: */ false, /* compute_crc: */ true);
   } else {
-    u1* buf = reinterpret_cast<u1*>(malloc(size));
-    u1* classdata_out = buf;
+    u1 *buf = reinterpret_cast<u1 *>(malloc(size));
+    u1 *classdata_out = buf;
     if (!StripClass(buf, data, size)) {
       free(classdata_out);
       return;
     }
-    u1* q = builder->NewFile(filename, 0);
+    u1 *q = builder_->NewFile(filename, 0);
     size_t out_length = buf - classdata_out;
     memcpy(q, classdata_out, out_length);
-    builder->FinishFile(out_length, false, true);
+    builder_->FinishFile(out_length, /* compress: */ false,
+                         /* compute_crc: */ true);
     free(classdata_out);
   }
 }
 
-// Copies the string into the buffer without the null terminator, returns length
-static size_t WriteStr(u1 *buf, const char *str) {
+// Copies the string into the buffer without the null terminator, returns
+// updated buffer pointer
+static u1 *WriteStr(u1 *buf, const char *str) {
   size_t len = strlen(str);
   memcpy(buf, str, len);
-  return len;
+  return buf + len;
 }
 
-// Computes the size of zip file content for the manifest created by
+// Writes a manifest attribute including a "\r\n" line break, returns updated
+// buffer pointer.
+static u1 *WriteManifestAttr(u1 *buf, const char *key, const char *val) {
+  buf = WriteStr(buf, key);
+  buf = WriteStr(buf, val);
+  *buf++ = '\r';
+  *buf++ = '\n';
+  return buf;
+}
+
+void JarStripperProcessor::WriteManifest(const char *target_label,
+                                         const char *injecting_rule_kind) {
+  if (target_label == nullptr) {
+    return;
+  }
+  builder_->WriteEmptyFile(MANIFEST_DIR_PATH);
+  u1 *start = builder_->NewFile(MANIFEST_PATH, 0);
+  u1 *buf = start;
+  buf = WriteStr(buf, MANIFEST_HEADER);
+  buf = WriteManifestAttr(buf, TARGET_LABEL_KEY, target_label);
+  if (injecting_rule_kind) {
+    buf = WriteManifestAttr(buf, INJECTING_RULE_KIND_KEY, injecting_rule_kind);
+  }
+  size_t total_len = buf - start;
+  builder_->FinishFile(total_len, /* compress: */ false,
+                       /* compute_crc: */ true);
+}
+
+class JarCopierProcessor : public JarExtractorProcessor {
+ public:
+  JarCopierProcessor(const char *jar) : jar_(jar) {}
+  virtual ~JarCopierProcessor() {}
+
+  virtual void Process(const char *filename, const u4 /*attr*/, const u1 *data,
+                       const size_t size);
+  virtual bool Accept(const char *filename, const u4 /*attr*/);
+
+  virtual void WriteManifest(const char *target_label,
+                             const char *injecting_rule_kind);
+
+ private:
+  class ManifestLocator : public ZipExtractorProcessor {
+   public:
+    ManifestLocator() : manifest_buf_(nullptr), manifest_size_(0) {}
+    virtual ~ManifestLocator() { free(manifest_buf_); }
+
+    u1 *manifest_buf_;
+    size_t manifest_size_;
+
+    virtual bool Accept(const char *filename, const u4 /*attr*/) {
+      return strcmp(filename, MANIFEST_PATH) == 0;
+    }
+
+    virtual void Process(const char * /*filename*/, const u4 /*attr*/,
+                         const u1 *data, const size_t size) {
+      manifest_buf_ = (u1 *)malloc(size);
+      memmove(manifest_buf_, data, size);
+      manifest_size_ = size;
+    }
+  };
+
+  const char *jar_;
+
+  u1 *AppendTargetLabelToManifest(u1 *buf, const u1 *manifest_data,
+                                  const size_t size, const char *target_label,
+                                  const char *injecting_rule_kind);
+};
+
+void JarCopierProcessor::Process(const char *filename, const u4 /*attr*/,
+                                 const u1 *data, const size_t size) {
+  if (verbose) {
+    fprintf(stderr, "INFO: CopyFile: %s\n", filename);
+  }
+  // We already handled the manifest in WriteManifest
+  if (strcmp(filename, MANIFEST_DIR_PATH) == 0 ||
+      strcmp(filename, MANIFEST_PATH) == 0) {
+    return;
+  }
+  u1 *q = builder_->NewFile(filename, 0);
+  memcpy(q, data, size);
+  builder_->FinishFile(size, /* compress: */ false, /* compute_crc: */ true);
+}
+
+bool JarCopierProcessor::Accept(const char * /*filename*/, const u4 /*attr*/) {
+  return true;
+}
+
+void JarCopierProcessor::WriteManifest(const char *target_label,
+                                       const char *injecting_rule_kind) {
+  ManifestLocator manifest_locator;
+  std::unique_ptr<ZipExtractor> in(
+      ZipExtractor::Create(jar_, &manifest_locator));
+  in->ProcessAll();
+
+  bool wants_manifest =
+      manifest_locator.manifest_buf_ != nullptr || target_label != nullptr;
+  if (wants_manifest) {
+    builder_->WriteEmptyFile(MANIFEST_DIR_PATH);
+    u1 *start = builder_->NewFile(MANIFEST_PATH, 0);
+    u1 *buf = start;
+    // Three cases:
+    // 1. We need to merge the target label into a pre-existing manifest
+    // 2. Write a manifest from scratch with a target label
+    // 3. Copy existing manifest without adding target label
+    if (manifest_locator.manifest_buf_ != nullptr && target_label != nullptr) {
+      buf = AppendTargetLabelToManifest(buf, manifest_locator.manifest_buf_,
+                                        manifest_locator.manifest_size_,
+                                        target_label, injecting_rule_kind);
+    } else if (target_label != nullptr) {
+      buf = WriteStr(buf, MANIFEST_HEADER);
+      buf = WriteManifestAttr(buf, TARGET_LABEL_KEY, target_label);
+      if (injecting_rule_kind) {
+        buf = WriteManifestAttr(buf, INJECTING_RULE_KIND_KEY,
+                                injecting_rule_kind);
+      }
+    } else {
+      memcpy(buf, manifest_locator.manifest_buf_,
+             manifest_locator.manifest_size_);
+      buf += manifest_locator.manifest_size_;
+    }
+
+    size_t total_len = buf - start;
+    builder_->FinishFile(total_len, /* compress: */ false,
+                         /* compute_crc: */ true);
+  }
+}
+
+u1 *JarCopierProcessor::AppendTargetLabelToManifest(
+    u1 *buf, const u1 *manifest_data, const size_t size,
+    const char *target_label, const char *injecting_rule_kind) {
+  const char *line_start = (const char *)manifest_data;
+  const char *data_end = (const char *)manifest_data + size;
+  while (line_start < data_end) {
+    const char *line_end = strchr(line_start, '\n');
+    // Go past return char to point to next line, or to end of data buffer
+    line_end = line_end != nullptr ? line_end + 1 : data_end;
+
+    // Copy line unless it's Target-Label/Injecting-Rule-Kind and we're writing
+    // that ourselves
+    if (strncmp(line_start, TARGET_LABEL_KEY, TARGET_LABEL_KEY_LENGTH) != 0 &&
+        strncmp(line_start, INJECTING_RULE_KIND_KEY,
+                INJECTING_RULE_KIND_KEY_LENGTH) != 0) {
+      size_t len = line_end - line_start;
+      // Skip empty lines
+      if (len > 0 && line_start[0] != '\r' && line_start[0] != '\n') {
+        memcpy(buf, line_start, len);
+        buf += len;
+      }
+    }
+    line_start = line_end;
+  }
+  buf = WriteManifestAttr(buf, TARGET_LABEL_KEY, target_label);
+  if (injecting_rule_kind != nullptr) {
+    buf = WriteManifestAttr(buf, INJECTING_RULE_KIND_KEY, injecting_rule_kind);
+  }
+  return buf;
+}
+
 // WriteManifest, including zip file format overhead.
 static size_t EstimateManifestOutputSize(const char *target_label,
                                          const char *injecting_rule_kind) {
-  if (target_label == NULL) {
+  if (target_label == nullptr) {
     return 0;
   }
   // local headers
@@ -152,36 +316,21 @@
   return length;
 }
 
-static void WriteManifest(ZipBuilder *out, const char *target_label,
-                          const char *injecting_rule_kind) {
-  if (target_label == NULL) {
-    return;
-  }
-  out->WriteEmptyFile(MANIFEST_DIR_PATH);
-  u1 *start = out->NewFile(MANIFEST_PATH, 0);
-  u1 *buf = start;
-  buf += WriteStr(buf, MANIFEST_HEADER);
-  buf += WriteStr(buf, TARGET_LABEL_KEY);
-  buf += WriteStr(buf, target_label);
-  *buf++ = '\r';
-  *buf++ = '\n';
-  if (injecting_rule_kind) {
-    buf += WriteStr(buf, INJECTING_RULE_KIND_KEY);
-    buf += WriteStr(buf, injecting_rule_kind);
-    *buf++ = '\r';
-    *buf++ = '\n';
-  }
-  size_t total_len = buf - start;
-  out->FinishFile(total_len);
-}
-
 // Opens "file_in" (a .jar file) for reading, and writes an interface
 // .jar to "file_out".
 static void OpenFilesAndProcessJar(const char *file_out, const char *file_in,
-                                   const char *target_label,
+                                   bool strip_jar, const char *target_label,
                                    const char *injecting_rule_kind) {
-  JarStripperProcessor processor;
-  std::unique_ptr<ZipExtractor> in(ZipExtractor::Create(file_in, &processor));
+  std::unique_ptr<JarExtractorProcessor> processor;
+  if (strip_jar) {
+    processor =
+        std::unique_ptr<JarExtractorProcessor>(new JarStripperProcessor());
+  } else {
+    processor =
+        std::unique_ptr<JarExtractorProcessor>(new JarCopierProcessor(file_in));
+  }
+  std::unique_ptr<ZipExtractor> in(
+      ZipExtractor::Create(file_in, processor.get()));
   if (in.get() == NULL) {
     fprintf(stderr, "Unable to open Zip file %s: %s\n", file_in,
             strerror(errno));
@@ -196,9 +345,8 @@
             strerror(errno));
     abort();
   }
-  processor.SetZipBuilder(out.get());
-
-  WriteManifest(out.get(), target_label, injecting_rule_kind);
+  processor->SetZipBuilder(out.get());
+  processor->WriteManifest(target_label, injecting_rule_kind);
 
   // Process all files in the zip
   if (in->ProcessAll() < 0) {
@@ -219,9 +367,8 @@
   size_t in_length = in->GetSize();
   size_t out_length = out->GetSize();
   if (verbose) {
-    fprintf(stderr, "INFO: produced interface jar: %s -> %s (%d%%).\n",
-            file_in, file_out,
-            static_cast<int>(100.0 * out_length / in_length));
+    fprintf(stderr, "INFO: produced interface jar: %s -> %s (%d%%).\n", file_in,
+            file_out, static_cast<int>(100.0 * out_length / in_length));
   }
 }
 }  // namespace devtools_ijar
@@ -232,13 +379,15 @@
 static void usage() {
   fprintf(stderr,
           "Usage: ijar "
-          "[-v] [--target label label] [--injecting_rule_kind kind] "
+          "[-v] [--[no]strip_jar] "
+          "[--target label label] [--injecting_rule_kind kind] "
           "x.jar [x_interface.jar>]\n");
   fprintf(stderr, "Creates an interface jar from the specified jar file.\n");
   exit(1);
 }
 
 int main(int argc, char **argv) {
+  bool strip_jar = true;
   const char *target_label = NULL;
   const char *injecting_rule_kind = NULL;
   const char *filename_in = NULL;
@@ -247,6 +396,10 @@
   for (int ii = 1; ii < argc; ++ii) {
     if (strcmp(argv[ii], "-v") == 0) {
       devtools_ijar::verbose = true;
+    } else if (strcmp(argv[ii], "--strip_jar") == 0) {
+      strip_jar = true;
+    } else if (strcmp(argv[ii], "--nostrip_jar") == 0) {
+      strip_jar = false;
     } else if (strcmp(argv[ii], "--target_label") == 0) {
       if (++ii >= argc) {
         usage();
@@ -279,7 +432,8 @@
       strcpy(filename_out_buf + len - 4, "-interface.jar");
       filename_out = filename_out_buf;
     } else {
-      fprintf(stderr, "Can't determine output filename since input filename "
+      fprintf(stderr,
+              "Can't determine output filename since input filename "
               "doesn't end with '.jar'.\n");
       return 1;
     }
@@ -289,7 +443,7 @@
     fprintf(stderr, "INFO: writing to '%s'.\n", filename_out);
   }
 
-  devtools_ijar::OpenFilesAndProcessJar(filename_out, filename_in, target_label,
-                                        injecting_rule_kind);
+  devtools_ijar::OpenFilesAndProcessJar(filename_out, filename_in, strip_jar,
+                                        target_label, injecting_rule_kind);
   return 0;
 }
diff --git a/third_party/ijar/test/BUILD b/third_party/ijar/test/BUILD
index 62b6edd..816ae12 100644
--- a/third_party/ijar/test/BUILD
+++ b/third_party/ijar/test/BUILD
@@ -110,6 +110,46 @@
 )
 
 genrule(
+    name = "interface_ijar_testlib_nostrip",
+    srcs = [":ijar_testlib"],
+    outs = ["interface_ijar_testlib_nostrip.jar"],
+    cmd = "$(location //third_party/ijar) --target_label //foo:foo --nostrip_jar $< $@",
+    tools = ["//third_party/ijar"],
+)
+
+genrule(
+    name = "jar_with_manifest_nostrip",
+    srcs = ["jar-with-manifest.jar"],
+    outs = ["jar-with-manifest-nostrip.jar"],
+    cmd = "$(location //third_party/ijar) --target_label //foo:foo --nostrip_jar $< $@",
+    tools = ["//third_party/ijar"],
+)
+
+genrule(
+    name = "jar_with_manifest_and_target_label_nostrip",
+    srcs = ["jar-with-manifest-and-target-label.jar"],
+    outs = ["jar-with-manifest-and-target-label-nostrip.jar"],
+    cmd = "$(location //third_party/ijar) --target_label //foo:foo --nostrip_jar $< $@",
+    tools = ["//third_party/ijar"],
+)
+
+genrule(
+    name = "jar_without_manifest_nostrip",
+    srcs = ["jar-without-manifest.jar"],
+    outs = ["jar-without-manifest-nostrip.jar"],
+    cmd = "$(location //third_party/ijar) --target_label //foo:foo --nostrip_jar $< $@",
+    tools = ["//third_party/ijar"],
+)
+
+genrule(
+    name = "jar_without_manifest_nostrip_idempotence",
+    srcs = ["jar-without-manifest-nostrip.jar"],
+    outs = ["jar-without-manifest-nostrip-idempotence.jar"],
+    cmd = "$(location //third_party/ijar) --target_label //foo:foo --nostrip_jar $< $@",
+    tools = ["//third_party/ijar"],
+)
+
+genrule(
     name = "empty_with_target_label",
     srcs = [":empty_zip.jar"],
     outs = ["empty_with_target_label.jar"],
@@ -194,10 +234,19 @@
         "PrivateNestedClass.java",
         "UseDeprecatedParts.java",
         "UseRestrictedAnnotation.java",
+        "jar-with-manifest.jar",
+        "jar-with-manifest-and-target-label.jar",
+        "jar-without-manifest.jar",
         "package-info.java",
         ":empty_with_target_label",
+        ":ijar_testlib",
         ":interface_ijar_testlib",
+        ":interface_ijar_testlib_nostrip",
         ":interface_ijar_testlib_with_target_label",
+        ":jar_with_manifest_and_target_label_nostrip",
+        ":jar_with_manifest_nostrip",
+        ":jar_without_manifest_nostrip",
+        ":jar_without_manifest_nostrip_idempotence",
         ":liblocal_and_anonymous_lib.jar",
         ":local_and_anonymous-interface.jar",
         ":module_info-interface.jar",
diff --git a/third_party/ijar/test/IjarTests.java b/third_party/ijar/test/IjarTests.java
index f88616f..f8e487f 100644
--- a/third_party/ijar/test/IjarTests.java
+++ b/third_party/ijar/test/IjarTests.java
@@ -25,6 +25,9 @@
 import com.google.devtools.build.java.bazel.BazelJavaCompiler;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.util.Arrays;
@@ -309,4 +312,102 @@
               LocalDateTime.of(2010, 1, 1, 0, 0, 0).atZone(ZoneOffset.systemDefault()).toInstant());
     }
   }
+
+  // Tests --nostrip_jar with a jar that already has a manifest, but no target label
+  @Test
+  public void testNoStripJarWithManifest() throws Exception {
+    JarFile original = new JarFile("third_party/ijar/test/jar-with-manifest.jar");
+    JarFile stripped = new JarFile("third_party/ijar/test/jar-with-manifest-nostrip.jar");
+    try {
+      ImmutableList<String> strippedEntries =
+          stripped.stream().map(JarEntry::getName).collect(toImmutableList());
+      assertThat(strippedEntries.get(0)).isEqualTo("META-INF/");
+      assertThat(strippedEntries.get(1)).isEqualTo("META-INF/MANIFEST.MF");
+      Manifest manifest = stripped.getManifest();
+      Attributes attributes = manifest.getMainAttributes();
+      assertThat(attributes.getValue("Manifest-Version")).isEqualTo("1.0");
+      // Created-By was already in manifest, doesn't get overwritten
+      assertThat(attributes.getValue("Created-By")).isEqualTo("test-code");
+      assertThat(attributes.getValue("Target-Label")).isEqualTo("//foo:foo");
+      assertNonManifestFilesBitIdentical(original, stripped);
+    } finally {
+      original.close();
+      stripped.close();
+    }
+  }
+
+  // Tests --nostrip_jar with a jar that already has a manifest with a target label
+  @Test
+  public void testNoStripJarWithManifestAndTargetLabel() throws Exception {
+    JarFile original = new JarFile("third_party/ijar/test/jar-with-manifest-and-target-label.jar");
+    JarFile stripped =
+        new JarFile("third_party/ijar/test/jar-with-manifest-and-target-label-nostrip.jar");
+    try {
+      ImmutableList<String> strippedEntries =
+          stripped.stream().map(JarEntry::getName).collect(toImmutableList());
+      assertThat(strippedEntries.get(0)).isEqualTo("META-INF/");
+      assertThat(strippedEntries.get(1)).isEqualTo("META-INF/MANIFEST.MF");
+      Manifest manifest = stripped.getManifest();
+      Attributes attributes = manifest.getMainAttributes();
+      assertThat(attributes.getValue("Manifest-Version")).isEqualTo("1.0");
+      // Created-By was already in manifest, doesn't get overwritten
+      assertThat(attributes.getValue("Created-By")).isEqualTo("test-code");
+      assertThat(attributes.getValue("Target-Label")).isEqualTo("//foo:foo");
+      assertNonManifestFilesBitIdentical(original, stripped);
+    } finally {
+      original.close();
+      stripped.close();
+    }
+  }
+
+  // Tests --nostrip_jar with a jar that didn't already have a manifest
+  @Test
+  public void testNoStripJarWithoutManifest() throws Exception {
+    JarFile original = new JarFile("third_party/ijar/test/jar-without-manifest.jar");
+    JarFile stripped = new JarFile("third_party/ijar/test/jar-without-manifest-nostrip.jar");
+    try {
+      ImmutableList<String> strippedEntries =
+          stripped.stream().map(JarEntry::getName).collect(toImmutableList());
+      assertThat(strippedEntries.get(0)).isEqualTo("META-INF/");
+      assertThat(strippedEntries.get(1)).isEqualTo("META-INF/MANIFEST.MF");
+      Manifest manifest = stripped.getManifest();
+      Attributes attributes = manifest.getMainAttributes();
+      assertThat(attributes.getValue("Manifest-Version")).isEqualTo("1.0");
+      assertThat(attributes.getValue("Created-By")).isEqualTo("bazel");
+      assertThat(attributes.getValue("Target-Label")).isEqualTo("//foo:foo");
+      assertNonManifestFilesBitIdentical(original, stripped);
+    } finally {
+      original.close();
+      stripped.close();
+    }
+  }
+
+  // Tests idempotence of --nostrip
+  @Test
+  public void testNoStripIdempotence() throws Exception {
+    byte[] original =
+        Files.readAllBytes(Paths.get("third_party/ijar/test/jar-without-manifest-nostrip.jar"));
+    byte[] stripped =
+        Files.readAllBytes(
+            Paths.get("third_party/ijar/test/jar-without-manifest-nostrip-idempotence.jar"));
+    assertThat(original).isEqualTo(stripped);
+  }
+
+  private static void assertNonManifestFilesBitIdentical(JarFile original, JarFile stripped)
+      throws IOException {
+    // Make sure that all other files came across bitwise equal
+    for (String classEntry :
+        original
+            .stream()
+            .map(JarEntry::getName)
+            .filter(name -> !name.equals("META-INF/MANIFEST.MF"))
+            .collect(toImmutableList())) {
+      ZipEntry originalEntry = original.getEntry(classEntry);
+      ZipEntry strippedEntry = stripped.getEntry(classEntry);
+      InputStream originalStream = original.getInputStream(originalEntry);
+      InputStream strippedStream = stripped.getInputStream(strippedEntry);
+      assertThat(ByteStreams.toByteArray(strippedStream))
+          .isEqualTo(ByteStreams.toByteArray(originalStream));
+    }
+  }
 }
diff --git a/third_party/ijar/test/jar-with-manifest-and-target-label.jar b/third_party/ijar/test/jar-with-manifest-and-target-label.jar
new file mode 100644
index 0000000..aab7c70
--- /dev/null
+++ b/third_party/ijar/test/jar-with-manifest-and-target-label.jar
Binary files differ
diff --git a/third_party/ijar/test/jar-with-manifest.jar b/third_party/ijar/test/jar-with-manifest.jar
new file mode 100644
index 0000000..7191935
--- /dev/null
+++ b/third_party/ijar/test/jar-with-manifest.jar
Binary files differ
diff --git a/third_party/ijar/test/jar-without-manifest.jar b/third_party/ijar/test/jar-without-manifest.jar
new file mode 100644
index 0000000..d2bd18d
--- /dev/null
+++ b/third_party/ijar/test/jar-without-manifest.jar
Binary files differ