blob: c6d9f517b18887f619f13d026ebc695ab9d1748a [file] [edit]
// Copyright 2026 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.
package test;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
/**
* Generates a JAR containing a class with a {@code Record} attribute whose declared {@code
* attribute_length} is smaller than the actual component data, and whose total serialized component
* data exceeds 65,535 bytes (testing 32-bit {@code u4} length backpatching).
*/
public final class GenRecordLengthMismatch {
public static void main(String[] args) throws IOException {
try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(Paths.get(args[0])))) {
ZipEntry foo = new ZipEntry("Foo.class");
foo.setTime(0);
jos.putNextEntry(foo);
jos.write(dump("Foo", /* mismatchedLength= */ true));
ZipEntry bar = new ZipEntry("Bar.class");
bar.setTime(0);
jos.putNextEntry(bar);
jos.write(dump("Bar", /* mismatchedLength= */ false));
}
}
private static byte[] dump(String className, boolean mismatchedLength) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
// Header: magic, minor (0), major (61 = Java 17)
dos.writeInt(0xCAFEBABE);
dos.writeShort(0);
dos.writeShort(61);
// Constant pool (count = 10, entries 1..9)
dos.writeShort(10);
// #1: Utf8 className
dos.writeByte(1);
dos.writeUTF(className);
// #2: Class #1
dos.writeByte(7);
dos.writeShort(1);
// #3: Utf8 "java/lang/Record"
dos.writeByte(1);
dos.writeUTF("java/lang/Record");
// #4: Class #3
dos.writeByte(7);
dos.writeShort(3);
// #5: Utf8 "Record"
dos.writeByte(1);
dos.writeUTF("Record");
// #6: Utf8 "x"
dos.writeByte(1);
dos.writeUTF("x");
// #7: Utf8 "I"
dos.writeByte(1);
dos.writeUTF("I");
// #8: Utf8 "priv"
dos.writeByte(1);
dos.writeUTF("priv");
// #9: Utf8 "()V"
dos.writeByte(1);
dos.writeUTF("()V");
// access_flags (ACC_PUBLIC | ACC_FINAL | ACC_SUPER), this_class (#2), super_class (#4)
dos.writeShort(0x0031);
dos.writeShort(2);
dos.writeShort(4);
// interfaces_count (0), fields_count (0)
dos.writeShort(0);
dos.writeShort(0);
// methods_count (1): private void priv() so ijar strips it when valid
dos.writeShort(1);
dos.writeShort(0x0002); // ACC_PRIVATE
dos.writeShort(8); // "priv"
dos.writeShort(9); // "()V"
dos.writeShort(0); // attributes_count = 0
// attributes_count (1)
dos.writeShort(1);
// Record attribute: attribute_name_index (#5 = "Record")
dos.writeShort(5);
int componentsCount = 65535;
int actualPayloadSize = 2 + componentsCount * 6;
dos.writeInt(mismatchedLength ? 6 : actualPayloadSize);
dos.writeShort(componentsCount);
for (int i = 0; i < componentsCount; i++) {
// name_index (#6 = "x"), descriptor_index (#7 = "I"), attributes_count (0)
dos.writeShort(6);
dos.writeShort(7);
dos.writeShort(0);
}
dos.flush();
return baos.toByteArray();
}
private GenRecordLengthMismatch() {}
}