java-topology/test/jdk/tools/jlink/runtimeImage/JimageDiffGeneratorTest.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

327 lines
12 KiB
Java

/*
* Copyright (c) 2025, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
import org.testng.annotations.Test;
import jdk.tools.jlink.internal.runtimelink.JimageDiffGenerator;
import jdk.tools.jlink.internal.runtimelink.JimageDiffGenerator.ImageResource;
import jdk.tools.jlink.internal.runtimelink.ResourceDiff;
/*
* @test
* @summary Unit test the diff generator logic for JEP 493
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal.runtimelink
* @run testng JimageDiffGeneratorTest
*/
public class JimageDiffGeneratorTest {
/*
* Expect a resource diff since the "b" item is removed in
* the optimized image.
*/
@Test
public void testItemsRemovedInOpt() throws Exception {
List<String> entriesOpt = List.of("a", "c", "d");
byte[][] bytesOpt = new byte[][] {
{ 0x01, 0x03, 0x03 }, /* a */
{ 0x09, 0x11, 0x11 }, /* c */
{ 0x22, 0x22, 0x30 }, /* d */
};
ImageResource opt = new BasicImageResource(entriesOpt, bytesOpt);
List<String> entriesBase = List.of("a", "b", "c", "d");
byte[][] bytesBase = new byte[][] {
{ 0x01, 0x03, 0x03 }, /* a */
{ 0x08, 0x04, 0x04 }, /* b */
{ 0x09, 0x11, 0x11 }, /* c */
{ 0x22, 0x22, 0x30 }, /* d */
};
ImageResource base = new BasicImageResource(entriesBase, bytesBase);
JimageDiffGenerator gen = new JimageDiffGenerator();
List<ResourceDiff> result = gen.generateDiff(base, opt);
assertEquals(result.size(), 1);
assertEquals(result.get(0).getKind(), ResourceDiff.Kind.REMOVED);
assertEquals(result.get(0).getName(), "b");
assertEquals(result.get(0).getResourceBytes(), bytesBase[1]);
}
/*
* Expect no difference as streams are the same
*/
@Test
public void testNoDiff() throws Exception {
List<String> entriesBase = List.of("a", "b", "c", "d");
byte[][] bytesBase = new byte[][] {
{ 0x01, 0x03, 0x03 }, /* a */
{ 0x08, 0x04, 0x04 }, /* b */
{ 0x09, 0x11, 0x11 }, /* c */
{ 0x22, 0x22, 0x30 }, /* d */
};
ImageResource base = new BasicImageResource(entriesBase, bytesBase);
ImageResource opt = new BasicImageResource(entriesBase, bytesBase);
JimageDiffGenerator gen = new JimageDiffGenerator();
List<ResourceDiff> result = gen.generateDiff(base, opt);
assertTrue(result.isEmpty());
}
/*
* Expect a resource diff since the "b" item has been added in
* the optimized image.
*/
@Test
public void testItemsAddedInOpt() throws Exception {
List<String> entriesBase = List.of("a", "c", "d");
byte[][] bytesBase = new byte[][] {
{ 0x01, 0x03, 0x03 }, /* a */
{ 0x09, 0x11, 0x11 }, /* c */
{ 0x22, 0x22, 0x30 }, /* d */
};
ImageResource base = new BasicImageResource(entriesBase, bytesBase);
List<String> entriesOpt = List.of("a", "b", "c", "d");
byte[][] bytesOpt = new byte[][] {
{ 0x01, 0x03, 0x03 }, /* a */
{ 0x08, 0x04, 0x04 }, /* b */
{ 0x09, 0x11, 0x11 }, /* c */
{ 0x22, 0x22, 0x30 }, /* d */
};
ImageResource opt = new BasicImageResource(entriesOpt, bytesOpt);
JimageDiffGenerator gen = new JimageDiffGenerator();
List<ResourceDiff> result = gen.generateDiff(base, opt);
assertEquals(result.size(), 1);
assertEquals(result.get(0).getKind(), ResourceDiff.Kind.ADDED);
assertEquals(result.get(0).getName(), "b");
assertEquals(result.get(0).getResourceBytes(), null, "Added entries in opt don't have resource bytes");
}
/*
* Expect a resource diff since the "d" item has modified bytes in the
* optimized image resource.
*/
@Test
public void testBytesDiffer() throws Exception {
List<String> entriesBase = List.of("a", "b", "c", "d");
byte[][] bytesBase = new byte[][] {
{ 0x01, 0x03, 0x03 }, /* a */
{ 0x08, 0x04, 0x04 }, /* b */
{ 0x09, 0x11, 0x11 }, /* c */
{ 0x11, 0x12, 0x31 }, /* d */
};
ImageResource base = new BasicImageResource(entriesBase, bytesBase);
List<String> entriesOpt = List.of("a", "b", "c", "d");
byte[][] bytesOpt = new byte[][] {
{ 0x01, 0x03, 0x03 }, /* a */
{ 0x08, 0x04, 0x04 }, /* b */
{ 0x09, 0x11, 0x11 }, /* c */
{ 0x22, 0x22, 0x30 }, /* d - differs to base! */
};
ImageResource opt = new BasicImageResource(entriesOpt, bytesOpt);
JimageDiffGenerator gen = new JimageDiffGenerator();
List<ResourceDiff> result = gen.generateDiff(base, opt);
assertEquals(result.size(), 1);
assertEquals(result.get(0).getKind(), ResourceDiff.Kind.MODIFIED);
assertEquals(result.get(0).getName(), "d");
assertEquals(result.get(0).getResourceBytes(), bytesBase[3]);
}
/*
* Expect a resource diff since an item has modified bytes. Test
* for a resource that has more than 1K bytes (the buffer size used
* internally).
*/
@Test
public void testBytesDifferLarge() throws Exception {
List<String> entriesBase = List.of("a", "b", "c", "d");
byte[][] bytesBase = new byte[][] {
{ 0x01, 0x03, 0x03 }, /* a */
{ 0x08, 0x04, 0x04 }, /* b */
{ }, /* c */
{ 0x11, 0x12, 0x31 }, /* d */
};
bytesBase[2] = generateBytes();
ImageResource base = new BasicImageResource(entriesBase, bytesBase);
List<String> entriesOpt = List.of("a", "b", "c", "d");
byte[][] bytesOpt = new byte[][] {
{ 0x01, 0x03, 0x03 }, /* a */
{ 0x08, 0x04, 0x04 }, /* b */
{ }, /* c */
{ 0x22, 0x22, 0x30 }, /* d */
};
bytesOpt[2] = generateBytes();
// Change the first byte of 'c' in the opt bytes
bytesOpt[2][0] = -1;
// assert pre-condition
assertTrue(bytesOpt[2][0] != bytesBase[2][0]);
ImageResource opt = new BasicImageResource(entriesOpt, bytesOpt);
JimageDiffGenerator gen = new JimageDiffGenerator();
List<ResourceDiff> result = gen.generateDiff(base, opt);
assertEquals(result.size(), 2);
// assertions for 'c' differences
assertEquals(result.get(0).getKind(), ResourceDiff.Kind.MODIFIED);
assertEquals(result.get(0).getName(), "c");
assertEquals(result.get(0).getResourceBytes(), bytesBase[2]);
// assertion for 'd' differences
assertEquals(result.get(1).getKind(), ResourceDiff.Kind.MODIFIED);
assertEquals(result.get(1).getName(), "d");
assertEquals(result.get(1).getResourceBytes(), bytesBase[3]);
}
/*
* Expect a no resource difference since the steams are both empty
*/
@Test
public void testEmptyStreams() throws Exception {
List<String> entriesBase = List.of("a", "b", "c", "d");
byte[][] bytesBase = new byte[][] {
{ }, /* a */
{ }, /* b */
{ }, /* c */
{ }, /* d */
};
ImageResource base = new BasicImageResource(entriesBase, bytesBase);
ImageResource opt = new BasicImageResource(entriesBase, bytesBase);
JimageDiffGenerator gen = new JimageDiffGenerator();
List<ResourceDiff> result = gen.generateDiff(base, opt);
assertTrue(result.isEmpty());
}
/*
* Expect a difference since entry 'a' has zero bytes in opt.
*/
@Test
public void testNotEqualLength() throws Exception {
List<String> entriesBase = List.of("a", "b", "c", "d");
byte[][] bytesBase = new byte[][] {
{ 0x01, 0x03, 0x03 }, /* a */
{ 0x08, 0x04, 0x04 }, /* b */
{ 0x09, 0x11, 0x11 }, /* c */
{ 0x11, 0x12, 0x31 }, /* d */
};
byte[][] bytesOpt = new byte[][] {
{ }, /* a */
{ 0x08, 0x04, 0x04 }, /* b */
{ 0x09, 0x11, 0x11 }, /* c */
{ 0x11, 0x12, 0x31 }, /* d */
};
ImageResource base = new BasicImageResource(entriesBase, bytesBase);
ImageResource opt = new BasicImageResource(entriesBase, bytesOpt);
JimageDiffGenerator gen = new JimageDiffGenerator();
List<ResourceDiff> result = gen.generateDiff(base, opt);
assertEquals(result.size(), 1);
assertEquals(result.get(0).getKind(), ResourceDiff.Kind.MODIFIED);
assertEquals(result.get(0).getName(), "a");
assertEquals(result.get(0).getResourceBytes(), bytesBase[0]);
}
/*
* Expect a difference since entry 'a' on the optimized version is
* one byte longer.
*/
@Test
public void testBytesDifferExactBufferSize() throws Exception {
List<String> entriesBase = List.of("a", "b", "c", "d");
byte[][] bytesBase = new byte[][] {
{ }, /* a */
{ 0x08, 0x04, 0x04 }, /* b */
{ 0x09, 0x11, 0x11 }, /* c */
{ 0x11, 0x12, 0x31 }, /* d */
};
byte[][] bytesOpt = new byte[][] {
{ }, /* a */
{ 0x08, 0x04, 0x04 }, /* b */
{ 0x09, 0x11, 0x11 }, /* c */
{ 0x11, 0x12, 0x31 }, /* d */
};
bytesBase[0] = genBytesOfSize(1024); // exact buffer size
bytesOpt[0] = genBytesOfSize(1024 + 1); // buffer size + 1
ImageResource base = new BasicImageResource(entriesBase, bytesBase);
ImageResource opt = new BasicImageResource(entriesBase, bytesOpt);
JimageDiffGenerator gen = new JimageDiffGenerator();
List<ResourceDiff> result = gen.generateDiff(base, opt);
assertEquals(result.size(), 1);
assertEquals(result.get(0).getKind(), ResourceDiff.Kind.MODIFIED);
assertEquals(result.get(0).getName(), "a");
assertEquals(result.get(0).getResourceBytes(), bytesBase[0]);
}
private byte[] generateBytes() {
int size = 1024 + 254;
return genBytesOfSize(size);
}
private byte[] genBytesOfSize(int size) {
byte[] result = new byte[size];
for (int i = 0; i < size; i++) {
result[i] = (byte)(i % Byte.MAX_VALUE);
}
return result;
}
// Simple stub ImageResource for test purposes
static class BasicImageResource implements ImageResource {
private final List<String> entries;
private final byte[][] entryBytes;
public BasicImageResource(List<String> entries, byte[][] entryBytes) {
this.entries = entries;
this.entryBytes = entryBytes;
}
@Override
public void close() throws Exception {
// nothing
}
@Override
public List<String> getEntries() {
return entries;
}
@Override
public byte[] getResourceBytes(String name) {
for (int i = 0; i < entries.size(); i++) {
if (entries.get(i).equals(name)) {
return entryBytes[i];
}
}
return null;
}
@Override
public InputStream getResource(String name) {
byte[] bytes = getResourceBytes(name);
return new ByteArrayInputStream(bytes);
}
}
}