Commit 6297ccf3 by Jamie Madill Committed by Commit Bot

Replace ijar sources with Chromium subtree mirror.

This will ensure a smoother update process. Bug: angleproject:2344 Change-Id: I81bf496ea013825588b1baa573855ce809a8decf Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2842355Reviewed-by: 's avatarYuly Novikov <ynovikov@chromium.org> Commit-Queue: Jamie Madill <jmadill@chromium.org>
parent f78927b0
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
/third_party/gles1_conform /third_party/gles1_conform
/third_party/glmark2/src /third_party/glmark2/src
/third_party/googletest /third_party/googletest
/third_party/ijar
/third_party/jdk/current /third_party/jdk/current
/third_party/jdk/extras/java_8 /third_party/jdk/extras/java_8
/third_party/jinja2 /third_party/jinja2
......
...@@ -347,6 +347,11 @@ deps = { ...@@ -347,6 +347,11 @@ deps = {
'condition': 'not build_with_chromium', 'condition': 'not build_with_chromium',
}, },
'third_party/ijar': {
'url': '{chromium_git}/chromium/src/third_party/ijar@174c5004785b456f247a6535694dec16e0ef1e2b',
'condition': 'checkout_android and not build_with_chromium',
},
# libjpeg_turbo is used by glmark2. # libjpeg_turbo is used by glmark2.
'third_party/libjpeg_turbo': { 'third_party/libjpeg_turbo': {
'url': '{chromium_git}/chromium/deps/libjpeg_turbo.git@7b4981b6500ccba10733c352b9ed2dad14ce3c73', 'url': '{chromium_git}/chromium/deps/libjpeg_turbo.git@7b4981b6500ccba10733c352b9ed2dad14ce3c73',
......
...@@ -55,6 +55,7 @@ ANGLE_CHROMIUM_DEPS = [ ...@@ -55,6 +55,7 @@ ANGLE_CHROMIUM_DEPS = [
'third_party/catapult', 'third_party/catapult',
'third_party/colorama/src', 'third_party/colorama/src',
'third_party/depot_tools', 'third_party/depot_tools',
'third_party/ijar',
'third_party/jdk', 'third_party/jdk',
'third_party/jdk/extras', 'third_party/jdk/extras',
'third_party/jinja2', 'third_party/jinja2',
......
package(
default_visibility = [
"//src:__subpackages__",
"//third_party/ijar:__subpackages__",
],
)
licenses(["notice"]) # Apache 2.0
cc_library(
name = "zip",
srcs = [
"zip.cc",
] + select({
"//src:windows": [
"mapped_file_windows.cc",
],
"//conditions:default": [
"mapped_file_unix.cc",
],
}),
hdrs = [
"common.h",
"mapped_file.h",
"zip.h",
],
visibility = [
"//src:__subpackages__",
"//third_party/ijar:__subpackages__",
"//tools/test:__pkg__",
],
deps = [
":platform_utils",
":zlib_client",
] + select({
"//src:windows": [
"//src/main/cpp/util:errors",
"//src/main/cpp/util:filesystem",
"//src/main/cpp/util:logging",
"//src/main/cpp/util:strings",
],
"//conditions:default": [
],
}),
)
cc_library(
name = "zlib_client",
srcs = ["zlib_client.cc"],
hdrs = [
"common.h",
"zlib_client.h",
],
deps = ["//third_party/zlib"],
)
cc_library(
name = "platform_utils",
srcs = ["platform_utils.cc"],
hdrs = [
"common.h",
"platform_utils.h",
],
visibility = ["//visibility:private"],
deps = [
"//src/main/cpp/util:errors",
"//src/main/cpp/util:filesystem",
"//src/main/cpp/util:logging",
],
)
cc_binary(
name = "zipper",
srcs = ["zip_main.cc"],
visibility = ["//visibility:public"],
deps = [":zip"],
)
cc_binary(
name = "ijar",
srcs = [
"classfile.cc",
"ijar.cc",
],
visibility = ["//visibility:public"],
deps = [":zip"],
)
filegroup(
name = "srcs",
srcs = glob(["**"]) + ["//third_party/ijar/test:srcs"],
visibility = ["//third_party:__pkg__"],
)
filegroup(
name = "embedded_zipper_sources",
srcs = [
"zip.cc",
"zip.h",
"zip_main.cc",
"common.h",
"mapped_file.h",
"platform_utils.cc",
"platform_utils.h",
"zlib_client.cc",
"zlib_client.h",
"BUILD",
] + select({
"//src:windows": [
"mapped_file_windows.cc",
],
"//conditions:default": [
"mapped_file_unix.cc",
],
}),
visibility = ["//visibility:public"],
)
filegroup(
name = "transitive_sources",
srcs = [":srcs"] + ["//src/main/cpp/util:embedded_java_tools"],
visibility = ["//visibility:public"],
)
genrule(
name = "ijar_transitive_zip",
srcs = [
":ijar_srcs_zip",
"//src:zlib_zip",
"//src/main/cpp/util:cpp_util_with_deps_zip",
],
outs = ["ijar_srcs_with_deps.zip"],
cmd = "$(location //src:merge_zip_files) - $@ $(SRCS)",
tools = ["//src:merge_zip_files"],
visibility = ["//visibility:public"],
)
genrule(
name = "ijar_srcs_zip",
srcs = glob(
["**"],
exclude = ["BUILD"],
) + [
":ijar",
":zipper",
],
outs = ["ijar_srcs.zip"],
cmd = "$(location //src:zip_files) ijar $@ $(SRCS)",
tools = ["//src:zip_files"],
visibility = ["//visibility:private"],
)
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# A tool that removes all non-interface-specific parts from a .jar file.
if (is_linux || is_chromeos) {
executable("ijar") {
sources = [
"classfile.cc",
"common.h",
"ijar.cc",
"mapped_file.h",
"mapped_file_unix.cc",
"platform_utils.cc",
"platform_utils.h",
"zip.cc",
"zip.h",
"zlib_client.cc",
"zlib_client.h",
]
deps = [ "//third_party/zlib" ]
# Always build release since this is a build tool.
if (is_debug) {
configs -= [ "//build/config:debug" ]
configs += [ "//build/config:release" ]
}
}
}
monorail: {
component: "Build"
}
team_email: "build@chromium.org"
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
agrieve@chromium.org
dpranke@google.com
Name: ijar
URL: https://github.com/bazelbuild/bazel
Version: b786db0994a4c4c65e3f93a4f6fa268780caff9c
License: Apache 2.0
License File: NOT_SHIPPED
Security Critical: No
Description:
A tool for generating interface .jars from normal .jars.
Local Modifications:
- Removed test directory
- Removed code from platform_utils.cc that referenced "blaze_util".
- Removed mapped_file_windows.cc since it caused checkdeps to fail.
- Added BUILD.gn
ijar: A tool for generating interface .jars from normal .jars
=============================================================
Alan Donovan, 26 May 2007.
Rationale:
In order to improve the speed of compilation of Java programs in
Bazel, the output of build steps is cached.
This works very nicely for C++ compilation: a compilation unit
includes a .cc source file and typically dozens of header files.
Header files change relatively infrequently, so the need for a
rebuild is usually driven by a change in the .cc file. Even after
syncing a slightly newer version of the tree and doing a rebuild,
many hits in the cache are still observed.
In Java, by contrast, a compilation unit involves a set of .java
source files, plus a set of .jar files containing already-compiled
JVM .class files. Class files serve a dual purpose: from the JVM's
perspective, they are containers of executable code, but from the
compiler's perspective, they are interface definitions. The problem
here is that .jar files are very much more sensitive to change than
C++ header files, so even a change that is insignificant to the
compiler (such as the addition of a print statement to a method in a
prerequisite class) will cause the jar to change, and any code that
depends on this jar's interface will be recompiled unnecessarily.
The purpose of ijar is to produce, from a .jar file, a much smaller,
simpler .jar file containing only the parts that are significant for
the purposes of compilation. In other words, an interface .jar
file. By changing ones compilation dependencies to be the interface
jar files, unnecessary recompilation is avoided when upstream
changes don't affect the interface.
Details:
ijar is a tool that reads a .jar file and emits a .jar file
containing only the parts that are relevant to Java compilation.
For example, it throws away:
- Files whose name does not end in ".class".
- All executable method code.
- All private methods and fields.
- All constants and attributes except the minimal set necessary to
describe the class interface.
- All debugging information
(LineNumberTable, SourceFile, LocalVariableTables attributes).
It also sets to zero the file modification times in the index of the
.jar file.
Implementation:
ijar is implemented in C++, and runs very quickly. For example
(when optimized) it takes only 530ms to process a 42MB
.jar file containing 5878 classes, resulting in an interface .jar
file of only 11.4MB in size. For more usual .jar sizes of a few
megabytes, a runtime of 50ms is typical.
The implementation strategy is to mmap both the input jar and the
newly-created _interface.jar, and to scan through the former and
emit the latter in a single pass. There are a couple of locations
where some kind of "backpatching" is required:
- in the .zip file format, for each file, the size field precedes
the data. We emit a zero but note its location, generate and emit
the stripped classfile, then poke the correct size into the
location.
- for JVM .class files, the header (including the constant table)
precedes the body, but cannot be emitted before it because it's
not until we emit the body that we know which constants are
referenced and which are garbage. So we emit the body into a
temporary buffer, then emit the header to the output jar, followed
by the contents of the temp buffer.
Also note that the zip file format has unnecessary duplication of
the index metadata: it has header+data for each file, then another
set of (similar) headers at the end. Rather than save the metadata
explicitly in some datastructure, we just record the addresses of
the already-emitted zip metadata entries in the output file, and
then read from there as necessary.
Notes:
This code has no dependency except on the STL and on zlib.
Almost all of the getX/putX/ReadX/WriteX functions in the code
advance their first argument pointer, which is passed by reference.
It's tempting to discard package-private classes and class members.
However, this would be incorrect because they are a necessary part
of the package interface, as a Java package is often compiled in
multiple stages. For example: in Bazel, both java tests and java
code inhabit the same Java package but are compiled separately.
Assumptions:
We assume that jar files are uncompressed v1.0 zip files (created
with 'jar c0f') with a zero general_purpose_bit_flag.
We assume that javap/javac don't need the correct CRC checksums in
the .jar file.
We assume that it's better simply to abort in the face of unknown
input than to risk leaving out something important from the output
(although in the case of annotations, it should be safe to ignore
ones we don't understand).
TODO:
Maybe: ensure a canonical sort order is used for every list (jar
entries, class members, attributes, etc.) This isn't essential
because we can assume the compiler is deterministic and the order in
the source files changes little. Also, it would require two passes. :(
Maybe: delete dynamically-allocated memory.
Add (a lot) more tests. Include a test of idempotency.
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// classfile.cc -- classfile parsing and stripping.
//
// TODO(adonovan) don't pass pointers by reference; this is not
// compatible with Google C++ style.
// See README.txt for details.
//
// For definition of JVM class file format, see:
// Java SE 8 Edition:
// http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4
#define __STDC_FORMAT_MACROS 1
#define __STDC_LIMIT_MACROS 1
#include <inttypes.h> // for PRIx32
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#include "third_party/ijar/common.h"
namespace
{
// Converts a value to string.
// Workaround for mingw where std::to_string is not implemented.
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52015.
template <typename T>
std::string ToString(const T &value)
{
std::ostringstream oss;
oss << value;
return oss.str();
}
} // namespace
namespace devtools_ijar
{
// See Table 4.3 in JVM Spec.
enum CONSTANT
{
CONSTANT_Class = 7,
CONSTANT_FieldRef = 9,
CONSTANT_Methodref = 10,
CONSTANT_Interfacemethodref = 11,
CONSTANT_String = 8,
CONSTANT_Integer = 3,
CONSTANT_Float = 4,
CONSTANT_Long = 5,
CONSTANT_Double = 6,
CONSTANT_NameAndType = 12,
CONSTANT_Utf8 = 1,
CONSTANT_MethodHandle = 15,
CONSTANT_MethodType = 16,
CONSTANT_InvokeDynamic = 18
};
// See Tables 4.1, 4.4, 4.5 in JVM Spec.
enum ACCESS
{
ACC_PUBLIC = 0x0001,
ACC_PRIVATE = 0x0002,
ACC_PROTECTED = 0x0004,
ACC_STATIC = 0x0008,
ACC_FINAL = 0x0010,
ACC_SYNCHRONIZED = 0x0020,
ACC_BRIDGE = 0x0040,
ACC_VOLATILE = 0x0040,
ACC_TRANSIENT = 0x0080,
ACC_INTERFACE = 0x0200,
ACC_ABSTRACT = 0x0400,
ACC_SYNTHETIC = 0x1000
};
// See Table 4.7.20-A in Java 8 JVM Spec.
enum TARGET_TYPE
{
// Targets for type parameter declarations (ElementType.TYPE_PARAMETER):
CLASS_TYPE_PARAMETER = 0x00,
METHOD_TYPE_PARAMETER = 0x01,
// Targets for type uses that may be externally visible in classes and members
// (ElementType.TYPE_USE):
CLASS_EXTENDS = 0x10,
CLASS_TYPE_PARAMETER_BOUND = 0x11,
METHOD_TYPE_PARAMETER_BOUND = 0x12,
FIELD = 0x13,
METHOD_RETURN = 0x14,
METHOD_RECEIVER = 0x15,
METHOD_FORMAL_PARAMETER = 0x16,
THROWS = 0x17,
// TARGET_TYPE >= 0x40 is reserved for type uses that occur only within code
// blocks. Ijar doesn't need to know about these.
};
struct Constant;
// TODO(adonovan) these globals are unfortunate
static std::vector<Constant *> const_pool_in; // input constant pool
static std::vector<Constant *> const_pool_out; // output constant_pool
static std::set<std::string> used_class_names;
static Constant *class_name;
// Returns the Constant object, given an index into the input constant pool.
// Note: constant(0) == NULL; this invariant is exploited by the
// InnerClassesAttribute, inter alia.
inline Constant *constant(int idx)
{
if (idx < 0 || (unsigned)idx >= const_pool_in.size())
{
fprintf(stderr, "Illegal constant pool index: %d\n", idx);
abort();
}
return const_pool_in[idx];
}
/**********************************************************************
* *
* Constants *
* *
**********************************************************************/
// See sec.4.4 of JVM spec.
struct Constant
{
Constant(u1 tag) : slot_(0), tag_(tag) {}
virtual ~Constant() {}
// For UTF-8 string constants, returns the encoded string.
// Otherwise, returns an undefined string value suitable for debugging.
virtual std::string Display() = 0;
virtual void Write(u1 *&p) = 0;
// Called by slot() when a constant has been identified as required
// in the output classfile's constant pool. This is a hook allowing
// constants to register their dependency on other constants, by
// calling slot() on them in turn.
virtual void Keep() {}
bool Kept() { return slot_ != 0; }
// Returns the index of this constant in the output class's constant
// pool, assigning a slot if not already done.
u2 slot()
{
if (slot_ == 0)
{
Keep();
slot_ = const_pool_out.size(); // BugBot's "narrowing" warning
// is bogus. The number of
// output constants can't exceed
// the number of input constants.
if (slot_ == 0)
{
fprintf(stderr, "Constant::slot() called before output phase.\n");
abort();
}
const_pool_out.push_back(this);
if (tag_ == CONSTANT_Long || tag_ == CONSTANT_Double)
{
const_pool_out.push_back(NULL);
}
}
return slot_;
}
u2 slot_; // zero => "this constant is unreachable garbage"
u1 tag_;
};
// Extracts class names from a signature and puts them into the global
// variable used_class_names.
//
// desc: the descriptor class names should be extracted from.
// p: the position where the extraction should tart.
void ExtractClassNames(const std::string &desc, size_t *p);
// See sec.4.4.1 of JVM spec.
struct Constant_Class : Constant
{
Constant_Class(u2 name_index) : Constant(CONSTANT_Class), name_index_(name_index) {}
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u2be(p, constant(name_index_)->slot());
}
std::string Display() { return constant(name_index_)->Display(); }
void Keep() { constant(name_index_)->slot(); }
u2 name_index_;
};
// See sec.4.4.2 of JVM spec.
struct Constant_FMIref : Constant
{
Constant_FMIref(u1 tag, u2 class_index, u2 name_type_index)
: Constant(tag), class_index_(class_index), name_type_index_(name_type_index)
{}
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u2be(p, constant(class_index_)->slot());
put_u2be(p, constant(name_type_index_)->slot());
}
std::string Display()
{
return constant(class_index_)->Display() + "::" + constant(name_type_index_)->Display();
}
void Keep()
{
constant(class_index_)->slot();
constant(name_type_index_)->slot();
}
u2 class_index_;
u2 name_type_index_;
};
// See sec.4.4.3 of JVM spec.
struct Constant_String : Constant
{
Constant_String(u2 string_index) : Constant(CONSTANT_String), string_index_(string_index) {}
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u2be(p, constant(string_index_)->slot());
}
std::string Display() { return "\"" + constant(string_index_)->Display() + "\""; }
void Keep() { constant(string_index_)->slot(); }
u2 string_index_;
};
// See sec.4.4.4 of JVM spec.
struct Constant_IntegerOrFloat : Constant
{
Constant_IntegerOrFloat(u1 tag, u4 bytes) : Constant(tag), bytes_(bytes) {}
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u4be(p, bytes_);
}
std::string Display() { return "int/float"; }
u4 bytes_;
};
// See sec.4.4.5 of JVM spec.
struct Constant_LongOrDouble : Constant_IntegerOrFloat
{
Constant_LongOrDouble(u1 tag, u4 high_bytes, u4 low_bytes)
: Constant_IntegerOrFloat(tag, high_bytes), low_bytes_(low_bytes)
{}
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u4be(p, bytes_);
put_u4be(p, low_bytes_);
}
std::string Display() { return "long/double"; }
u4 low_bytes_;
};
// See sec.4.4.6 of JVM spec.
struct Constant_NameAndType : Constant
{
Constant_NameAndType(u2 name_index, u2 descr_index)
: Constant(CONSTANT_NameAndType), name_index_(name_index), descr_index_(descr_index)
{}
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u2be(p, constant(name_index_)->slot());
put_u2be(p, constant(descr_index_)->slot());
}
std::string Display()
{
return constant(name_index_)->Display() + "::" + constant(descr_index_)->Display();
}
void Keep()
{
constant(name_index_)->slot();
constant(descr_index_)->slot();
}
u2 name_index_;
u2 descr_index_;
};
// See sec.4.4.7 of JVM spec.
struct Constant_Utf8 : Constant
{
Constant_Utf8(u4 length, const u1 *utf8) : Constant(CONSTANT_Utf8), length_(length), utf8_(utf8)
{}
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u2be(p, length_);
put_n(p, utf8_, length_);
}
std::string Display() { return std::string((const char *)utf8_, length_); }
u4 length_;
const u1 *utf8_;
};
// See sec.4.4.8 of JVM spec.
struct Constant_MethodHandle : Constant
{
Constant_MethodHandle(u1 reference_kind, u2 reference_index)
: Constant(CONSTANT_MethodHandle),
reference_kind_(reference_kind),
reference_index_(reference_index)
{}
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u1(p, reference_kind_);
put_u2be(p, reference_index_);
}
std::string Display()
{
return "Constant_MethodHandle::" + ToString(reference_kind_) +
"::" + constant(reference_index_)->Display();
}
u1 reference_kind_;
u2 reference_index_;
};
// See sec.4.4.9 of JVM spec.
struct Constant_MethodType : Constant
{
Constant_MethodType(u2 descriptor_index)
: Constant(CONSTANT_MethodType), descriptor_index_(descriptor_index)
{}
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u2be(p, descriptor_index_);
}
std::string Display()
{
return "Constant_MethodType::" + constant(descriptor_index_)->Display();
}
u2 descriptor_index_;
};
// See sec.4.4.10 of JVM spec.
struct Constant_InvokeDynamic : Constant
{
Constant_InvokeDynamic(u2 bootstrap_method_attr_index, u2 name_and_type_index)
: Constant(CONSTANT_InvokeDynamic),
bootstrap_method_attr_index_(bootstrap_method_attr_index),
name_and_type_index_(name_and_type_index)
{}
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u2be(p, bootstrap_method_attr_index_);
put_u2be(p, name_and_type_index_);
}
std::string Display()
{
return "Constant_InvokeDynamic::" + ToString(bootstrap_method_attr_index_) +
"::" + constant(name_and_type_index_)->Display();
}
u2 bootstrap_method_attr_index_;
u2 name_and_type_index_;
};
/**********************************************************************
* *
* Attributes *
* *
**********************************************************************/
// See sec.4.7 of JVM spec.
struct Attribute
{
virtual ~Attribute() {}
virtual void Write(u1 *&p) = 0;
virtual void ExtractClassNames() {}
void WriteProlog(u1 *&p, u2 length)
{
put_u2be(p, attribute_name_->slot());
put_u4be(p, length);
}
Constant *attribute_name_;
};
struct KeepForCompileAttribute : Attribute
{
void Write(u1 *&p) { WriteProlog(p, 0); }
};
// See sec.4.7.5 of JVM spec.
struct ExceptionsAttribute : Attribute
{
static ExceptionsAttribute *Read(const u1 *&p, Constant *attribute_name)
{
ExceptionsAttribute *attr = new ExceptionsAttribute;
attr->attribute_name_ = attribute_name;
u2 number_of_exceptions = get_u2be(p);
for (int ii = 0; ii < number_of_exceptions; ++ii)
{
attr->exceptions_.push_back(constant(get_u2be(p)));
}
return attr;
}
void Write(u1 *&p)
{
WriteProlog(p, exceptions_.size() * 2 + 2);
put_u2be(p, exceptions_.size());
for (size_t ii = 0; ii < exceptions_.size(); ++ii)
{
put_u2be(p, exceptions_[ii]->slot());
}
}
std::vector<Constant *> exceptions_;
};
// See sec.4.7.6 of JVM spec.
struct InnerClassesAttribute : Attribute
{
struct Entry
{
Constant *inner_class_info;
Constant *outer_class_info;
Constant *inner_name;
u2 inner_class_access_flags;
};
virtual ~InnerClassesAttribute()
{
for (size_t i = 0; i < entries_.size(); i++)
{
delete entries_[i];
}
}
static InnerClassesAttribute *Read(const u1 *&p, Constant *attribute_name)
{
InnerClassesAttribute *attr = new InnerClassesAttribute;
attr->attribute_name_ = attribute_name;
u2 number_of_classes = get_u2be(p);
for (int ii = 0; ii < number_of_classes; ++ii)
{
Entry *entry = new Entry;
entry->inner_class_info = constant(get_u2be(p));
entry->outer_class_info = constant(get_u2be(p));
entry->inner_name = constant(get_u2be(p));
entry->inner_class_access_flags = get_u2be(p);
attr->entries_.push_back(entry);
}
return attr;
}
void Write(u1 *&p)
{
std::set<int> kept_entries;
// We keep an entry if the constant referring to the inner class is already
// kept. Then we mark its outer class and its class name as kept, too, then
// iterate until a fixed point is reached.
int entry_count;
int iteration = 0;
do
{
entry_count = kept_entries.size();
for (int i_entry = 0; i_entry < static_cast<int>(entries_.size()); ++i_entry)
{
Entry *entry = entries_[i_entry];
if (entry->inner_class_info->Kept() ||
used_class_names.find(entry->inner_class_info->Display()) !=
used_class_names.end() ||
entry->outer_class_info == class_name)
{
if (entry->inner_name == NULL)
{
// JVMS 4.7.6: inner_name_index is zero iff the class is anonymous
continue;
}
kept_entries.insert(i_entry);
// JVMS 4.7.6: outer_class_info_index is zero for top-level classes
if (entry->outer_class_info != NULL)
{
entry->outer_class_info->slot();
}
entry->inner_name->slot();
}
}
iteration += 1;
} while (entry_count != static_cast<int>(kept_entries.size()));
if (kept_entries.empty())
{
return;
}
WriteProlog(p, 2 + kept_entries.size() * 8);
put_u2be(p, kept_entries.size());
for (std::set<int>::iterator it = kept_entries.begin(); it != kept_entries.end(); ++it)
{
Entry *entry = entries_[*it];
put_u2be(p, entry->inner_class_info == NULL ? 0 : entry->inner_class_info->slot());
put_u2be(p, entry->outer_class_info == NULL ? 0 : entry->outer_class_info->slot());
put_u2be(p, entry->inner_name == NULL ? 0 : entry->inner_name->slot());
put_u2be(p, entry->inner_class_access_flags);
}
}
std::vector<Entry *> entries_;
};
// See sec.4.7.7 of JVM spec.
// We preserve EnclosingMethod attributes to be able to identify local and
// anonymous classes. These classes will be stripped of most content, as they
// represent implementation details that shoudn't leak into the ijars. Omitting
// EnclosingMethod attributes can lead to type-checking failures in the presence
// of generics (see b/9070939).
struct EnclosingMethodAttribute : Attribute
{
static EnclosingMethodAttribute *Read(const u1 *&p, Constant *attribute_name)
{
EnclosingMethodAttribute *attr = new EnclosingMethodAttribute;
attr->attribute_name_ = attribute_name;
attr->class_ = constant(get_u2be(p));
attr->method_ = constant(get_u2be(p));
return attr;
}
void Write(u1 *&p)
{
WriteProlog(p, 4);
put_u2be(p, class_->slot());
put_u2be(p, method_ == NULL ? 0 : method_->slot());
}
Constant *class_;
Constant *method_;
};
// See sec.4.7.16.1 of JVM spec.
// Used by AnnotationDefault and other attributes.
struct ElementValue
{
virtual ~ElementValue() {}
virtual void Write(u1 *&p) = 0;
virtual void ExtractClassNames() {}
static ElementValue *Read(const u1 *&p);
u1 tag_;
u4 length_;
};
struct BaseTypeElementValue : ElementValue
{
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u2be(p, const_value_->slot());
}
static BaseTypeElementValue *Read(const u1 *&p)
{
BaseTypeElementValue *value = new BaseTypeElementValue;
value->const_value_ = constant(get_u2be(p));
return value;
}
Constant *const_value_;
};
struct EnumTypeElementValue : ElementValue
{
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u2be(p, type_name_->slot());
put_u2be(p, const_name_->slot());
}
static EnumTypeElementValue *Read(const u1 *&p)
{
EnumTypeElementValue *value = new EnumTypeElementValue;
value->type_name_ = constant(get_u2be(p));
value->const_name_ = constant(get_u2be(p));
return value;
}
Constant *type_name_;
Constant *const_name_;
};
struct ClassTypeElementValue : ElementValue
{
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u2be(p, class_info_->slot());
}
virtual void ExtractClassNames()
{
size_t idx = 0;
devtools_ijar::ExtractClassNames(class_info_->Display(), &idx);
}
static ClassTypeElementValue *Read(const u1 *&p)
{
ClassTypeElementValue *value = new ClassTypeElementValue;
value->class_info_ = constant(get_u2be(p));
return value;
}
Constant *class_info_;
};
struct ArrayTypeElementValue : ElementValue
{
virtual ~ArrayTypeElementValue()
{
for (const auto *value : values_)
{
delete value;
}
}
virtual void ExtractClassNames()
{
for (auto *value : values_)
{
value->ExtractClassNames();
}
}
void Write(u1 *&p)
{
put_u1(p, tag_);
put_u2be(p, values_.size());
for (auto *value : values_)
{
value->Write(p);
}
}
static ArrayTypeElementValue *Read(const u1 *&p)
{
ArrayTypeElementValue *value = new ArrayTypeElementValue;
u2 num_values = get_u2be(p);
for (int ii = 0; ii < num_values; ++ii)
{
value->values_.push_back(ElementValue::Read(p));
}
return value;
}
std::vector<ElementValue *> values_;
};
// See sec.4.7.16 of JVM spec.
struct Annotation
{
virtual ~Annotation()
{
for (size_t i = 0; i < element_value_pairs_.size(); i++)
{
delete element_value_pairs_[i]->element_value_;
delete element_value_pairs_[i];
}
}
void ExtractClassNames()
{
for (size_t i = 0; i < element_value_pairs_.size(); i++)
{
element_value_pairs_[i]->element_value_->ExtractClassNames();
}
}
void Write(u1 *&p)
{
put_u2be(p, type_->slot());
put_u2be(p, element_value_pairs_.size());
for (size_t ii = 0; ii < element_value_pairs_.size(); ++ii)
{
put_u2be(p, element_value_pairs_[ii]->element_name_->slot());
element_value_pairs_[ii]->element_value_->Write(p);
}
}
static Annotation *Read(const u1 *&p)
{
Annotation *value = new Annotation;
value->type_ = constant(get_u2be(p));
u2 num_element_value_pairs = get_u2be(p);
for (int ii = 0; ii < num_element_value_pairs; ++ii)
{
ElementValuePair *pair = new ElementValuePair;
pair->element_name_ = constant(get_u2be(p));
pair->element_value_ = ElementValue::Read(p);
value->element_value_pairs_.push_back(pair);
}
return value;
}
Constant *type_;
struct ElementValuePair
{
Constant *element_name_;
ElementValue *element_value_;
};
std::vector<ElementValuePair *> element_value_pairs_;
};
// See sec 4.7.20 of Java 8 JVM Spec
//
// Each entry in the annotations table represents a single run-time visible
// annotation on a type used in a declaration or expression. The type_annotation
// structure has the following format:
//
// type_annotation {
// u1 target_type;
// union {
// type_parameter_target;
// supertype_target;
// type_parameter_bound_target;
// empty_target;
// method_formal_parameter_target;
// throws_target;
// localvar_target;
// catch_target;
// offset_target;
// type_argument_target;
// } target_info;
// type_path target_path;
// u2 type_index;
// u2 num_element_value_pairs;
// {
// u2 element_name_index;
// element_value value;
// }
// element_value_pairs[num_element_value_pairs];
// }
//
struct TypeAnnotation
{
virtual ~TypeAnnotation()
{
delete target_info_;
delete type_path_;
delete annotation_;
}
void ExtractClassNames() { annotation_->ExtractClassNames(); }
void Write(u1 *&p)
{
put_u1(p, target_type_);
target_info_->Write(p);
type_path_->Write(p);
annotation_->Write(p);
}
static TypeAnnotation *Read(const u1 *&p)
{
TypeAnnotation *value = new TypeAnnotation;
value->target_type_ = get_u1(p);
value->target_info_ = ReadTargetInfo(p, value->target_type_);
value->type_path_ = TypePath::Read(p);
value->annotation_ = Annotation::Read(p);
return value;
}
struct TargetInfo
{
virtual ~TargetInfo() {}
virtual void Write(u1 *&p) = 0;
};
struct TypeParameterTargetInfo : TargetInfo
{
void Write(u1 *&p) { put_u1(p, type_parameter_index_); }
static TypeParameterTargetInfo *Read(const u1 *&p)
{
TypeParameterTargetInfo *value = new TypeParameterTargetInfo;
value->type_parameter_index_ = get_u1(p);
return value;
}
u1 type_parameter_index_;
};
struct ClassExtendsInfo : TargetInfo
{
void Write(u1 *&p) { put_u2be(p, supertype_index_); }
static ClassExtendsInfo *Read(const u1 *&p)
{
ClassExtendsInfo *value = new ClassExtendsInfo;
value->supertype_index_ = get_u2be(p);
return value;
}
u2 supertype_index_;
};
struct TypeParameterBoundInfo : TargetInfo
{
void Write(u1 *&p)
{
put_u1(p, type_parameter_index_);
put_u1(p, bound_index_);
}
static TypeParameterBoundInfo *Read(const u1 *&p)
{
TypeParameterBoundInfo *value = new TypeParameterBoundInfo;
value->type_parameter_index_ = get_u1(p);
value->bound_index_ = get_u1(p);
return value;
}
u1 type_parameter_index_;
u1 bound_index_;
};
struct EmptyInfo : TargetInfo
{
void Write(u1 *& /*p*/) {}
static EmptyInfo *Read(const u1 *& /*p*/) { return new EmptyInfo; }
};
struct MethodFormalParameterInfo : TargetInfo
{
void Write(u1 *&p) { put_u1(p, method_formal_parameter_index_); }
static MethodFormalParameterInfo *Read(const u1 *&p)
{
MethodFormalParameterInfo *value = new MethodFormalParameterInfo;
value->method_formal_parameter_index_ = get_u1(p);
return value;
}
u1 method_formal_parameter_index_;
};
struct ThrowsTypeInfo : TargetInfo
{
void Write(u1 *&p) { put_u2be(p, throws_type_index_); }
static ThrowsTypeInfo *Read(const u1 *&p)
{
ThrowsTypeInfo *value = new ThrowsTypeInfo;
value->throws_type_index_ = get_u2be(p);
return value;
}
u2 throws_type_index_;
};
static TargetInfo *ReadTargetInfo(const u1 *&p, u1 target_type)
{
switch (target_type)
{
case CLASS_TYPE_PARAMETER:
case METHOD_TYPE_PARAMETER:
return TypeParameterTargetInfo::Read(p);
case CLASS_EXTENDS:
return ClassExtendsInfo::Read(p);
case CLASS_TYPE_PARAMETER_BOUND:
case METHOD_TYPE_PARAMETER_BOUND:
return TypeParameterBoundInfo::Read(p);
case FIELD:
case METHOD_RETURN:
case METHOD_RECEIVER:
return new EmptyInfo;
case METHOD_FORMAL_PARAMETER:
return MethodFormalParameterInfo::Read(p);
case THROWS:
return ThrowsTypeInfo::Read(p);
default:
fprintf(stderr, "Illegal type annotation target type: %d\n", target_type);
abort();
}
}
struct TypePath
{
void Write(u1 *&p)
{
put_u1(p, path_.size());
for (TypePathEntry entry : path_)
{
put_u1(p, entry.type_path_kind_);
put_u1(p, entry.type_argument_index_);
}
}
static TypePath *Read(const u1 *&p)
{
TypePath *value = new TypePath;
u1 path_length = get_u1(p);
for (int ii = 0; ii < path_length; ++ii)
{
TypePathEntry entry;
entry.type_path_kind_ = get_u1(p);
entry.type_argument_index_ = get_u1(p);
value->path_.push_back(entry);
}
return value;
}
struct TypePathEntry
{
u1 type_path_kind_;
u1 type_argument_index_;
};
std::vector<TypePathEntry> path_;
};
u1 target_type_;
TargetInfo *target_info_;
TypePath *type_path_;
Annotation *annotation_;
};
struct AnnotationTypeElementValue : ElementValue
{
virtual ~AnnotationTypeElementValue() { delete annotation_; }
void Write(u1 *&p)
{
put_u1(p, tag_);
annotation_->Write(p);
}
static AnnotationTypeElementValue *Read(const u1 *&p)
{
AnnotationTypeElementValue *value = new AnnotationTypeElementValue;
value->annotation_ = Annotation::Read(p);
return value;
}
Annotation *annotation_;
};
ElementValue *ElementValue::Read(const u1 *&p)
{
const u1 *start = p;
ElementValue *result;
u1 tag = get_u1(p);
if (tag != 0 && strchr("BCDFIJSZs", (char)tag) != NULL)
{
result = BaseTypeElementValue::Read(p);
}
else if ((char)tag == 'e')
{
result = EnumTypeElementValue::Read(p);
}
else if ((char)tag == 'c')
{
result = ClassTypeElementValue::Read(p);
}
else if ((char)tag == '[')
{
result = ArrayTypeElementValue::Read(p);
}
else if ((char)tag == '@')
{
result = AnnotationTypeElementValue::Read(p);
}
else
{
fprintf(stderr, "Illegal element_value::tag: %d\n", tag);
abort();
}
result->tag_ = tag;
result->length_ = p - start;
return result;
}
// See sec.4.7.20 of JVM spec.
// We preserve AnnotationDefault attributes because they are required
// in order to make use of an annotation in new code.
struct AnnotationDefaultAttribute : Attribute
{
virtual ~AnnotationDefaultAttribute() { delete default_value_; }
static AnnotationDefaultAttribute *Read(const u1 *&p, Constant *attribute_name)
{
AnnotationDefaultAttribute *attr = new AnnotationDefaultAttribute;
attr->attribute_name_ = attribute_name;
attr->default_value_ = ElementValue::Read(p);
return attr;
}
void Write(u1 *&p)
{
WriteProlog(p, default_value_->length_);
default_value_->Write(p);
}
virtual void ExtractClassNames() { default_value_->ExtractClassNames(); }
ElementValue *default_value_;
};
// See sec.4.7.2 of JVM spec.
// We preserve ConstantValue attributes because they are required for
// compile-time constant propagation.
struct ConstantValueAttribute : Attribute
{
static ConstantValueAttribute *Read(const u1 *&p, Constant *attribute_name)
{
ConstantValueAttribute *attr = new ConstantValueAttribute;
attr->attribute_name_ = attribute_name;
attr->constantvalue_ = constant(get_u2be(p));
return attr;
}
void Write(u1 *&p)
{
WriteProlog(p, 2);
put_u2be(p, constantvalue_->slot());
}
Constant *constantvalue_;
};
// See sec.4.7.9 of JVM spec.
// We preserve Signature attributes because they are required by the
// compiler for type-checking of generics.
struct SignatureAttribute : Attribute
{
static SignatureAttribute *Read(const u1 *&p, Constant *attribute_name)
{
SignatureAttribute *attr = new SignatureAttribute;
attr->attribute_name_ = attribute_name;
attr->signature_ = constant(get_u2be(p));
return attr;
}
void Write(u1 *&p)
{
WriteProlog(p, 2);
put_u2be(p, signature_->slot());
}
virtual void ExtractClassNames()
{
size_t signature_idx = 0;
devtools_ijar::ExtractClassNames(signature_->Display(), &signature_idx);
}
Constant *signature_;
};
// See sec.4.7.15 of JVM spec.
// We preserve Deprecated attributes because they are required by the
// compiler to generate warning messages.
struct DeprecatedAttribute : Attribute
{
static DeprecatedAttribute *Read(const u1 *& /*p*/, Constant *attribute_name)
{
DeprecatedAttribute *attr = new DeprecatedAttribute;
attr->attribute_name_ = attribute_name;
return attr;
}
void Write(u1 *&p) { WriteProlog(p, 0); }
};
// See sec.4.7.16-17 of JVM spec v3. Includes RuntimeVisible and
// RuntimeInvisible.
//
// We preserve all annotations.
struct AnnotationsAttribute : Attribute
{
virtual ~AnnotationsAttribute()
{
for (size_t i = 0; i < annotations_.size(); i++)
{
delete annotations_[i];
}
}
static AnnotationsAttribute *Read(const u1 *&p, Constant *attribute_name)
{
AnnotationsAttribute *attr = new AnnotationsAttribute;
attr->attribute_name_ = attribute_name;
u2 num_annotations = get_u2be(p);
for (int ii = 0; ii < num_annotations; ++ii)
{
Annotation *annotation = Annotation::Read(p);
attr->annotations_.push_back(annotation);
}
return attr;
}
virtual void ExtractClassNames()
{
for (auto *annotation : annotations_)
{
annotation->ExtractClassNames();
}
}
void Write(u1 *&p)
{
WriteProlog(p, -1);
u1 *payload_start = p - 4;
put_u2be(p, annotations_.size());
for (auto *annotation : annotations_)
{
annotation->Write(p);
}
put_u4be(payload_start, p - 4 - payload_start); // backpatch length
}
std::vector<Annotation *> annotations_;
};
// See sec.4.7.18-19 of JVM spec. Includes RuntimeVisible and
// RuntimeInvisible.
//
// We preserve all annotations.
struct ParameterAnnotationsAttribute : Attribute
{
static ParameterAnnotationsAttribute *Read(const u1 *&p, Constant *attribute_name)
{
ParameterAnnotationsAttribute *attr = new ParameterAnnotationsAttribute;
attr->attribute_name_ = attribute_name;
u1 num_parameters = get_u1(p);
for (int ii = 0; ii < num_parameters; ++ii)
{
std::vector<Annotation *> annotations;
u2 num_annotations = get_u2be(p);
for (int ii = 0; ii < num_annotations; ++ii)
{
Annotation *annotation = Annotation::Read(p);
annotations.push_back(annotation);
}
attr->parameter_annotations_.push_back(annotations);
}
return attr;
}
virtual void ExtractClassNames()
{
for (size_t i = 0; i < parameter_annotations_.size(); i++)
{
const std::vector<Annotation *> &annotations = parameter_annotations_[i];
for (size_t j = 0; j < annotations.size(); j++)
{
annotations[j]->ExtractClassNames();
}
}
}
void Write(u1 *&p)
{
WriteProlog(p, -1);
u1 *payload_start = p - 4;
put_u1(p, parameter_annotations_.size());
for (size_t ii = 0; ii < parameter_annotations_.size(); ++ii)
{
std::vector<Annotation *> &annotations = parameter_annotations_[ii];
put_u2be(p, annotations.size());
for (size_t jj = 0; jj < annotations.size(); ++jj)
{
annotations[jj]->Write(p);
}
}
put_u4be(payload_start, p - 4 - payload_start); // backpatch length
}
std::vector<std::vector<Annotation *>> parameter_annotations_;
};
// See sec.4.7.20 of Java 8 JVM spec. Includes RuntimeVisibleTypeAnnotations
// and RuntimeInvisibleTypeAnnotations.
struct TypeAnnotationsAttribute : Attribute
{
static TypeAnnotationsAttribute *Read(const u1 *&p,
Constant *attribute_name,
u4 /*attribute_length*/)
{
auto attr = new TypeAnnotationsAttribute;
attr->attribute_name_ = attribute_name;
u2 num_annotations = get_u2be(p);
for (int ii = 0; ii < num_annotations; ++ii)
{
TypeAnnotation *annotation = TypeAnnotation::Read(p);
attr->type_annotations_.push_back(annotation);
}
return attr;
}
virtual void ExtractClassNames()
{
for (auto *type_annotation : type_annotations_)
{
type_annotation->ExtractClassNames();
}
}
void Write(u1 *&p)
{
WriteProlog(p, -1);
u1 *payload_start = p - 4;
put_u2be(p, type_annotations_.size());
for (TypeAnnotation *annotation : type_annotations_)
{
annotation->Write(p);
}
put_u4be(payload_start, p - 4 - payload_start); // backpatch length
}
std::vector<TypeAnnotation *> type_annotations_;
};
// See JVMS §4.7.24
struct MethodParametersAttribute : Attribute
{
static MethodParametersAttribute *Read(const u1 *&p,
Constant *attribute_name,
u4 /*attribute_length*/)
{
auto attr = new MethodParametersAttribute;
attr->attribute_name_ = attribute_name;
u1 parameters_count = get_u1(p);
for (int ii = 0; ii < parameters_count; ++ii)
{
MethodParameter *parameter = new MethodParameter;
parameter->name_ = constant(get_u2be(p));
parameter->access_flags_ = get_u2be(p);
attr->parameters_.push_back(parameter);
}
return attr;
}
void Write(u1 *&p)
{
WriteProlog(p, -1);
u1 *payload_start = p - 4;
put_u1(p, parameters_.size());
for (MethodParameter *parameter : parameters_)
{
put_u2be(p, parameter->name_->slot());
put_u2be(p, parameter->access_flags_);
}
put_u4be(payload_start, p - 4 - payload_start); // backpatch length
}
struct MethodParameter
{
Constant *name_;
u2 access_flags_;
};
std::vector<MethodParameter *> parameters_;
};
// See JVMS §4.7.28
struct NestHostAttribute : Attribute
{
static NestHostAttribute *Read(const u1 *&p, Constant *attribute_name, u4 /*attribute_length*/)
{
auto attr = new NestHostAttribute;
attr->attribute_name_ = attribute_name;
attr->host_class_index_ = constant(get_u2be(p));
return attr;
}
void Write(u1 *&p)
{
WriteProlog(p, 2);
put_u2be(p, host_class_index_->slot());
}
Constant *host_class_index_;
};
// See JVMS §4.7.29
struct NestMembersAttribute : Attribute
{
static NestMembersAttribute *Read(const u1 *&p,
Constant *attribute_name,
u4 /*attribute_length*/)
{
auto attr = new NestMembersAttribute;
attr->attribute_name_ = attribute_name;
u2 number_of_classes = get_u2be(p);
for (int ii = 0; ii < number_of_classes; ++ii)
{
attr->classes_.push_back(constant(get_u2be(p)));
}
return attr;
}
void Write(u1 *&p)
{
WriteProlog(p, classes_.size() * 2 + 2);
put_u2be(p, classes_.size());
for (size_t ii = 0; ii < classes_.size(); ++ii)
{
put_u2be(p, classes_[ii]->slot());
}
}
std::vector<Constant *> classes_;
};
struct GeneralAttribute : Attribute
{
static GeneralAttribute *Read(const u1 *&p, Constant *attribute_name, u4 attribute_length)
{
auto attr = new GeneralAttribute;
attr->attribute_name_ = attribute_name;
attr->attribute_length_ = attribute_length;
attr->attribute_content_ = p;
p += attribute_length;
return attr;
}
void Write(u1 *&p)
{
WriteProlog(p, attribute_length_);
put_n(p, attribute_content_, attribute_length_);
}
u4 attribute_length_;
const u1 *attribute_content_;
};
/**********************************************************************
* *
* ClassFile *
* *
**********************************************************************/
struct HasAttrs
{
std::vector<Attribute *> attributes;
void WriteAttrs(u1 *&p);
void ReadAttrs(const u1 *&p);
virtual ~HasAttrs()
{
for (const auto *attribute : attributes)
{
delete attribute;
}
}
void ExtractClassNames()
{
for (auto *attribute : attributes)
{
attribute->ExtractClassNames();
}
}
};
// A field or method.
// See sec.4.5 and 4.6 of JVM spec.
struct Member : HasAttrs
{
u2 access_flags;
Constant *name;
Constant *descriptor;
static Member *Read(const u1 *&p)
{
Member *m = new Member;
m->access_flags = get_u2be(p);
m->name = constant(get_u2be(p));
m->descriptor = constant(get_u2be(p));
m->ReadAttrs(p);
return m;
}
void Write(u1 *&p)
{
put_u2be(p, access_flags);
put_u2be(p, name->slot());
put_u2be(p, descriptor->slot());
WriteAttrs(p);
}
};
// See sec.4.1 of JVM spec.
struct ClassFile : HasAttrs
{
size_t length;
// Header:
u4 magic;
u2 major;
u2 minor;
// Body:
u2 access_flags;
Constant *this_class;
Constant *super_class;
std::vector<Constant *> interfaces;
std::vector<Member *> fields;
std::vector<Member *> methods;
virtual ~ClassFile()
{
for (size_t i = 0; i < fields.size(); i++)
{
delete fields[i];
}
for (size_t i = 0; i < methods.size(); i++)
{
delete methods[i];
}
// Constants do not need to be deleted; they are owned by the constant pool.
}
void WriteClass(u1 *&p);
bool ReadConstantPool(const u1 *&p);
bool IsExplicitlyKept();
bool IsLocalOrAnonymous();
void WriteHeader(u1 *&p)
{
put_u4be(p, magic);
put_u2be(p, major);
put_u2be(p, minor);
put_u2be(p, const_pool_out.size());
for (u2 ii = 1; ii < const_pool_out.size(); ++ii)
{
if (const_pool_out[ii] != NULL)
{ // NB: NULLs appear after long/double.
const_pool_out[ii]->Write(p);
}
}
}
void WriteBody(u1 *&p)
{
put_u2be(p, access_flags);
put_u2be(p, this_class->slot());
put_u2be(p, super_class == NULL ? 0 : super_class->slot());
put_u2be(p, interfaces.size());
for (size_t ii = 0; ii < interfaces.size(); ++ii)
{
put_u2be(p, interfaces[ii]->slot());
}
put_u2be(p, fields.size());
for (size_t ii = 0; ii < fields.size(); ++ii)
{
fields[ii]->Write(p);
}
put_u2be(p, methods.size());
for (size_t ii = 0; ii < methods.size(); ++ii)
{
methods[ii]->Write(p);
}
Attribute *inner_classes = NULL;
// Make the inner classes attribute the last, so that it can know which
// constants were needed
for (size_t ii = 0; ii < attributes.size(); ii++)
{
if (attributes[ii]->attribute_name_->Display() == "InnerClasses")
{
inner_classes = attributes[ii];
attributes.erase(attributes.begin() + ii);
break;
}
}
if (inner_classes != NULL)
{
attributes.push_back(inner_classes);
}
WriteAttrs(p);
}
};
void HasAttrs::ReadAttrs(const u1 *&p)
{
u2 attributes_count = get_u2be(p);
for (int ii = 0; ii < attributes_count; ii++)
{
Constant *attribute_name = constant(get_u2be(p));
u4 attribute_length = get_u4be(p);
std::string attr_name = attribute_name->Display();
if (attr_name == "SourceFile" || attr_name == "StackMapTable" ||
attr_name == "LineNumberTable" || attr_name == "LocalVariableTable" ||
attr_name == "LocalVariableTypeTable" || attr_name == "Code" ||
attr_name == "Synthetic" || attr_name == "BootstrapMethods" ||
attr_name == "SourceDebugExtension")
{
p += attribute_length; // drop these attributes
}
else if (attr_name == "Exceptions")
{
attributes.push_back(ExceptionsAttribute::Read(p, attribute_name));
}
else if (attr_name == "Signature")
{
attributes.push_back(SignatureAttribute::Read(p, attribute_name));
}
else if (attr_name == "Deprecated")
{
attributes.push_back(DeprecatedAttribute::Read(p, attribute_name));
}
else if (attr_name == "EnclosingMethod")
{
attributes.push_back(EnclosingMethodAttribute::Read(p, attribute_name));
}
else if (attr_name == "InnerClasses")
{
// TODO(bazel-team): omit private inner classes
attributes.push_back(InnerClassesAttribute::Read(p, attribute_name));
}
else if (attr_name == "AnnotationDefault")
{
attributes.push_back(AnnotationDefaultAttribute::Read(p, attribute_name));
}
else if (attr_name == "ConstantValue")
{
attributes.push_back(ConstantValueAttribute::Read(p, attribute_name));
}
else if (attr_name == "RuntimeVisibleAnnotations" ||
attr_name == "RuntimeInvisibleAnnotations")
{
attributes.push_back(AnnotationsAttribute::Read(p, attribute_name));
}
else if (attr_name == "RuntimeVisibleParameterAnnotations" ||
attr_name == "RuntimeInvisibleParameterAnnotations")
{
attributes.push_back(ParameterAnnotationsAttribute::Read(p, attribute_name));
}
else if (attr_name == "Scala" || attr_name == "ScalaSig" || attr_name == "ScalaInlineInfo")
{
// These are opaque blobs, so can be handled with a general
// attribute handler
attributes.push_back(GeneralAttribute::Read(p, attribute_name, attribute_length));
}
else if (attr_name == "RuntimeVisibleTypeAnnotations" ||
attr_name == "RuntimeInvisibleTypeAnnotations")
{
attributes.push_back(
TypeAnnotationsAttribute::Read(p, attribute_name, attribute_length));
}
else if (attr_name == "MethodParameters")
{
attributes.push_back(
MethodParametersAttribute::Read(p, attribute_name, attribute_length));
}
else if (attr_name == "NestHost")
{
attributes.push_back(NestHostAttribute::Read(p, attribute_name, attribute_length));
}
else if (attr_name == "NestMembers")
{
attributes.push_back(NestMembersAttribute::Read(p, attribute_name, attribute_length));
}
else if (attr_name == "com.google.devtools.ijar.KeepForCompile")
{
auto attr = new KeepForCompileAttribute;
attr->attribute_name_ = attribute_name;
attributes.push_back(attr);
}
else
{
// Skip over unknown attributes with a warning. The JVM spec
// says this is ok, so long as we handle the mandatory attributes.
fprintf(stderr, "ijar: skipping unknown attribute: \"%s\".\n", attr_name.c_str());
p += attribute_length;
}
}
}
void HasAttrs::WriteAttrs(u1 *&p)
{
u1 *p_size = p;
put_u2be(p, 0);
int n_written_attrs = 0;
for (size_t ii = 0; ii < attributes.size(); ii++)
{
u1 *before = p;
attributes[ii]->Write(p);
if (p != before)
{
n_written_attrs++;
}
}
put_u2be(p_size, n_written_attrs);
}
// See sec.4.4 of JVM spec.
bool ClassFile::ReadConstantPool(const u1 *&p)
{
const_pool_in.clear();
const_pool_in.push_back(NULL); // dummy first item
u2 cp_count = get_u2be(p);
for (int ii = 1; ii < cp_count; ++ii)
{
u1 tag = get_u1(p);
if (devtools_ijar::verbose)
{
fprintf(stderr, "cp[%d/%d] = tag %d\n", ii, cp_count, tag);
}
switch (tag)
{
case CONSTANT_Class:
{
u2 name_index = get_u2be(p);
const_pool_in.push_back(new Constant_Class(name_index));
break;
}
case CONSTANT_FieldRef:
case CONSTANT_Methodref:
case CONSTANT_Interfacemethodref:
{
u2 class_index = get_u2be(p);
u2 nti = get_u2be(p);
const_pool_in.push_back(new Constant_FMIref(tag, class_index, nti));
break;
}
case CONSTANT_String:
{
u2 string_index = get_u2be(p);
const_pool_in.push_back(new Constant_String(string_index));
break;
}
case CONSTANT_NameAndType:
{
u2 name_index = get_u2be(p);
u2 descriptor_index = get_u2be(p);
const_pool_in.push_back(new Constant_NameAndType(name_index, descriptor_index));
break;
}
case CONSTANT_Utf8:
{
u2 length = get_u2be(p);
if (devtools_ijar::verbose)
{
fprintf(stderr, "Utf8: \"%s\" (%d)\n",
std::string((const char *)p, length).c_str(), length);
}
const_pool_in.push_back(new Constant_Utf8(length, p));
p += length;
break;
}
case CONSTANT_Integer:
case CONSTANT_Float:
{
u4 bytes = get_u4be(p);
const_pool_in.push_back(new Constant_IntegerOrFloat(tag, bytes));
break;
}
case CONSTANT_Long:
case CONSTANT_Double:
{
u4 high_bytes = get_u4be(p);
u4 low_bytes = get_u4be(p);
const_pool_in.push_back(new Constant_LongOrDouble(tag, high_bytes, low_bytes));
// Longs and doubles occupy two constant pool slots.
// ("In retrospect, making 8-byte constants take two "constant
// pool entries was a poor choice." --JVM Spec.)
const_pool_in.push_back(NULL);
ii++;
break;
}
case CONSTANT_MethodHandle:
{
u1 reference_kind = get_u1(p);
u2 reference_index = get_u2be(p);
const_pool_in.push_back(new Constant_MethodHandle(reference_kind, reference_index));
break;
}
case CONSTANT_MethodType:
{
u2 descriptor_index = get_u2be(p);
const_pool_in.push_back(new Constant_MethodType(descriptor_index));
break;
}
case CONSTANT_InvokeDynamic:
{
u2 bootstrap_method_attr = get_u2be(p);
u2 name_name_type_index = get_u2be(p);
const_pool_in.push_back(
new Constant_InvokeDynamic(bootstrap_method_attr, name_name_type_index));
break;
}
default:
{
fprintf(stderr, "Unknown constant: %02x. Passing class through.\n", tag);
return false;
}
}
}
return true;
}
bool ClassFile::IsLocalOrAnonymous()
{
for (const Attribute *attribute : attributes)
{
if (attribute->attribute_name_->Display() == "EnclosingMethod")
{
// JVMS 4.7.6: a class must has EnclosingMethod attribute iff it
// represents a local class or an anonymous class
return true;
}
}
return false;
}
static bool HasKeepForCompile(const std::vector<Attribute *> attributes)
{
for (const Attribute *attribute : attributes)
{
if (attribute->attribute_name_->Display() == "com.google.devtools.ijar.KeepForCompile")
{
return true;
}
}
return false;
}
bool ClassFile::IsExplicitlyKept()
{
if (HasKeepForCompile(attributes))
{
return true;
}
for (const Member *method : methods)
{
if (HasKeepForCompile(method->attributes))
{
return true;
}
}
return false;
}
static ClassFile *ReadClass(const void *classdata, size_t length)
{
const u1 *p = (u1 *)classdata;
ClassFile *clazz = new ClassFile;
clazz->length = length;
clazz->magic = get_u4be(p);
if (clazz->magic != 0xCAFEBABE)
{
fprintf(stderr, "Bad magic %" PRIx32 "\n", clazz->magic);
abort();
}
clazz->major = get_u2be(p);
clazz->minor = get_u2be(p);
if (!clazz->ReadConstantPool(p))
{
delete clazz;
return NULL;
}
clazz->access_flags = get_u2be(p);
clazz->this_class = constant(get_u2be(p));
class_name = clazz->this_class;
u2 super_class_id = get_u2be(p);
clazz->super_class = super_class_id == 0 ? NULL : constant(super_class_id);
u2 interfaces_count = get_u2be(p);
for (int ii = 0; ii < interfaces_count; ++ii)
{
clazz->interfaces.push_back(constant(get_u2be(p)));
}
u2 fields_count = get_u2be(p);
for (int ii = 0; ii < fields_count; ++ii)
{
Member *field = Member::Read(p);
if ((field->access_flags & ACC_PRIVATE) == ACC_PRIVATE)
{
// drop private fields
continue;
}
clazz->fields.push_back(field);
}
u2 methods_count = get_u2be(p);
for (int ii = 0; ii < methods_count; ++ii)
{
Member *method = Member::Read(p);
if (HasKeepForCompile(method->attributes))
{
// Always keep methods marked as such
clazz->methods.push_back(method);
continue;
}
// drop class initializers
if (method->name->Display() == "<clinit>")
continue;
if ((method->access_flags & ACC_PRIVATE) == ACC_PRIVATE)
{
// drop private methods
continue;
}
if ((method->access_flags & (ACC_SYNTHETIC | ACC_BRIDGE | ACC_PUBLIC | ACC_PROTECTED)) ==
ACC_SYNTHETIC)
{
// drop package-private non-bridge synthetic methods, e.g. synthetic
// constructors used to instantiate private nested classes within their
// declaring compilation unit
continue;
}
clazz->methods.push_back(method);
}
clazz->ReadAttrs(p);
return clazz;
}
// In theory, '/' is also reserved, but it's okay if we just parse package
// identifiers as part of the class name. Note that signatures are UTF-8, but
// this works just as well as in plain ASCII.
static const char *SIGNATURE_NON_IDENTIFIER_CHARS = ".;[<>:";
void Expect(const std::string &desc, size_t *p, char expected)
{
if (desc[*p] != expected)
{
fprintf(stderr, "Expected '%c' in '%s' at %zd in signature\n", expected,
desc.substr(*p).c_str(), *p);
exit(1);
}
*p += 1;
}
// These functions form a crude recursive descent parser for descriptors and
// signatures in class files (see JVM spec 4.3).
//
// This parser is a bit more liberal than the spec, but this should be fine,
// because it accepts all valid class files and croaks only on invalid ones.
void ParseFromClassTypeSignature(const std::string &desc, size_t *p);
void ParseSimpleClassTypeSignature(const std::string &desc, size_t *p);
void ParseClassTypeSignatureSuffix(const std::string &desc, size_t *p);
void ParseIdentifier(const std::string &desc, size_t *p);
void ParseTypeArgumentsOpt(const std::string &desc, size_t *p);
void ParseMethodDescriptor(const std::string &desc, size_t *p);
void ParseClassTypeSignature(const std::string &desc, size_t *p)
{
Expect(desc, p, 'L');
ParseSimpleClassTypeSignature(desc, p);
ParseClassTypeSignatureSuffix(desc, p);
Expect(desc, p, ';');
}
void ParseSimpleClassTypeSignature(const std::string &desc, size_t *p)
{
ParseIdentifier(desc, p);
ParseTypeArgumentsOpt(desc, p);
}
void ParseClassTypeSignatureSuffix(const std::string &desc, size_t *p)
{
while (desc[*p] == '.')
{
*p += 1;
ParseSimpleClassTypeSignature(desc, p);
}
}
void ParseIdentifier(const std::string &desc, size_t *p)
{
size_t next = desc.find_first_of(SIGNATURE_NON_IDENTIFIER_CHARS, *p);
std::string id = desc.substr(*p, next - *p);
used_class_names.insert(id);
*p = next;
}
void ParseTypeArgumentsOpt(const std::string &desc, size_t *p)
{
if (desc[*p] != '<')
{
return;
}
*p += 1;
while (desc[*p] != '>')
{
switch (desc[*p])
{
case '*':
*p += 1;
break;
case '+':
case '-':
*p += 1;
ExtractClassNames(desc, p);
break;
default:
ExtractClassNames(desc, p);
break;
}
}
*p += 1;
}
void ParseMethodDescriptor(const std::string &desc, size_t *p)
{
Expect(desc, p, '(');
while (desc[*p] != ')')
{
ExtractClassNames(desc, p);
}
Expect(desc, p, ')');
ExtractClassNames(desc, p);
}
void ParseFormalTypeParameters(const std::string &desc, size_t *p)
{
Expect(desc, p, '<');
while (desc[*p] != '>')
{
ParseIdentifier(desc, p);
Expect(desc, p, ':');
if (desc[*p] != ':' && desc[*p] != '>')
{
ExtractClassNames(desc, p);
}
while (desc[*p] == ':')
{
Expect(desc, p, ':');
ExtractClassNames(desc, p);
}
}
Expect(desc, p, '>');
}
void ExtractClassNames(const std::string &desc, size_t *p)
{
switch (desc[*p])
{
case '<':
ParseFormalTypeParameters(desc, p);
ExtractClassNames(desc, p);
break;
case 'L':
ParseClassTypeSignature(desc, p);
break;
case '[':
*p += 1;
ExtractClassNames(desc, p);
break;
case 'T':
*p += 1;
ParseIdentifier(desc, p);
Expect(desc, p, ';');
break;
case '(':
ParseMethodDescriptor(desc, p);
break;
case 'B':
case 'C':
case 'D':
case 'F':
case 'I':
case 'J':
case 'S':
case 'Z':
case 'V':
*p += 1;
break;
default:
fprintf(stderr, "Invalid signature %s\n", desc.substr(*p).c_str());
}
}
void ClassFile::WriteClass(u1 *&p)
{
used_class_names.clear();
std::vector<Member *> members;
members.insert(members.end(), fields.begin(), fields.end());
members.insert(members.end(), methods.begin(), methods.end());
ExtractClassNames();
for (auto *member : members)
{
size_t idx = 0;
devtools_ijar::ExtractClassNames(member->descriptor->Display(), &idx);
member->ExtractClassNames();
}
// We have to write the body out before the header in order to reference
// the essential constants and populate the output constant pool:
u1 *body = new u1[length];
u1 *q = body;
WriteBody(q); // advances q
u4 body_length = q - body;
WriteHeader(p); // advances p
put_n(p, body, body_length);
delete[] body;
}
bool StripClass(u1 *&classdata_out, const u1 *classdata_in, size_t in_length)
{
ClassFile *clazz = ReadClass(classdata_in, in_length);
bool keep = true;
if (clazz == NULL || clazz->IsExplicitlyKept())
{
// Class is invalid or kept. Simply copy it to the output and call it a day.
// TODO: If kept, only emit methods marked with KeepForCompile attribute,
// as opposed to the entire type.
put_n(classdata_out, classdata_in, in_length);
}
else if (clazz->IsLocalOrAnonymous())
{
keep = false;
}
else
{
// Constant pool item zero is a dummy entry. Setting it marks the
// beginning of the output phase; calls to Constant::slot() will
// fail if called prior to this.
const_pool_out.push_back(NULL);
clazz->WriteClass(classdata_out);
delete clazz;
}
// Now clean up all the mess we left behind.
for (size_t i = 0; i < const_pool_in.size(); i++)
{
delete const_pool_in[i];
}
const_pool_in.clear();
const_pool_out.clear();
return keep;
}
} // namespace devtools_ijar
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// common.h -- common definitions.
//
#ifndef INCLUDED_DEVTOOLS_IJAR_COMMON_H
#define INCLUDED_DEVTOOLS_IJAR_COMMON_H
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#ifdef _WIN32
# define PATH_MAX 4096
typedef int mode_t;
#endif // _WIN32
namespace devtools_ijar
{
typedef unsigned long long u8;
typedef uint32_t u4;
typedef uint16_t u2;
typedef uint8_t u1;
// be = big endian, le = little endian
inline u1 get_u1(const u1 *&p)
{
return *p++;
}
inline u2 get_u2be(const u1 *&p)
{
u4 x = (p[0] << 8) | p[1];
p += 2;
return x;
}
inline u2 get_u2le(const u1 *&p)
{
u4 x = (p[1] << 8) | p[0];
p += 2;
return x;
}
inline u4 get_u4be(const u1 *&p)
{
u4 x = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
p += 4;
return x;
}
inline u4 get_u4le(const u1 *&p)
{
u4 x = (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0];
p += 4;
return x;
}
inline u8 get_u8le(const u1 *&p)
{
u4 lo = get_u4le(p);
u4 hi = get_u4le(p);
u8 x = ((u8)hi << 32) | lo;
return x;
}
inline void put_u1(u1 *&p, u1 x)
{
*p++ = x;
}
inline void put_u2be(u1 *&p, u2 x)
{
*p++ = x >> 8;
*p++ = x & 0xff;
}
inline void put_u2le(u1 *&p, u2 x)
{
*p++ = x & 0xff;
*p++ = x >> 8;
;
}
inline void put_u4be(u1 *&p, u4 x)
{
*p++ = x >> 24;
*p++ = (x >> 16) & 0xff;
*p++ = (x >> 8) & 0xff;
*p++ = x & 0xff;
}
inline void put_u4le(u1 *&p, u4 x)
{
*p++ = x & 0xff;
*p++ = (x >> 8) & 0xff;
*p++ = (x >> 16) & 0xff;
*p++ = x >> 24;
}
inline void put_u8le(u1 *&p, u8 x)
{
put_u4le(p, x & 0xffffffff);
put_u4le(p, (x >> 32) & 0xffffffff);
}
// Copy n bytes from src to p, and advance p.
inline void put_n(u1 *&p, const u1 *src, size_t n)
{
memcpy(p, src, n);
p += n;
}
extern bool verbose;
} // namespace devtools_ijar
#endif // INCLUDED_DEVTOOLS_IJAR_COMMON_H
// Copyright 2016 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <algorithm>
#include "third_party/ijar/common.h"
#include "third_party/ijar/zlib_client.h"
namespace devtools_ijar
{
u4 ComputeCrcChecksum(u1 *buf, size_t length)
{
return 0;
}
size_t TryDeflate(u1 *buf, size_t length)
{
return 0;
}
Decompressor::Decompressor() {}
Decompressor::~Decompressor() {}
DecompressedFile *Decompressor::UncompressFile(const u1 *buffer, size_t bytes_avail)
{
return NULL;
}
char *Decompressor::GetError()
{
return NULL;
}
int Decompressor::error(const char *fmt, ...)
{
return 0;
}
} // namespace devtools_ijar
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ijar.cpp -- .jar -> _interface.jar tool.
//
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory>
#include "third_party/ijar/zip.h"
namespace devtools_ijar
{
bool verbose = false;
// 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);
const char *CLASS_EXTENSION = ".class";
const size_t CLASS_EXTENSION_LENGTH = strlen(CLASS_EXTENSION);
const char *KOTLIN_MODULE_EXTENSION = ".kotlin_module";
const size_t KOTLIN_MODULE_EXTENSION_LENGTH = strlen(KOTLIN_MODULE_EXTENSION);
const char *MANIFEST_DIR_PATH = "META-INF/";
const size_t MANIFEST_DIR_PATH_LENGTH = strlen(MANIFEST_DIR_PATH);
const char *MANIFEST_PATH = "META-INF/MANIFEST.MF";
const size_t MANIFEST_PATH_LENGTH = strlen(MANIFEST_PATH);
const char *MANIFEST_HEADER =
"Manifest-Version: 1.0\r\n"
"Created-By: bazel\r\n";
const size_t MANIFEST_HEADER_LENGTH = strlen(MANIFEST_HEADER);
// These attributes are used by JavaBuilder, Turbine, and ijar.
// They must all be kept in sync.
const char *TARGET_LABEL_KEY = "Target-Label: ";
const size_t TARGET_LABEL_KEY_LENGTH = strlen(TARGET_LABEL_KEY);
const char *INJECTING_RULE_KIND_KEY = "Injecting-Rule-Kind: ";
const size_t INJECTING_RULE_KIND_KEY_LENGTH = strlen(INJECTING_RULE_KIND_KEY);
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; }
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);
};
static bool StartsWith(const char *str,
const size_t str_len,
const char *prefix,
const size_t prefix_len)
{
return str_len >= prefix_len && strncmp(str, prefix, prefix_len) == 0;
}
static bool EndsWith(const char *str,
const size_t str_len,
const char *suffix,
const size_t suffix_len)
{
return str_len >= suffix_len && strcmp(str + str_len - suffix_len, suffix) == 0;
}
static bool IsKotlinModule(const char *filename, const size_t filename_len)
{
return StartsWith(filename, filename_len, MANIFEST_DIR_PATH, MANIFEST_DIR_PATH_LENGTH) &&
EndsWith(filename, filename_len, KOTLIN_MODULE_EXTENSION,
KOTLIN_MODULE_EXTENSION_LENGTH);
}
bool JarStripperProcessor::Accept(const char *filename, const u4 /*attr*/)
{
const size_t filename_len = strlen(filename);
if (IsKotlinModule(filename, filename_len))
{
return true;
}
if (filename_len < CLASS_EXTENSION_LENGTH ||
strcmp(filename + filename_len - CLASS_EXTENSION_LENGTH, CLASS_EXTENSION) != 0)
{
return false;
}
return true;
}
static bool IsModuleInfo(const char *filename)
{
const char *slash = strrchr(filename, '/');
if (slash == NULL)
{
slash = filename;
}
else
{
slash++;
}
return strcmp(slash, "module-info.class") == 0;
}
void JarStripperProcessor::Process(const char *filename,
const u4 /*attr*/,
const u1 *data,
const size_t size)
{
if (verbose)
{
fprintf(stderr, "INFO: StripClass: %s\n", filename);
}
if (IsModuleInfo(filename) || IsKotlinModule(filename, strlen(filename)))
{
u1 *q = builder_->NewFile(filename, 0);
memcpy(q, data, size);
builder_->FinishFile(size, /* compress: */ false, /* compute_crc: */ true);
}
else
{
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);
size_t out_length = buf - classdata_out;
memcpy(q, classdata_out, out_length);
builder_->FinishFile(out_length, /* compress: */ false,
/* compute_crc: */ true);
free(classdata_out);
}
}
// 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 buf + len;
}
// 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 == nullptr)
{
return 0;
}
// local headers
size_t length = 30 * 2 + MANIFEST_DIR_PATH_LENGTH + MANIFEST_PATH_LENGTH;
// central directory
length += 46 * 2 + MANIFEST_DIR_PATH_LENGTH + MANIFEST_PATH_LENGTH;
// zip64 EOCD entries
length += 56 * 2;
// manifest content
length += MANIFEST_HEADER_LENGTH;
// target label manifest entry, including newline
length += TARGET_LABEL_KEY_LENGTH + strlen(target_label) + 2;
if (injecting_rule_kind)
{
// injecting rule kind manifest entry, including newline
length += INJECTING_RULE_KIND_KEY_LENGTH + strlen(injecting_rule_kind) + 2;
}
return length;
}
// 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,
bool strip_jar,
const char *target_label,
const char *injecting_rule_kind)
{
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 == NULL)
{
fprintf(stderr, "Unable to open Zip file %s: %s\n", file_in, strerror(errno));
abort();
}
u8 output_length =
in->CalculateOutputLength() + EstimateManifestOutputSize(target_label, injecting_rule_kind);
std::unique_ptr<ZipBuilder> out(ZipBuilder::Create(file_out, output_length));
if (out == NULL)
{
fprintf(stderr, "Unable to open output file %s: %s\n", file_out, strerror(errno));
abort();
}
processor->SetZipBuilder(out.get());
processor->WriteManifest(target_label, injecting_rule_kind);
// Process all files in the zip
if (in->ProcessAll() < 0)
{
fprintf(stderr, "%s\n", in->GetError());
abort();
}
// Add dummy file, since javac doesn't like truly empty jars.
if (out->GetNumberFiles() == 0)
{
out->WriteEmptyFile("dummy");
}
// Finish writing the output file
if (out->Finish() < 0)
{
fprintf(stderr, "%s\n", out->GetError());
abort();
}
// Get all file size
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));
}
}
} // namespace devtools_ijar
//
// main method
//
static void usage()
{
fprintf(stderr,
"Usage: ijar "
"[-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;
const char *filename_out = NULL;
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();
}
target_label = argv[ii];
}
else if (strcmp(argv[ii], "--injecting_rule_kind") == 0)
{
if (++ii >= argc)
{
usage();
}
injecting_rule_kind = argv[ii];
}
else if (filename_in == NULL)
{
filename_in = argv[ii];
}
else if (filename_out == NULL)
{
filename_out = argv[ii];
}
else
{
usage();
}
}
if (filename_in == NULL)
{
usage();
}
// Guess output filename from input:
char filename_out_buf[PATH_MAX];
if (filename_out == NULL)
{
size_t len = strlen(filename_in);
if (len > 4 && strncmp(filename_in + len - 4, ".jar", 4) == 0)
{
strcpy(filename_out_buf, filename_in);
strcpy(filename_out_buf + len - 4, "-interface.jar");
filename_out = filename_out_buf;
}
else
{
fprintf(stderr,
"Can't determine output filename since input filename "
"doesn't end with '.jar'.\n");
return 1;
}
}
if (devtools_ijar::verbose)
{
fprintf(stderr, "INFO: writing to '%s'.\n", filename_out);
}
devtools_ijar::OpenFilesAndProcessJar(filename_out, filename_in, strip_jar, target_label,
injecting_rule_kind);
return 0;
}
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef INCLUDED_THIRD_PARTY_IJAR_MAPPED_FILE_H
#define INCLUDED_THIRD_PARTY_IJAR_MAPPED_FILE_H
#include "third_party/ijar/common.h"
namespace devtools_ijar
{
struct MappedInputFileImpl;
struct MappedOutputFileImpl;
// A memory mapped input file.
class MappedInputFile
{
private:
MappedInputFileImpl *impl_;
protected:
const char *errmsg_;
bool opened_;
u1 *buffer_;
size_t length_;
public:
MappedInputFile(const char *name);
virtual ~MappedInputFile();
// If opening the file succeeded or not.
bool Opened() const { return opened_; }
// Description of the last error that happened.
const char *Error() const { return errmsg_; }
// The mapped contents of the file.
u1 *Buffer() const { return buffer_; }
// The length of the file.
size_t Length() const { return length_; }
// Unmap a given number of bytes from the beginning of the file.
void Discard(size_t bytes);
int Close();
};
class MappedOutputFile
{
private:
MappedOutputFileImpl *impl_;
protected:
const char *errmsg_;
bool opened_;
u1 *buffer_;
size_t estimated_size_;
public:
MappedOutputFile(const char *name, size_t estimated_size);
virtual ~MappedOutputFile();
// If opening the file succeeded or not.
bool Opened() const { return opened_; }
// Description of the last error that happened.
const char *Error() const { return errmsg_; }
// The mapped contents of the file.
u1 *Buffer() const { return buffer_; }
int Close(size_t size);
};
} // namespace devtools_ijar
#endif
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <algorithm>
#include "third_party/ijar/mapped_file.h"
#define MAX_ERROR 2048
namespace devtools_ijar
{
static char errmsg[MAX_ERROR];
struct MappedInputFileImpl
{
size_t discarded_;
int fd_;
};
MappedInputFile::MappedInputFile(const char *name)
{
impl_ = NULL;
opened_ = false;
int fd = open(name, O_RDONLY);
if (fd < 0)
{
snprintf(errmsg, MAX_ERROR, "open(): %s", strerror(errno));
errmsg_ = errmsg;
return;
}
off_t length = lseek(fd, 0, SEEK_END);
if (length < 0)
{
snprintf(errmsg, MAX_ERROR, "lseek(): %s", strerror(errno));
errmsg_ = errmsg;
return;
}
void *buffer = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
if (buffer == MAP_FAILED)
{
snprintf(errmsg, MAX_ERROR, "mmap(): %s", strerror(errno));
errmsg_ = errmsg;
return;
}
impl_ = new MappedInputFileImpl();
impl_->fd_ = fd;
impl_->discarded_ = 0;
buffer_ = reinterpret_cast<u1 *>(buffer);
length_ = length;
opened_ = true;
}
MappedInputFile::~MappedInputFile()
{
delete impl_;
}
void MappedInputFile::Discard(size_t bytes)
{
munmap(buffer_ + impl_->discarded_, bytes);
impl_->discarded_ += bytes;
}
int MappedInputFile::Close()
{
if (close(impl_->fd_) < 0)
{
snprintf(errmsg, MAX_ERROR, "close(): %s", strerror(errno));
errmsg_ = errmsg;
return -1;
}
return 0;
}
struct MappedOutputFileImpl
{
int fd_;
int mmap_length_;
};
MappedOutputFile::MappedOutputFile(const char *name, size_t estimated_size)
: estimated_size_(estimated_size)
{
impl_ = NULL;
opened_ = false;
int fd = open(name, O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd < 0)
{
snprintf(errmsg, MAX_ERROR, "open(): %s", strerror(errno));
errmsg_ = errmsg;
return;
}
// Create mmap-able sparse file
if (ftruncate(fd, estimated_size) < 0)
{
snprintf(errmsg, MAX_ERROR, "ftruncate(): %s", strerror(errno));
errmsg_ = errmsg;
return;
}
// Ensure that any buffer overflow in JarStripper will result in
// SIGSEGV or SIGBUS by over-allocating beyond the end of the file.
size_t mmap_length = std::min(static_cast<size_t>(estimated_size + sysconf(_SC_PAGESIZE)),
std::numeric_limits<size_t>::max());
void *mapped = mmap(NULL, mmap_length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mapped == MAP_FAILED)
{
snprintf(errmsg, MAX_ERROR, "mmap(): %s", strerror(errno));
errmsg_ = errmsg;
return;
}
impl_ = new MappedOutputFileImpl();
impl_->fd_ = fd;
impl_->mmap_length_ = mmap_length;
buffer_ = reinterpret_cast<u1 *>(mapped);
opened_ = true;
}
MappedOutputFile::~MappedOutputFile()
{
delete impl_;
}
int MappedOutputFile::Close(size_t size)
{
if (size > estimated_size_)
{
snprintf(errmsg, MAX_ERROR, "size %zu > estimated size %zu", size, estimated_size_);
errmsg_ = errmsg;
return -1;
}
munmap(buffer_, impl_->mmap_length_);
if (ftruncate(impl_->fd_, size) < 0)
{
snprintf(errmsg, MAX_ERROR, "ftruncate(): %s", strerror(errno));
errmsg_ = errmsg;
return -1;
}
if (close(impl_->fd_) < 0)
{
snprintf(errmsg, MAX_ERROR, "close(): %s", strerror(errno));
errmsg_ = errmsg;
return -1;
}
return 0;
}
} // namespace devtools_ijar
// Copyright 2016 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "third_party/ijar/platform_utils.h"
#include <limits.h>
#include <stdio.h>
#if defined(_WIN32) || defined(__CYGWIN__)
# include <windows.h>
#else // !(defined(_WIN32) || defined(__CYGWIN__))
# include <sys/stat.h>
# include <sys/types.h>
# include <unistd.h>
#endif // defined(_WIN32) || defined(__CYGWIN__)
#include <string>
namespace devtools_ijar
{
using std::string;
bool stat_file(const char *path, Stat *result)
{
#if defined(_WIN32) || defined(__CYGWIN__)
std::wstring wpath;
std::string error;
bool success = false;
BY_HANDLE_FILE_INFORMATION info;
HANDLE handle = ::CreateFileW(
/* lpFileName */ wpath.c_str(),
/* dwDesiredAccess */ GENERIC_READ,
/* dwShareMode */ FILE_SHARE_READ,
/* lpSecurityAttributes */ NULL,
/* dwCreationDisposition */ OPEN_EXISTING,
/* dwFlagsAndAttributes */ FILE_ATTRIBUTE_NORMAL,
/* hTemplateFile */ NULL);
if (handle == INVALID_HANDLE_VALUE)
{
// Opening it as a file failed, try opening it as a directory.
handle = ::CreateFileW(
/* lpFileName */ wpath.c_str(),
/* dwDesiredAccess */ GENERIC_READ,
/* dwShareMode */ FILE_SHARE_READ,
/* lpSecurityAttributes */ NULL,
/* dwCreationDisposition */ OPEN_EXISTING,
/* dwFlagsAndAttributes */ FILE_FLAG_BACKUP_SEMANTICS,
/* hTemplateFile */ NULL);
}
if (handle != INVALID_HANDLE_VALUE && ::GetFileInformationByHandle(handle, &info))
{
success = true;
bool is_dir = (info.dwFileAttributes != INVALID_FILE_ATTRIBUTES) &&
(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
// TODO(laszlocsomor): use info.nFileSizeHigh after we updated total_size to
// be u8 type.
result->total_size = is_dir ? 0 : info.nFileSizeLow;
// TODO(laszlocsomor): query the actual permissions and write in file_mode.
result->file_mode = 0777;
result->is_directory = is_dir;
}
::CloseHandle(handle);
return success;
#else // !(defined(_WIN32) || defined(__CYGWIN__))
struct stat statst;
if (stat(path, &statst) < 0)
{
return false;
}
result->total_size = statst.st_size;
result->file_mode = statst.st_mode;
result->is_directory = (statst.st_mode & S_IFDIR) != 0;
return true;
#endif
}
} // namespace devtools_ijar
// Copyright 2016 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef THIRD_PARTY_IJAR_PLATFORM_UTILS_H_
#define THIRD_PARTY_IJAR_PLATFORM_UTILS_H_
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
#include "third_party/ijar/common.h"
namespace devtools_ijar
{
// Platform-independent stat data.
struct Stat
{
// Total size of the file in bytes.
int total_size;
// The Unix file mode from the stat.st_mode field.
mode_t file_mode;
// True if this is a directory.
bool is_directory;
};
// Converts a Stat object to ZIP attributes.
inline u4 stat_to_zipattr(const Stat &file_stat)
{
return (((u4)file_stat.file_mode) << 16) | (file_stat.is_directory != 0 ? 0x10 : 0);
}
// Writes stat data into `result` about the file under `path`.
// Returns true if file is found and can be stat'ed.
// Returns false if the file is not found or cannot be stat'ed.
// Doesn't report any errors because it can also be used to simply check if a
// file exists.
bool stat_file(const char *path, Stat *result);
// Writes `size` bytes from `data` into file under `path`.
// The file is created or overwritten and is set to have `perm` permissions.
// Returns true upon success: file is created and all data is written.
// Returns false upon failure and reports the error to stderr.
bool write_file(const char *path, unsigned int perm, const void *data, size_t size);
// Reads at most `size` bytes into `buffer` from the file under `path`.
// Returns true upon success: file is opened and all data is read.
// Returns false upon failure and reports the error to stderr.
bool read_file(const char *path, void *buffer, size_t size);
// Returns the current working directory.
// Returns the empty string upon failure and reports the error to stderr.
std::string get_cwd();
// Do a recursive mkdir of all folders of path except the last path
// segment (if path ends with a / then the last path segment is empty).
// All folders are created using "perm" for creation mode, and are writable and
// openable by the current user.
// Returns true if all directories were created and permissions set.
// Returns false upon failure and reports the error to stderr.
bool make_dirs(const char *path, unsigned int perm);
} // namespace devtools_ijar
#endif // THIRD_PARTY_IJAR_PLATFORM_UTILS_H_
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// zip.cc -- .zip (.jar) file reading/writing routines.
//
// See README.txt for details.
//
// See http://www.pkware.com/documents/casestudies/APPNOTE.TXT
// for definition of PKZIP file format.
#define _FILE_OFFSET_BITS 64 // Support zip files larger than 2GB
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits>
#include <vector>
#include "third_party/ijar/mapped_file.h"
#include "third_party/ijar/platform_utils.h"
#include "third_party/ijar/zip.h"
#include "third_party/ijar/zlib_client.h"
#define LOCAL_FILE_HEADER_SIGNATURE 0x04034b50
#define CENTRAL_FILE_HEADER_SIGNATURE 0x02014b50
#define UNIX_ZIP_FILE_VERSION 0x0300
#define DIGITAL_SIGNATURE 0x05054b50
#define ZIP64_EOCD_SIGNATURE 0x06064b50
#define ZIP64_EOCD_LOCATOR_SIGNATURE 0x07064b50
#define EOCD_SIGNATURE 0x06054b50
#define DATA_DESCRIPTOR_SIGNATURE 0x08074b50
#define U2_MAX 0xffff
#define U4_MAX 0xffffffffUL
#define ZIP64_EOCD_LOCATOR_SIZE 20
// zip64 eocd is fixed size in the absence of a zip64 extensible data sector
#define ZIP64_EOCD_FIXED_SIZE 56
// version to extract: 1.0 - default value from APPNOTE.TXT.
// Output JAR files contain no extra ZIP features, so this is enough.
#define ZIP_VERSION_TO_EXTRACT 10
#define COMPRESSION_METHOD_STORED 0 // no compression
#define COMPRESSION_METHOD_DEFLATED 8
#define GENERAL_PURPOSE_BIT_FLAG_COMPRESSED (1 << 3)
#define GENERAL_PURPOSE_BIT_FLAG_UTF8_ENCODED (1 << 11)
#define GENERAL_PURPOSE_BIT_FLAG_COMPRESSION_SPEED ((1 << 2) | (1 << 1))
#define GENERAL_PURPOSE_BIT_FLAG_SUPPORTED \
(GENERAL_PURPOSE_BIT_FLAG_COMPRESSED | GENERAL_PURPOSE_BIT_FLAG_UTF8_ENCODED | \
GENERAL_PURPOSE_BIT_FLAG_COMPRESSION_SPEED)
namespace devtools_ijar
{
// In the absence of ZIP64 support, zip files are limited to 4GB.
// http://www.info-zip.org/FAQ.html#limits
static const size_t kMaximumOutputSize = std::numeric_limits<uint32_t>::max();
static const u4 kDefaultTimestamp = 30 << 25 | 1 << 21 | 1 << 16; // January 1, 2010 in DOS time
//
// A class representing a ZipFile for reading. Its public API is exposed
// using the ZipExtractor abstract class.
//
class InputZipFile : public ZipExtractor
{
public:
InputZipFile(ZipExtractorProcessor *processor, const char *filename);
virtual ~InputZipFile();
virtual const char *GetError()
{
if (errmsg[0] == 0)
{
return NULL;
}
return errmsg;
}
bool Open();
virtual bool ProcessNext();
virtual void Reset();
virtual size_t GetSize() { return input_file_->Length(); }
virtual u8 CalculateOutputLength();
virtual bool ProcessCentralDirEntry(const u1 *&p,
size_t *compressed_size,
size_t *uncompressed_size,
char *filename,
size_t filename_size,
u4 *attr,
u4 *offset);
private:
ZipExtractorProcessor *processor;
const char *filename_;
MappedInputFile *input_file_;
// InputZipFile is responsible for maintaining the following
// pointers. They are allocated by the Create() method before
// the object is actually created using mmap.
const u1 *zipdata_in_; // start of input file mmap
size_t bytes_unmapped_; // bytes that have already been unmapped
const u1 *central_dir_; // central directory in input file
size_t in_offset_; // offset the input file
const u1 *p; // input cursor
const u1 *central_dir_current_; // central dir input cursor
// Buffer size is initially INITIAL_BUFFER_SIZE. It doubles in size every
// time it is found too small, until it reaches MAX_BUFFER_SIZE. If that is
// not enough, we bail out. We only decompress class files, so they should
// be smaller than 64K anyway, but we give a little leeway.
// MAX_BUFFER_SIZE must be bigger than the size of the biggest file in the
// ZIP. It is set to 2GB here because no one has audited the code for 64-bit
// cleanliness.
static constexpr size_t INITIAL_BUFFER_SIZE = 256 * 1024; // 256K
static constexpr size_t MAX_BUFFER_SIZE = std::numeric_limits<int32_t>::max();
static constexpr size_t MAX_MAPPED_REGION = 32 * 1024 * 1024;
// These metadata fields are the fields of the ZIP header of the file being
// processed.
u2 extract_version_;
u2 general_purpose_bit_flag_;
u2 compression_method_;
u4 uncompressed_size_;
u4 compressed_size_;
u2 file_name_length_;
u2 extra_field_length_;
const u1 *file_name_;
const u1 *extra_field_;
// Copy of the last filename entry - Null-terminated.
char filename[PATH_MAX];
// The external file attribute field
u4 attr;
// last error
char errmsg[4 * PATH_MAX];
Decompressor *decompressor_;
int error(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsnprintf(errmsg, 4 * PATH_MAX, fmt, ap);
va_end(ap);
return -1;
}
// Check that at least n bytes remain in the input file, otherwise
// abort with an error message. "state" is the name of the field
// we're about to read, for diagnostics.
int EnsureRemaining(size_t n, const char *state)
{
size_t in_offset = p - zipdata_in_;
size_t remaining = input_file_->Length() - in_offset;
if (n > remaining)
{
return error(
"Premature end of file (at offset %zd, state=%s); "
"expected %zd more bytes but found %zd.\n",
in_offset, state, n, remaining);
}
return 0;
}
// Read one entry from input zip file
int ProcessLocalFileEntry(size_t compressed_size, size_t uncompressed_size);
// Uncompress a file from the archive using zlib. The pointer returned
// is owned by InputZipFile, so it must not be freed. Advances the input
// cursor to the first byte after the compressed data.
u1 *UncompressFile();
// Skip a file
int SkipFile(const bool compressed);
// Process a file
int ProcessFile(const bool compressed);
};
//
// A class implementing ZipBuilder that represent an open zip file for writing.
//
class OutputZipFile : public ZipBuilder
{
public:
OutputZipFile(const char *filename, size_t estimated_size)
: output_file_(NULL), filename_(filename), estimated_size_(estimated_size), finished_(false)
{
errmsg[0] = 0;
}
virtual const char *GetError()
{
if (errmsg[0] == 0)
{
return NULL;
}
return errmsg;
}
virtual ~OutputZipFile() { Finish(); }
virtual u1 *NewFile(const char *filename, const u4 attr);
virtual int FinishFile(size_t filelength, bool compress = false, bool compute_crc = false);
virtual int WriteEmptyFile(const char *filename);
virtual size_t GetSize() { return Offset(q); }
virtual int GetNumberFiles() { return entries_.size(); }
virtual int Finish();
bool Open();
private:
struct LocalFileEntry
{
// Start of the local header (in the output buffer).
size_t local_header_offset;
// Sizes of the file entry
size_t uncompressed_length;
size_t compressed_length;
// Compression method
u2 compression_method;
// CRC32
u4 crc32;
// external attributes field
u4 external_attr;
// Start/length of the file_name in the local header.
u1 *file_name;
u2 file_name_length;
// Start/length of the extra_field in the local header.
const u1 *extra_field;
u2 extra_field_length;
};
MappedOutputFile *output_file_;
const char *filename_;
size_t estimated_size_;
bool finished_;
// OutputZipFile is responsible for maintaining the following
// pointers. They are allocated by the Create() method before
// the object is actually created using mmap.
u1 *zipdata_out_; // start of output file mmap
u1 *q; // output cursor
u1 *header_ptr; // Current pointer to "compression method" entry.
// List of entries to write the central directory
std::vector<LocalFileEntry *> entries_;
// last error
char errmsg[4 * PATH_MAX];
int error(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsnprintf(errmsg, 4 * PATH_MAX, fmt, ap);
va_end(ap);
return -1;
}
// Write the ZIP central directory structure for each local file
// entry in "entries".
void WriteCentralDirectory();
// Returns the offset of the pointer relative to the start of the
// output zip file.
size_t Offset(const u1 *const x) { return x - zipdata_out_; }
// Write ZIP file header in the output. Since the compressed size is not
// known in advance, it must be recorded later. This method returns a pointer
// to "compressed size" in the file header that should be passed to
// WriteFileSizeInLocalFileHeader() later.
u1 *WriteLocalFileHeader(const char *filename, const u4 attr);
// Fill in the "compressed size" and "uncompressed size" fields in a local
// file header previously written by WriteLocalFileHeader().
size_t WriteFileSizeInLocalFileHeader(u1 *header_ptr,
size_t out_length,
bool compress = false,
const u4 crc = 0);
};
//
// Implementation of InputZipFile
//
bool InputZipFile::ProcessNext()
{
// Process the next entry in the central directory. Also make sure that the
// content pointer is in sync.
size_t compressed, uncompressed;
u4 offset;
if (!ProcessCentralDirEntry(central_dir_current_, &compressed, &uncompressed, filename,
PATH_MAX, &attr, &offset))
{
return false;
}
// There might be an offset specified in the central directory that does
// not match the file offset, so always update our pointer.
p = zipdata_in_ + in_offset_ + offset;
if (EnsureRemaining(4, "signature") < 0)
{
return false;
}
u4 signature = get_u4le(p);
if (signature == LOCAL_FILE_HEADER_SIGNATURE)
{
if (ProcessLocalFileEntry(compressed, uncompressed) < 0)
{
return false;
}
}
else
{
error("local file header signature for file %s not found\n", filename);
return false;
}
return true;
}
int InputZipFile::ProcessLocalFileEntry(size_t compressed_size, size_t uncompressed_size)
{
if (EnsureRemaining(26, "extract_version") < 0)
{
return -1;
}
extract_version_ = get_u2le(p);
general_purpose_bit_flag_ = get_u2le(p);
if ((general_purpose_bit_flag_ & ~GENERAL_PURPOSE_BIT_FLAG_SUPPORTED) != 0)
{
return error("Unsupported value (0x%04x) in general purpose bit flag.\n",
general_purpose_bit_flag_);
}
compression_method_ = get_u2le(p);
if (compression_method_ != COMPRESSION_METHOD_DEFLATED &&
compression_method_ != COMPRESSION_METHOD_STORED)
{
return error("Unsupported compression method (%d).\n", compression_method_);
}
// skip over: last_mod_file_time, last_mod_file_date, crc32
p += 2 + 2 + 4;
compressed_size_ = get_u4le(p);
uncompressed_size_ = get_u4le(p);
file_name_length_ = get_u2le(p);
extra_field_length_ = get_u2le(p);
if (EnsureRemaining(file_name_length_, "file_name") < 0)
{
return -1;
}
file_name_ = p;
p += file_name_length_;
if (EnsureRemaining(extra_field_length_, "extra_field") < 0)
{
return -1;
}
extra_field_ = p;
p += extra_field_length_;
bool is_compressed = compression_method_ == COMPRESSION_METHOD_DEFLATED;
// If the zip is compressed, compressed and uncompressed size members are
// zero in the local file header. If not, check that they are the same as the
// lengths from the central directory, otherwise, just believe the central
// directory
if (compressed_size_ == 0)
{
compressed_size_ = compressed_size;
}
else
{
if (compressed_size_ != compressed_size)
{
return error("central directory and file header inconsistent\n");
}
}
if (uncompressed_size_ == 0)
{
uncompressed_size_ = uncompressed_size;
}
else
{
if (uncompressed_size_ != uncompressed_size)
{
return error("central directory and file header inconsistent\n");
}
}
if (processor->Accept(filename, attr))
{
if (ProcessFile(is_compressed) < 0)
{
return -1;
}
}
else
{
if (SkipFile(is_compressed) < 0)
{
return -1;
}
}
if (general_purpose_bit_flag_ & GENERAL_PURPOSE_BIT_FLAG_COMPRESSED)
{
// Skip the data descriptor. Some implementations do not put the signature
// here, so check if the next 4 bytes are a signature, and if so, skip the
// next 12 bytes (for CRC, compressed/uncompressed size), otherwise skip
// the next 8 bytes (because the value just read was the CRC).
u4 signature = get_u4le(p);
if (signature == DATA_DESCRIPTOR_SIGNATURE)
{
p += 4 * 3;
}
else
{
p += 4 * 2;
}
}
size_t bytes_processed = p - zipdata_in_;
if (bytes_processed > bytes_unmapped_ + MAX_MAPPED_REGION)
{
input_file_->Discard(MAX_MAPPED_REGION);
bytes_unmapped_ += MAX_MAPPED_REGION;
}
return 0;
}
int InputZipFile::SkipFile(const bool compressed)
{
if (!compressed)
{
// In this case, compressed_size_ == uncompressed_size_ (since the file is
// uncompressed), so we can use either.
if (compressed_size_ != uncompressed_size_)
{
return error(
"compressed size != uncompressed size, although the file "
"is uncompressed.\n");
}
}
if (EnsureRemaining(compressed_size_, "file_data") < 0)
{
return -1;
}
p += compressed_size_;
return 0;
}
u1 *InputZipFile::UncompressFile()
{
size_t in_offset = p - zipdata_in_;
size_t remaining = input_file_->Length() - in_offset;
DecompressedFile *decompressed_file = decompressor_->UncompressFile(p, remaining);
if (decompressed_file == NULL)
{
if (decompressor_->GetError() != NULL)
{
error(decompressor_->GetError());
}
return NULL;
}
else
{
compressed_size_ = decompressed_file->compressed_size;
uncompressed_size_ = decompressed_file->uncompressed_size;
u1 *uncompressed_data = decompressed_file->uncompressed_data;
free(decompressed_file);
p += compressed_size_;
return uncompressed_data;
}
}
int InputZipFile::ProcessFile(const bool compressed)
{
const u1 *file_data;
if (compressed)
{
file_data = UncompressFile();
if (file_data == NULL)
{
return -1;
}
}
else
{
// In this case, compressed_size_ == uncompressed_size_ (since the file is
// uncompressed), so we can use either.
if (compressed_size_ != uncompressed_size_)
{
return error(
"compressed size != uncompressed size, although the file "
"is uncompressed.\n");
}
if (EnsureRemaining(compressed_size_, "file_data") < 0)
{
return -1;
}
file_data = p;
p += compressed_size_;
}
processor->Process(filename, attr, file_data, uncompressed_size_);
return 0;
}
// Reads and returns some metadata of the next file from the central directory:
// - compressed size
// - uncompressed size
// - whether the entry is a class file (to be included in the output).
// Precondition: p points to the beginning of an entry in the central dir
// Postcondition: p points to the beginning of the next entry in the central dir
// Returns true if the central directory contains another file and false if not.
// Of course, in the latter case, the size output variables are not changed.
// Note that the central directory is always followed by another data structure
// that has a signature, so parsing it this way is safe.
bool InputZipFile::ProcessCentralDirEntry(const u1 *&p,
size_t *compressed_size,
size_t *uncompressed_size,
char *filename,
size_t filename_size,
u4 *attr,
u4 *offset)
{
u4 signature = get_u4le(p);
if (signature != CENTRAL_FILE_HEADER_SIGNATURE)
{
if (signature != DIGITAL_SIGNATURE && signature != EOCD_SIGNATURE &&
signature != ZIP64_EOCD_SIGNATURE)
{
error("invalid central file header signature: 0x%x\n", signature);
}
return false;
}
p += 16; // skip to 'compressed size' field
*compressed_size = get_u4le(p);
*uncompressed_size = get_u4le(p);
u2 file_name_length = get_u2le(p);
u2 extra_field_length = get_u2le(p);
u2 file_comment_length = get_u2le(p);
p += 4; // skip to external file attributes field
*attr = get_u4le(p);
*offset = get_u4le(p);
{
size_t len = (file_name_length < filename_size) ? file_name_length : (filename_size - 1);
memcpy(reinterpret_cast<void *>(filename), p, len);
filename[len] = 0;
}
p += file_name_length;
p += extra_field_length;
p += file_comment_length;
return true;
}
// Gives a maximum bound on the size of the interface JAR. Basically, adds
// the difference between the compressed and uncompressed sizes to the size
// of the input file.
u8 InputZipFile::CalculateOutputLength()
{
const u1 *current = central_dir_;
u8 compressed_size = 0;
u8 uncompressed_size = 0;
u8 skipped_compressed_size = 0;
u4 attr;
u4 offset;
char filename[PATH_MAX];
while (true)
{
size_t file_compressed, file_uncompressed;
if (!ProcessCentralDirEntry(current, &file_compressed, &file_uncompressed, filename,
PATH_MAX, &attr, &offset))
{
break;
}
if (processor->Accept(filename, attr))
{
compressed_size += (u8)file_compressed;
uncompressed_size += (u8)file_uncompressed;
}
else
{
skipped_compressed_size += file_compressed;
}
}
// The worst case is when the output is simply the input uncompressed. The
// metadata in the zip file will stay the same, so the file will grow by the
// difference between the compressed and uncompressed sizes.
return (u8)input_file_->Length() - skipped_compressed_size +
(uncompressed_size - compressed_size);
}
// An end of central directory record, sized for optional zip64 contents.
struct EndOfCentralDirectoryRecord
{
u4 number_of_this_disk;
u4 disk_with_central_dir;
u8 central_dir_entries_on_this_disk;
u8 central_dir_entries;
u8 central_dir_size;
u8 central_dir_offset;
};
// Checks for a zip64 end of central directory record. If a valid zip64 EOCD is
// found, updates the original EOCD record and returns true.
bool MaybeReadZip64CentralDirectory(const u1 *bytes,
size_t /*in_length*/,
const u1 *current,
const u1 **end_of_central_dir,
EndOfCentralDirectoryRecord *cd)
{
if (current < bytes)
{
return false;
}
const u1 *candidate = current;
u4 zip64_directory_signature = get_u4le(current);
if (zip64_directory_signature != ZIP64_EOCD_SIGNATURE)
{
return false;
}
// size of zip64 end of central directory record
// (fixed size unless there's a zip64 extensible data sector, which
// we don't need to read)
get_u8le(current);
get_u2be(current); // version made by
get_u2be(current); // version needed to extract
u4 number_of_this_disk = get_u4be(current);
u4 disk_with_central_dir = get_u4le(current);
u8 central_dir_entries_on_this_disk = get_u8le(current);
u8 central_dir_entries = get_u8le(current);
u8 central_dir_size = get_u8le(current);
u8 central_dir_offset = get_u8le(current);
// check for a zip64 EOCD that matches the regular EOCD
if (number_of_this_disk != cd->number_of_this_disk && cd->number_of_this_disk != U2_MAX)
{
return false;
}
if (disk_with_central_dir != cd->disk_with_central_dir && cd->disk_with_central_dir != U2_MAX)
{
return false;
}
if (central_dir_entries_on_this_disk != cd->central_dir_entries_on_this_disk &&
cd->central_dir_entries_on_this_disk != U2_MAX)
{
return false;
}
if (central_dir_entries != cd->central_dir_entries && cd->central_dir_entries != U2_MAX)
{
return false;
}
if (central_dir_size != cd->central_dir_size && cd->central_dir_size != U4_MAX)
{
return false;
}
if (central_dir_offset != cd->central_dir_offset && cd->central_dir_offset != U4_MAX)
{
return false;
}
*end_of_central_dir = candidate;
cd->number_of_this_disk = number_of_this_disk;
cd->disk_with_central_dir = disk_with_central_dir;
cd->central_dir_entries_on_this_disk = central_dir_entries_on_this_disk;
cd->central_dir_entries = central_dir_entries;
cd->central_dir_size = central_dir_size;
cd->central_dir_offset = central_dir_offset;
return true;
}
// Starting from the end of central directory record, attempts to locate a zip64
// end of central directory record. If found, updates the given record and
// offset with the zip64 data. Returns false on error.
bool FindZip64CentralDirectory(const u1 *bytes,
size_t in_length,
const u1 **end_of_central_dir,
EndOfCentralDirectoryRecord *cd)
{
// In the absence of a zip64 extensible data sector, the zip64 EOCD is at a
// fixed offset from the regular central directory.
if (MaybeReadZip64CentralDirectory(
bytes, in_length, *end_of_central_dir - ZIP64_EOCD_LOCATOR_SIZE - ZIP64_EOCD_FIXED_SIZE,
end_of_central_dir, cd))
{
return true;
}
// If we couldn't find a zip64 EOCD at a fixed offset, either it doesn't exist
// or there was a zip64 extensible data sector, so try going through the
// locator. This approach doesn't work if data was prepended to the archive
// without updating the offset in the locator.
const u1 *zip64_locator = *end_of_central_dir - ZIP64_EOCD_LOCATOR_SIZE;
if (zip64_locator - ZIP64_EOCD_FIXED_SIZE < bytes)
{
return true;
}
u4 zip64_locator_signature = get_u4le(zip64_locator);
if (zip64_locator_signature != ZIP64_EOCD_LOCATOR_SIGNATURE)
{
return true;
}
u4 disk_with_zip64_central_directory = get_u4le(zip64_locator);
u8 zip64_end_of_central_dir_offset = get_u8le(zip64_locator);
u4 zip64_total_disks = get_u4le(zip64_locator);
if (MaybeReadZip64CentralDirectory(bytes, in_length, bytes + zip64_end_of_central_dir_offset,
end_of_central_dir, cd))
{
if (disk_with_zip64_central_directory != 0 || zip64_total_disks != 1)
{
fprintf(stderr, "multi-disk JAR files are not supported\n");
return false;
}
return true;
}
return true;
}
// Given the data in the zip file, returns the offset of the central directory
// and the number of files contained in it.
bool FindZipCentralDirectory(const u1 *bytes, size_t in_length, u4 *offset, const u1 **central_dir)
{
static const int MAX_COMMENT_LENGTH = 0xffff;
static const int CENTRAL_DIR_LOCATOR_SIZE = 22;
// Maximum distance of start of central dir locator from end of file
static const int MAX_DELTA = MAX_COMMENT_LENGTH + CENTRAL_DIR_LOCATOR_SIZE;
const u1 *last_pos_to_check = in_length < MAX_DELTA ? bytes : bytes + (in_length - MAX_DELTA);
const u1 *current;
bool found = false;
for (current = bytes + in_length - CENTRAL_DIR_LOCATOR_SIZE; current >= last_pos_to_check;
current--)
{
const u1 *p = current;
if (get_u4le(p) != EOCD_SIGNATURE)
{
continue;
}
p += 16; // skip to comment length field
u2 comment_length = get_u2le(p);
// Does the comment go exactly till the end of the file?
if (current + comment_length + CENTRAL_DIR_LOCATOR_SIZE != bytes + in_length)
{
continue;
}
// Hooray, we found it!
found = true;
break;
}
if (!found)
{
fprintf(stderr,
"file is invalid or corrupted (missing end of central "
"directory record)\n");
return false;
}
EndOfCentralDirectoryRecord cd;
const u1 *end_of_central_dir = current;
get_u4le(current); // central directory locator signature, already checked
cd.number_of_this_disk = get_u2le(current);
cd.disk_with_central_dir = get_u2le(current);
cd.central_dir_entries_on_this_disk = get_u2le(current);
cd.central_dir_entries = get_u2le(current);
cd.central_dir_size = get_u4le(current);
cd.central_dir_offset = get_u4le(current);
u2 file_comment_length = get_u2le(current);
current += file_comment_length; // set current to the end of the central dir
if (!FindZip64CentralDirectory(bytes, in_length, &end_of_central_dir, &cd))
{
return false;
}
if (cd.number_of_this_disk != 0 || cd.disk_with_central_dir != 0 ||
cd.central_dir_entries_on_this_disk != cd.central_dir_entries)
{
fprintf(stderr, "multi-disk JAR files are not supported\n");
return false;
}
// Do not change output values before determining that they are OK.
*offset = cd.central_dir_offset;
// Central directory start can then be used to determine the actual
// starts of the zip file (which can be different in case of a non-zip
// header like for auto-extractable binaries).
*central_dir = end_of_central_dir - cd.central_dir_size;
return true;
}
void InputZipFile::Reset()
{
central_dir_current_ = central_dir_;
bytes_unmapped_ = 0;
p = zipdata_in_ + in_offset_;
}
int ZipExtractor::ProcessAll()
{
while (ProcessNext())
{
}
if (GetError() != NULL)
{
return -1;
}
return 0;
}
ZipExtractor *ZipExtractor::Create(const char *filename, ZipExtractorProcessor *processor)
{
InputZipFile *result = new InputZipFile(processor, filename);
if (!result->Open())
{
fprintf(stderr, "Opening zip \"%s\": %s\n", filename, result->GetError());
delete result;
return NULL;
}
return result;
}
// zipdata_in_, in_offset_, p, central_dir_current_
InputZipFile::InputZipFile(ZipExtractorProcessor *processor, const char *filename)
: processor(processor), filename_(filename), input_file_(NULL), bytes_unmapped_(0)
{
decompressor_ = new Decompressor();
errmsg[0] = 0;
}
bool InputZipFile::Open()
{
MappedInputFile *input_file = new MappedInputFile(filename_);
if (!input_file->Opened())
{
snprintf(errmsg, sizeof(errmsg), "%s", input_file->Error());
delete input_file;
return false;
}
void *zipdata_in = input_file->Buffer();
u4 central_dir_offset;
const u1 *central_dir = NULL;
if (!devtools_ijar::FindZipCentralDirectory(static_cast<const u1 *>(zipdata_in),
input_file->Length(), &central_dir_offset,
&central_dir))
{
errno = EIO; // we don't really have a good error number
error("Cannot find central directory");
delete input_file;
return false;
}
const u1 *zipdata_start = static_cast<const u1 *>(zipdata_in);
in_offset_ = -static_cast<off_t>(zipdata_start + central_dir_offset - central_dir);
input_file_ = input_file;
zipdata_in_ = zipdata_start;
central_dir_ = central_dir;
central_dir_current_ = central_dir;
p = zipdata_in_ + in_offset_;
errmsg[0] = 0;
return true;
}
InputZipFile::~InputZipFile()
{
delete decompressor_;
if (input_file_ != NULL)
{
input_file_->Close();
delete input_file_;
}
}
//
// Implementation of OutputZipFile
//
int OutputZipFile::WriteEmptyFile(const char *filename)
{
const u1 *file_name = (const u1 *)filename;
size_t file_name_length = strlen(filename);
LocalFileEntry *entry = new LocalFileEntry;
entry->local_header_offset = Offset(q);
entry->external_attr = 0;
entry->crc32 = 0;
// Output the ZIP local_file_header:
put_u4le(q, LOCAL_FILE_HEADER_SIGNATURE);
put_u2le(q, 10); // extract_version
put_u2le(q, 0); // general_purpose_bit_flag
put_u2le(q, 0); // compression_method
put_u4le(q, kDefaultTimestamp); // last_mod_file date and time
put_u4le(q, entry->crc32); // crc32
put_u4le(q, 0); // compressed_size
put_u4le(q, 0); // uncompressed_size
put_u2le(q, file_name_length);
put_u2le(q, 0); // extra_field_length
put_n(q, file_name, file_name_length);
entry->file_name_length = file_name_length;
entry->extra_field_length = 0;
entry->compressed_length = 0;
entry->uncompressed_length = 0;
entry->compression_method = 0;
entry->extra_field = (const u1 *)"";
entry->file_name = (u1 *)strdup((const char *)file_name);
entries_.push_back(entry);
return 0;
}
void OutputZipFile::WriteCentralDirectory()
{
// central directory:
const u1 *central_directory_start = q;
for (size_t ii = 0; ii < entries_.size(); ++ii)
{
LocalFileEntry *entry = entries_[ii];
put_u4le(q, CENTRAL_FILE_HEADER_SIGNATURE);
put_u2le(q, UNIX_ZIP_FILE_VERSION);
put_u2le(q, ZIP_VERSION_TO_EXTRACT); // version to extract
put_u2le(q, 0); // general purpose bit flag
put_u2le(q, entry->compression_method); // compression method:
put_u4le(q, kDefaultTimestamp); // last_mod_file date and time
put_u4le(q, entry->crc32); // crc32
put_u4le(q, entry->compressed_length); // compressed_size
put_u4le(q, entry->uncompressed_length); // uncompressed_size
put_u2le(q, entry->file_name_length);
put_u2le(q, entry->extra_field_length);
put_u2le(q, 0); // file comment length
put_u2le(q, 0); // disk number start
put_u2le(q, 0); // internal file attributes
put_u4le(q, entry->external_attr); // external file attributes
// relative offset of local header:
put_u4le(q, entry->local_header_offset);
put_n(q, entry->file_name, entry->file_name_length);
put_n(q, entry->extra_field, entry->extra_field_length);
}
u8 central_directory_size = q - central_directory_start;
if (entries_.size() > U2_MAX || central_directory_size > U4_MAX ||
Offset(central_directory_start) > U4_MAX)
{
u1 *zip64_end_of_central_directory_start = q;
put_u4le(q, ZIP64_EOCD_SIGNATURE);
// signature and size field doesn't count towards size
put_u8le(q, ZIP64_EOCD_FIXED_SIZE - 12);
put_u2le(q, UNIX_ZIP_FILE_VERSION); // version made by
put_u2le(q, 0); // version needed to extract
put_u4le(q, 0); // number of this disk
put_u4le(q, 0); // # of the disk with the start of the central directory
put_u8le(q, entries_.size()); // # central dir entries on this disk
put_u8le(q, entries_.size()); // total # entries in the central directory
put_u8le(q, central_directory_size); // size of the central directory
// offset of start of central directory wrt starting disk
put_u8le(q, Offset(central_directory_start));
put_u4le(q, ZIP64_EOCD_LOCATOR_SIGNATURE);
// number of the disk with the start of the zip64 end of central directory
put_u4le(q, 0);
// relative offset of the zip64 end of central directory record
put_u8le(q, Offset(zip64_end_of_central_directory_start));
// total number of disks
put_u4le(q, 1);
put_u4le(q, EOCD_SIGNATURE);
put_u2le(q, 0); // number of this disk
put_u2le(q, 0); // # of disk with the start of the central directory
// # central dir entries on this disk
put_u2le(q, entries_.size() > 0xffff ? 0xffff : entries_.size());
// total # entries in the central directory
put_u2le(q, entries_.size() > 0xffff ? 0xffff : entries_.size());
// size of the central directory
put_u4le(q, central_directory_size > U4_MAX ? U4_MAX : central_directory_size);
// offset of start of central
put_u4le(
q, Offset(central_directory_start) > U4_MAX ? U4_MAX : Offset(central_directory_start));
put_u2le(q, 0); // .ZIP file comment length
}
else
{
put_u4le(q, EOCD_SIGNATURE);
put_u2le(q, 0); // number of this disk
put_u2le(q, 0); // # of the disk with the start of the central directory
put_u2le(q, entries_.size()); // # central dir entries on this disk
put_u2le(q, entries_.size()); // total # entries in the central directory
put_u4le(q, central_directory_size); // size of the central directory
// offset of start of central directory wrt starting disk
put_u4le(q, Offset(central_directory_start));
put_u2le(q, 0); // .ZIP file comment length
}
}
u1 *OutputZipFile::WriteLocalFileHeader(const char *filename, const u4 attr)
{
off_t file_name_length_ = strlen(filename);
LocalFileEntry *entry = new LocalFileEntry;
entry->local_header_offset = Offset(q);
entry->file_name_length = file_name_length_;
entry->file_name = new u1[file_name_length_];
entry->external_attr = attr;
memcpy(entry->file_name, filename, file_name_length_);
entry->extra_field_length = 0;
entry->extra_field = (const u1 *)"";
entry->crc32 = 0;
// Output the ZIP local_file_header:
put_u4le(q, LOCAL_FILE_HEADER_SIGNATURE);
put_u2le(q, ZIP_VERSION_TO_EXTRACT); // version to extract
put_u2le(q, 0); // general purpose bit flag
u1 *header_ptr = q;
put_u2le(q, COMPRESSION_METHOD_STORED); // compression method = placeholder
put_u4le(q, kDefaultTimestamp); // last_mod_file date and time
put_u4le(q, entry->crc32); // crc32
put_u4le(q, 0); // compressed_size = placeholder
put_u4le(q, 0); // uncompressed_size = placeholder
put_u2le(q, entry->file_name_length);
put_u2le(q, entry->extra_field_length);
put_n(q, entry->file_name, entry->file_name_length);
put_n(q, entry->extra_field, entry->extra_field_length);
entries_.push_back(entry);
return header_ptr;
}
size_t OutputZipFile::WriteFileSizeInLocalFileHeader(u1 *header_ptr,
size_t out_length,
bool compress,
const u4 crc)
{
size_t compressed_size = out_length;
if (compress)
{
compressed_size = TryDeflate(q, out_length);
}
// compression method
if (compressed_size < out_length)
{
put_u2le(header_ptr, COMPRESSION_METHOD_DEFLATED);
}
else
{
put_u2le(header_ptr, COMPRESSION_METHOD_STORED);
}
header_ptr += 4;
put_u4le(header_ptr, crc); // crc32
put_u4le(header_ptr, compressed_size); // compressed_size
put_u4le(header_ptr, out_length); // uncompressed_size
return compressed_size;
}
int OutputZipFile::Finish()
{
if (finished_)
{
return 0;
}
finished_ = true;
WriteCentralDirectory();
if (output_file_->Close(GetSize()) < 0)
{
return error("%s", output_file_->Error());
}
delete output_file_;
output_file_ = NULL;
return 0;
}
u1 *OutputZipFile::NewFile(const char *filename, const u4 attr)
{
header_ptr = WriteLocalFileHeader(filename, attr);
return q;
}
int OutputZipFile::FinishFile(size_t filelength, bool compress, bool compute_crc)
{
u4 crc = 0;
if (compute_crc)
{
crc = ComputeCrcChecksum(q, filelength);
if (filelength > 0 && crc == 0)
{
fprintf(stderr, "Error calculating CRC32 checksum.\n");
return -1;
}
}
size_t compressed_size = WriteFileSizeInLocalFileHeader(header_ptr, filelength, compress, crc);
if (compressed_size == 0 && filelength > 0)
{
fprintf(stderr, "Error compressing files.\n");
return -1;
}
entries_.back()->crc32 = crc;
entries_.back()->compressed_length = compressed_size;
entries_.back()->uncompressed_length = filelength;
if (compressed_size < filelength)
{
entries_.back()->compression_method = COMPRESSION_METHOD_DEFLATED;
}
else
{
entries_.back()->compression_method = COMPRESSION_METHOD_STORED;
}
q += compressed_size;
return 0;
}
bool OutputZipFile::Open()
{
if (estimated_size_ > kMaximumOutputSize)
{
fprintf(stderr,
"Uncompressed input jar has size %zu, "
"which exceeds the maximum supported output size %zu.\n"
"Assuming that ijar will be smaller and hoping for the best.\n",
estimated_size_, kMaximumOutputSize);
estimated_size_ = kMaximumOutputSize;
}
MappedOutputFile *output_file = new MappedOutputFile(filename_, estimated_size_);
if (!output_file->Opened())
{
snprintf(errmsg, sizeof(errmsg), "%s", output_file->Error());
delete output_file;
return false;
}
output_file_ = output_file;
q = output_file->Buffer();
zipdata_out_ = output_file->Buffer();
return true;
}
ZipBuilder *ZipBuilder::Create(const char *zip_file, size_t estimated_size)
{
OutputZipFile *result = new OutputZipFile(zip_file, estimated_size);
if (!result->Open())
{
fprintf(stderr, "%s\n", result->GetError());
delete result;
return NULL;
}
return result;
}
u8 ZipBuilder::EstimateSize(char const *const *files, char const *const *zip_paths, int nb_entries)
{
Stat file_stat;
// Digital signature field size = 6, End of central directory = 22, Total = 28
u8 size = 28;
// Count the size of all the files in the input to estimate the size of the
// output.
for (int i = 0; i < nb_entries; i++)
{
file_stat.total_size = 0;
if (files[i] != NULL && !stat_file(files[i], &file_stat))
{
fprintf(stderr, "File %s does not seem to exist.", files[i]);
return 0;
}
size += file_stat.total_size;
// Add sizes of Zip meta data
// local file header = 30 bytes
// data descriptor = 12 bytes
// central directory descriptor = 46 bytes
// Total: 88bytes
size += 88;
// The filename is stored twice (once in the central directory
// and once in the local file header).
size += strlen((zip_paths[i] != NULL) ? zip_paths[i] : files[i]) * 2;
}
return size;
}
} // namespace devtools_ijar
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// zip.h -- .zip (.jar) file reading/writing routines.
//
// This file specifies the interface to use the ZIP implementation of ijar.
//
#ifndef INCLUDED_THIRD_PARTY_IJAR_ZIP_H
#define INCLUDED_THIRD_PARTY_IJAR_ZIP_H
#include <sys/stat.h>
#include "third_party/ijar/common.h"
namespace devtools_ijar
{
// Tells if this is a directory entry from the mode. This method
// is safer than zipattr_to_mode(attr) & S_IFDIR because the unix
// mode might not be set in DOS zip files.
inline bool zipattr_is_dir(u4 attr)
{
return (attr & 0x10) != 0;
}
// Convert a ZIP file attribute to a Unix file permission mask.
inline mode_t zipattr_to_perm(u4 attr)
{
return ((mode_t)((attr >> 16) & 0777));
}
//
// Class interface for building ZIP files
//
class ZipBuilder
{
public:
virtual ~ZipBuilder() {}
// Returns the text for the last error, or null on no last error.
virtual const char *GetError() = 0;
// Add a new file to the ZIP, the file will have path "filename"
// and external attributes "attr". This function returns a pointer
// to a memory buffer to write the data of the file into. This buffer
// is owned by ZipBuilder and should not be free'd by the caller. The
// file length is then specified when the files is finished written
// using the FinishFile(size_t) function.
// On failure, returns NULL and GetError() will return an non-empty message.
virtual u1 *NewFile(const char *filename, const u4 attr) = 0;
// Finish writing a file and specify its length. After calling this method
// one should not reuse the pointer given by NewFile. The file can be
// compressed using the deflate algorithm by setting `compress` to true.
// By default, CRC32 are not computed as java tooling doesn't care, but
// computing it can be activated by setting `compute_crc` to true.
// On failure, returns -1 and GetError() will return an non-empty message.
virtual int FinishFile(size_t filelength, bool compress = false, bool compute_crc = false) = 0;
// Write an empty file, it is equivalent to:
// NewFile(filename, 0);
// FinishFile(0);
// On failure, returns -1 and GetError() will return an non-empty message.
virtual int WriteEmptyFile(const char *filename) = 0;
// Finish writing the ZIP file. This method can be called only once
// (subsequent calls will do nothing) and none of
// NewFile/FinishFile/WriteEmptyFile should be called after calling Finish. If
// this method was not called when the object is destroyed, it will be called.
// It is here as a convenience to get information on the final generated ZIP
// file.
// On failure, returns -1 and GetError() will return an non-empty message.
virtual int Finish() = 0;
// Get the current size of the ZIP file. This size will not be matching the
// final ZIP file until Finish() has been called because Finish() is actually
// writing the central directory of the ZIP File.
virtual size_t GetSize() = 0;
// Returns the current number of files stored in the ZIP.
virtual int GetNumberFiles() = 0;
// Create a new ZipBuilder writing the file zip_file and the size of the
// output will be at most estimated_size. Use ZipBuilder::EstimateSize() or
// ZipExtractor::CalculateOuputLength() to have an estimated_size depending on
// a list of file to store.
// On failure, returns NULL. Refer to errno for error code.
static ZipBuilder *Create(const char *zip_file, size_t estimated_size);
// Estimate the maximum size of the ZIP files containing files in the "files"
// null-terminated array.
// Returns 0 on error.
static u8 EstimateSize(char const *const *files, char const *const *zip_paths, int nb_entries);
};
//
// An abstract class to process data from a ZipExtractor.
// Derive from this class if you wish to process data from a ZipExtractor.
//
class ZipExtractorProcessor
{
public:
virtual ~ZipExtractorProcessor() {}
// Tells whether to skip or process the file "filename". "attr" is the
// external file attributes and can be converted to unix mode using the
// zipattr_to_mode() function. This method is suppoed to returns true
// if the file should be processed and false if it should be skipped.
virtual bool Accept(const char *filename, const u4 attr) = 0;
// Process a file accepted by Accept. The file "filename" has external
// attributes "attr" and length "size". The file content is accessible
// in the buffer pointed by "data".
virtual void Process(const char *filename,
const u4 attr,
const u1 *data,
const size_t size) = 0;
};
//
// Class interface for reading ZIP files
//
class ZipExtractor
{
public:
virtual ~ZipExtractor() {}
// Returns the text for the last error, or null on no last error.
virtual const char *GetError() = 0;
// Process the next files, returns false if the end of ZIP file has been
// reached. The processor provided by the Create method will be called
// if a file is encountered. If false is returned, check the return value
// of GetError() for potential errors.
virtual bool ProcessNext() = 0;
// Process the all files, returns -1 on error (GetError() will be populated
// on error).
virtual int ProcessAll();
// Reset the file pointer to the beginning.
virtual void Reset() = 0;
// Return the size of the ZIP file.
virtual size_t GetSize() = 0;
// Return the size of the resulting zip file by keeping only file
// accepted by the processor and storing them uncompressed. This
// method can be used to create a ZipBuilder for storing a subset
// of the input files.
// On error, 0 is returned and GetError() returns a non-empty message.
virtual u8 CalculateOutputLength() = 0;
// Create a ZipExtractor that extract the zip file "filename" and process
// it with "processor".
// On error, a null pointer is returned and the value of errno should be
// checked.
static ZipExtractor *Create(const char *filename, ZipExtractorProcessor *processor);
};
} // namespace devtools_ijar
#endif // INCLUDED_THIRD_PARTY_IJAR_ZIP_H
// Copyright 2015 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Zip / Unzip file using ijar zip implementation.
//
// Note that this Zip implementation intentionally don't compute CRC-32
// because it is useless computation for jar because Java doesn't care.
// CRC-32 of all files in the zip file will be set to 0.
//
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory>
#include <set>
#include <string>
#include "third_party/ijar/platform_utils.h"
#include "third_party/ijar/zip.h"
namespace devtools_ijar
{
//
// A ZipExtractorProcessor that extract files in the ZIP file.
//
class UnzipProcessor : public ZipExtractorProcessor
{
public:
// Create a processor who will extract the given files (or all files if NULL)
// into output_root if "extract" is set to true and will print the list of
// files and their unix modes if "verbose" is set to true.
UnzipProcessor(const char *output_root, char **files, bool verbose, bool extract, bool flatten)
: output_root_(output_root), verbose_(verbose), extract_(extract), flatten_(flatten)
{
if (files != NULL)
{
for (int i = 0; files[i] != NULL; i++)
{
file_names.insert(std::string(files[i]));
}
}
}
virtual ~UnzipProcessor() {}
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)
{
// All entry files are accepted by default.
if (file_names.empty())
{
return true;
}
else
{
// If users have specified file entries, only accept those files.
return file_names.count(std::string(filename)) == 1;
}
}
private:
const char *output_root_;
const bool verbose_;
const bool extract_;
const bool flatten_;
std::set<std::string> file_names;
};
// Concatene 2 path, path1 and path2, using / as a directory separator and
// puting the result in "out". "size" specify the size of the output buffer
void concat_path(char *out, const size_t size, const char *path1, const char *path2)
{
int len1 = strlen(path1);
size_t l = len1;
strncpy(out, path1, size - 1);
out[size - 1] = 0;
if (l < size - 1 && path1[len1] != '/' && path2[0] != '/')
{
out[l] = '/';
l++;
out[l] = 0;
}
if (l < size - 1)
{
strncat(out, path2, size - 1 - l);
}
}
void UnzipProcessor::Process(const char *filename, const u4 attr, const u1 *data, const size_t size)
{
mode_t perm = zipattr_to_perm(attr);
bool isdir = zipattr_is_dir(attr);
const char *output_file_name = filename;
if (attr == 0)
{
// Fallback when the external attribute is not set.
isdir = filename[strlen(filename) - 1] == '/';
perm = 0777;
}
if (flatten_)
{
if (isdir)
{
return;
}
const char *p = strrchr(filename, '/');
if (p != NULL)
{
output_file_name = p + 1;
}
}
if (verbose_)
{
printf("%c %o %s\n", isdir ? 'd' : 'f', perm, output_file_name);
}
if (extract_)
{
char path[PATH_MAX];
concat_path(path, PATH_MAX, output_root_, output_file_name);
if (!make_dirs(path, perm) || (!isdir && !write_file(path, perm, data, size)))
{
abort();
}
}
}
// Get the basename of path and store it in output. output_size
// is the size of the output buffer.
void basename(const char *path, char *output, size_t output_size)
{
const char *pointer = strrchr(path, '/');
if (pointer == NULL)
{
pointer = path;
}
else
{
pointer++; // Skip the leading slash.
}
strncpy(output, pointer, output_size);
output[output_size - 1] = 0;
}
// Execute the extraction (or just listing if just v is provided)
int extract(char *zipfile, char *exdir, char **files, bool verbose, bool extract, bool flatten)
{
std::string cwd = get_cwd();
if (cwd.empty())
{
return -1;
}
char output_root[PATH_MAX];
if (exdir != NULL)
{
concat_path(output_root, PATH_MAX, cwd.c_str(), exdir);
}
else
{
strncpy(output_root, cwd.c_str(), PATH_MAX);
}
UnzipProcessor processor(output_root, files, verbose, extract, flatten);
std::unique_ptr<ZipExtractor> extractor(ZipExtractor::Create(zipfile, &processor));
if (extractor == NULL)
{
fprintf(stderr, "Unable to open zip file %s: %s.\n", zipfile, strerror(errno));
return -1;
}
if (extractor->ProcessAll() < 0)
{
fprintf(stderr, "%s.\n", extractor->GetError());
return -1;
}
return 0;
}
// add a file to the zip
int add_file(std::unique_ptr<ZipBuilder> const &builder,
char *file,
char *zip_path,
bool flatten,
bool verbose,
bool compress)
{
Stat file_stat = {0, 0666, false};
if (file != NULL)
{
if (!stat_file(file, &file_stat))
{
fprintf(stderr, "Cannot stat file %s: %s\n", file, strerror(errno));
return -1;
}
}
char *final_path = zip_path != NULL ? zip_path : file;
bool isdir = file_stat.is_directory;
if (flatten && isdir)
{
return 0;
}
// Compute the path, flattening it if requested
char path[PATH_MAX];
size_t len = strlen(final_path);
if (len > PATH_MAX)
{
fprintf(stderr, "Path too long: %s.\n", final_path);
return -1;
}
if (flatten)
{
basename(final_path, path, PATH_MAX);
}
else
{
strncpy(path, final_path, PATH_MAX);
path[PATH_MAX - 1] = 0;
if (isdir && len < PATH_MAX - 1)
{
// Add the trailing slash for folders
path[len] = '/';
path[len + 1] = 0;
}
}
if (verbose)
{
mode_t perm = file_stat.file_mode & 0777;
printf("%c %o %s\n", isdir ? 'd' : 'f', perm, path);
}
u1 *buffer = builder->NewFile(path, stat_to_zipattr(file_stat));
if (isdir || file_stat.total_size == 0)
{
builder->FinishFile(0);
}
else
{
if (!read_file(file, buffer, file_stat.total_size))
{
return -1;
}
builder->FinishFile(file_stat.total_size, compress, true);
}
return 0;
}
// Read a list of files separated by newlines. The resulting array can be
// freed using the free method.
char **read_filelist(char *filename)
{
Stat file_stat;
if (!stat_file(filename, &file_stat))
{
fprintf(stderr, "Cannot stat file %s: %s\n", filename, strerror(errno));
return NULL;
}
char *data = static_cast<char *>(malloc(file_stat.total_size));
if (!read_file(filename, data, file_stat.total_size))
{
return NULL;
}
int nb_entries = 1;
for (int i = 0; i < file_stat.total_size; i++)
{
if (data[i] == '\n')
{
nb_entries++;
}
}
size_t sizeof_array = sizeof(char *) * (nb_entries + 1);
void *result = malloc(sizeof_array + file_stat.total_size + 1);
// copy the content
char **filelist = static_cast<char **>(result);
char *content = static_cast<char *>(result) + sizeof_array;
memcpy(content, data, file_stat.total_size);
content[file_stat.total_size] = '\0';
free(data);
// Create the corresponding array
int j = 1;
filelist[0] = content;
for (int i = 0; i < file_stat.total_size; i++)
{
if (content[i] == '\n')
{
content[i] = 0;
if (i + 1 < file_stat.total_size)
{
filelist[j] = content + i + 1;
j++;
}
}
}
filelist[j] = NULL;
return filelist;
}
// return real paths of the files
char **parse_filelist(char *zipfile, char **file_entries, int nb_entries, bool flatten)
{
// no need to free since the path lists should live until the end of the
// program
char **files = static_cast<char **>(malloc(sizeof(char *) * nb_entries));
char **zip_paths = file_entries;
for (int i = 0; i < nb_entries; i++)
{
char *p_eq = strchr(file_entries[i], '=');
if (p_eq != NULL)
{
if (flatten)
{
fprintf(stderr, "Unable to create zip file %s: %s.\n", zipfile,
"= can't be used with flatten");
free(files);
return NULL;
}
if (p_eq == file_entries[i])
{
fprintf(stderr, "Unable to create zip file %s: %s.\n", zipfile,
"A zip path should be given before =");
free(files);
return NULL;
}
*p_eq = '\0';
files[i] = p_eq + 1;
if (files[i][0] == '\0')
{
files[i] = NULL;
}
}
else
{
files[i] = file_entries[i];
zip_paths[i] = NULL;
}
}
return files;
}
// Execute the create operation
int create(char *zipfile, char **file_entries, bool flatten, bool verbose, bool compress)
{
int nb_entries = 0;
while (file_entries[nb_entries] != NULL)
{
nb_entries++;
}
char **zip_paths = file_entries;
char **files = parse_filelist(zipfile, file_entries, nb_entries, flatten);
if (files == NULL)
{
return -1;
}
u8 size = ZipBuilder::EstimateSize(files, zip_paths, nb_entries);
if (size == 0)
{
return -1;
}
std::unique_ptr<ZipBuilder> builder(ZipBuilder::Create(zipfile, size));
if (builder == NULL)
{
fprintf(stderr, "Unable to create zip file %s: %s.\n", zipfile, strerror(errno));
return -1;
}
for (int i = 0; i < nb_entries; i++)
{
if (add_file(builder, files[i], zip_paths[i], flatten, verbose, compress) < 0)
{
return -1;
}
}
if (builder->Finish() < 0)
{
fprintf(stderr, "%s\n", builder->GetError());
return -1;
}
return 0;
}
} // namespace devtools_ijar
//
// main method
//
static void usage(char *progname)
{
fprintf(stderr,
"Usage: %s [vxc[fC]] x.zip [-d exdir] [[zip_path1=]file1 ... "
"[zip_pathn=]filen]\n",
progname);
fprintf(stderr, " v verbose - list all file in x.zip\n");
fprintf(stderr,
" x extract - extract files in x.zip to current directory, or "
" an optional directory relative to the current directory "
" specified through -d option\n");
fprintf(stderr, " c create - add files to x.zip\n");
fprintf(stderr,
" f flatten - flatten files to use with create or "
"extract operation\n");
fprintf(stderr, " C compress - compress files when using the create operation\n");
fprintf(stderr, "x and c cannot be used in the same command-line.\n");
fprintf(stderr, "\nFor every file, a path in the zip can be specified. Examples:\n");
fprintf(stderr,
" zipper c x.zip a/b/__init__.py= # Add an empty file at "
"a/b/__init__.py\n");
fprintf(stderr,
" zipper c x.zip a/b/main.py=foo/bar/bin.py # Add file "
"foo/bar/bin.py at a/b/main.py\n");
fprintf(stderr,
"\nIf the zip path is not specified, it is assumed to be the file "
"path.\n");
exit(1);
}
int main(int argc, char **argv)
{
bool extract = false;
bool verbose = false;
bool create = false;
bool compress = false;
bool flatten = false;
if (argc < 3)
{
usage(argv[0]);
}
for (int i = 0; argv[1][i] != 0; i++)
{
switch (argv[1][i])
{
case 'x':
extract = true;
break;
case 'v':
verbose = true;
break;
case 'c':
create = true;
break;
case 'f':
flatten = true;
break;
case 'C':
compress = true;
break;
default:
usage(argv[0]);
}
}
// x and c cannot be used in the same command-line.
if (create && extract)
{
usage(argv[0]);
}
// Calculate the argument index of the first entry file.
int filelist_start_index;
if (argc > 3 && strcmp(argv[3], "-d") == 0)
{
filelist_start_index = 5;
}
else
{
filelist_start_index = 3;
}
char **filelist = NULL;
// We have one option file. Read and extract the content.
if (argc == filelist_start_index + 1 && argv[filelist_start_index][0] == '@')
{
char *filelist_name = argv[filelist_start_index];
filelist = devtools_ijar::read_filelist(filelist_name + 1);
if (filelist == NULL)
{
fprintf(stderr, "Can't read file list %s: %s.\n", filelist_name, strerror(errno));
return -1;
}
// We have more than one files. Assume that they are all file entries.
}
else if (argc >= filelist_start_index + 1)
{
filelist = argv + filelist_start_index;
}
else
{
// There are no entry files specified. This is forbidden if we are creating
// a zip file.
if (create)
{
fprintf(stderr, "Can't create zip without input files specified.");
return -1;
}
}
if (create)
{
// Create a zip
return devtools_ijar::create(argv[2], filelist, flatten, verbose, compress);
}
else
{
char *exdir = NULL;
if (argc > 3 && strcmp(argv[3], "-d") == 0)
{
exdir = argv[4];
}
// Extraction / list mode
return devtools_ijar::extract(argv[2], exdir, filelist, verbose, extract, flatten);
}
}
// Copyright 2016 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <algorithm>
#include <cstdio>
#include <zlib.h>
#include "third_party/ijar/common.h"
#include "third_party/ijar/zlib_client.h"
namespace devtools_ijar
{
u4 ComputeCrcChecksum(u1 *buf, size_t length)
{
return crc32(0, buf, length);
}
size_t TryDeflate(u1 *buf, size_t length)
{
u1 *outbuf = reinterpret_cast<u1 *>(malloc(length));
z_stream stream;
// Initialize the z_stream strcut for reading from buf and wrinting in outbuf.
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;
stream.total_in = length;
stream.avail_in = length;
stream.total_out = length;
stream.avail_out = length;
stream.next_in = buf;
stream.next_out = outbuf;
// deflateInit2 negative windows size prevent the zlib wrapper to be used.
if (deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8,
Z_DEFAULT_STRATEGY) != Z_OK)
{
// Failure to compress => return the buffer uncompressed
free(outbuf);
return length;
}
if (deflate(&stream, Z_FINISH) == Z_STREAM_END)
{
// Compression successful and fits in outbuf, let's copy the result in buf.
length = stream.total_out;
memcpy(buf, outbuf, length);
}
deflateEnd(&stream);
free(outbuf);
// Return the length of the resulting buffer
return length;
}
Decompressor::Decompressor()
{
uncompressed_data_allocated_ = INITIAL_BUFFER_SIZE;
uncompressed_data_ = reinterpret_cast<u1 *>(malloc(uncompressed_data_allocated_));
}
Decompressor::~Decompressor()
{
free(uncompressed_data_);
}
DecompressedFile *Decompressor::UncompressFile(const u1 *buffer, size_t bytes_avail)
{
z_stream stream;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;
stream.avail_in = bytes_avail;
stream.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(buffer));
int ret = inflateInit2(&stream, -MAX_WBITS);
if (ret != Z_OK)
{
error("inflateInit: %d\n", ret);
return NULL;
}
int uncompressed_until_now = 0;
while (true)
{
stream.avail_out = uncompressed_data_allocated_ - uncompressed_until_now;
stream.next_out = uncompressed_data_ + uncompressed_until_now;
int old_avail_out = stream.avail_out;
ret = inflate(&stream, Z_SYNC_FLUSH);
int uncompressed_now = old_avail_out - stream.avail_out;
uncompressed_until_now += uncompressed_now;
switch (ret)
{
case Z_STREAM_END:
{
struct DecompressedFile *decompressedFile =
reinterpret_cast<DecompressedFile *>(malloc(sizeof(DecompressedFile)));
// zlib said that there is no more data to decompress.
u1 *new_p = reinterpret_cast<u1 *>(stream.next_in);
decompressedFile->compressed_size = new_p - buffer;
decompressedFile->uncompressed_size = uncompressed_until_now;
decompressedFile->uncompressed_data = uncompressed_data_;
inflateEnd(&stream);
return decompressedFile;
}
case Z_OK:
{
// zlib said that there is no more room in the buffer allocated for
// the decompressed data. Enlarge that buffer and try again.
if (uncompressed_data_allocated_ == MAX_BUFFER_SIZE)
{
error(
"ijar does not support decompressing files "
"larger than %dMB.\n",
static_cast<int>((MAX_BUFFER_SIZE / (1024 * 1024))));
return NULL;
}
uncompressed_data_allocated_ *= 2;
if (uncompressed_data_allocated_ > MAX_BUFFER_SIZE)
{
uncompressed_data_allocated_ = MAX_BUFFER_SIZE;
}
uncompressed_data_ = reinterpret_cast<u1 *>(
realloc(uncompressed_data_, uncompressed_data_allocated_));
break;
}
case Z_DATA_ERROR:
case Z_BUF_ERROR:
case Z_STREAM_ERROR:
case Z_NEED_DICT:
default:
{
error("zlib returned error code %d during inflate.\n", ret);
return NULL;
}
}
}
}
char *Decompressor::GetError()
{
if (errmsg[0] == 0)
{
return NULL;
}
return errmsg;
}
int Decompressor::error(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsnprintf(errmsg, 4 * PATH_MAX, fmt, ap);
va_end(ap);
return -1;
}
} // namespace devtools_ijar
// Copyright 2016 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef THIRD_PARTY_IJAR_ZLIB_CLIENT_H_
#define THIRD_PARTY_IJAR_ZLIB_CLIENT_H_
#include <limits.h>
#include "third_party/ijar/common.h"
namespace devtools_ijar
{
// Try to compress a file entry in memory using the deflate algorithm.
// It will compress buf (of size length) unless the compressed size is bigger
// than the input size. The result will overwrite the content of buf and the
// final size is returned.
size_t TryDeflate(u1 *buf, size_t length);
u4 ComputeCrcChecksum(u1 *buf, size_t length);
struct DecompressedFile
{
u1 *uncompressed_data;
u4 uncompressed_size;
u4 compressed_size;
};
class Decompressor
{
public:
Decompressor();
~Decompressor();
DecompressedFile *UncompressFile(const u1 *buffer, size_t bytes_avail);
char *GetError();
private:
// Administration of memory reserved for decompressed data. We use the same
// buffer for each file to avoid some malloc()/free() calls and free the
// memory only in the dtor. C-style memory management is used so that we
// can call realloc.
u1 *uncompressed_data_;
size_t uncompressed_data_allocated_;
// last error
char errmsg[4 * PATH_MAX];
int error(const char *fmt, ...);
// Buffer size is initially INITIAL_BUFFER_SIZE. It doubles in size every
// time it is found too small, until it reaches MAX_BUFFER_SIZE. If that is
// not enough, we bail out. We only decompress class files, so they should
// be smaller than 64K anyway, but we give a little leeway.
// MAX_BUFFER_SIZE must be bigger than the size of the biggest file in the
// ZIP. It is set to 2GB here because no one has audited the code for 64-bit
// cleanliness.
static const size_t INITIAL_BUFFER_SIZE = 256 * 1024; // 256K
static const size_t MAX_BUFFER_SIZE = std::numeric_limits<int32_t>::max();
};
} // namespace devtools_ijar
#endif // THIRD_PARTY_IJAR_ZLIB_CLIENT_H_
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment