Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@
*/
package jdk.internal.jimage;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
Expand All @@ -39,6 +37,8 @@
import java.security.PrivilegedAction;
import java.util.Objects;
import java.util.stream.IntStream;

import jdk.internal.jimage.BasicImageReader.ImageError.Reason;
import jdk.internal.jimage.decompressor.Decompressor;

/**
Expand Down Expand Up @@ -144,10 +144,12 @@ public Void run() {
if (channel.read(headerBuffer, 0L) == headerSize) {
headerBuffer.rewind();
} else {
throw new IOException("\"" + name + "\" is not an image file");
throw new ImageError(Reason.INVALID_JIMAGE,
"\"" + name + "\" is not an image file");
}
} else if (headerBuffer.capacity() < headerSize) {
throw new IOException("\"" + name + "\" is not an image file");
throw new ImageError(Reason.INVALID_JIMAGE,
"\"" + name + "\" is not an image file");
}

// Interpret the image file header
Expand All @@ -164,7 +166,8 @@ public Void run() {

// Interpret the image index
if (memoryMap.capacity() < indexSize) {
throw new IOException("The image file \"" + name + "\" is corrupted");
throw new ImageError(Reason.CORRUPT_JIMAGE,
"The image file \"" + name + "\" is corrupted");
}
redirect = intBuffer(memoryMap, header.getRedirectOffset(), header.getRedirectSize());
offsets = intBuffer(memoryMap, header.getOffsetsOffset(), header.getOffsetsSize());
Expand All @@ -191,14 +194,16 @@ private ImageHeader readHeader(IntBuffer buffer) throws IOException {
ImageHeader result = ImageHeader.readFrom(buffer);

if (result.getMagic() != ImageHeader.MAGIC) {
throw new IOException("\"" + name + "\" is not an image file");
throw new ImageError(Reason.INVALID_JIMAGE,
"\"" + name + "\" is not an image file");
}

if (result.getMajorVersion() != ImageHeader.MAJOR_VERSION ||
result.getMinorVersion() != ImageHeader.MINOR_VERSION) {
throw new IOException("The image file \"" + name + "\" is not " +
"the correct version. Major: " + result.getMajorVersion() +
". Minor: " + result.getMinorVersion());
result.getMinorVersion() != ImageHeader.MINOR_VERSION) {
throw new ImageError(Reason.BAD_VERSION,
"The image file \"" + name + "\" is not the correct version.\n"
+ "Major: " + result.getMajorVersion()
+ ". Minor: " + result.getMinorVersion());
}

return result;
Expand Down Expand Up @@ -457,10 +462,31 @@ public ByteBuffer getResourceBuffer(ImageLocation loc) {
return null;
}

public InputStream getResourceStream(ImageLocation loc) {
Objects.requireNonNull(loc);
byte[] bytes = getResource(loc);
/**
* Specialized {@link IOException} thrown during construction to provide a
* semantic reason for failure and allow better user-facing error messages.
*/
public final static class ImageError extends IOException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception names ending in "Error" are usually fatal to the VM, this seems more like a normal exception.
Generally, we don't add single purpose exceptions, they are little used and just contribute to bloat.

Copy link
Contributor Author

@david-beaumont david-beaumont Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How else do you think it best to reliably detect the semantic difference for the JImageTool while keeping the non-tool usage as it was? This new exception has a vital role for doing that which, if it, or something like it, didn't exist would be done via unsatisfying heuristics on the debug message string.

An alternate version would be to refactor things to have an "open()" method that returns a failure-reason type of some sort.

And the exception based approach would pave the way to properly translated strings for all the failure modes (though the others aren't actionable in as distinct a way).

private static final long serialVersionUID = 6002259582237888214L;

public enum Reason {
/** The file being opened does not appear to be a jimage file. */
INVALID_JIMAGE,
/** The jimage file being opened is corrupted. */
CORRUPT_JIMAGE,
/** The jimage file being opened has the wrong version. */
BAD_VERSION,
}

private final Reason reason;

return new ByteArrayInputStream(bytes);
public ImageError(Reason reason, String message) {
super(message);
this.reason = reason;
}

public Reason getReason() {
return reason;
}
}
}
8 changes: 8 additions & 0 deletions src/jdk.jlink/share/classes/jdk/tools/jimage/JImageTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.io.PrintWriter;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -44,6 +45,7 @@
import java.lang.classfile.MethodModel;

import jdk.internal.jimage.BasicImageReader;
import jdk.internal.jimage.BasicImageReader.ImageError.Reason;
import jdk.internal.jimage.ImageHeader;
import jdk.internal.jimage.ImageLocation;
import jdk.tools.jlink.internal.ImageResourcesTree;
Expand Down Expand Up @@ -435,6 +437,12 @@ private void iterate(JImageAction jimageAction,
}
}
} catch (IOException ioe) {
// Handle specific errors for which better advice can be given.
if (ioe instanceof BasicImageReader.ImageError err
&& err.getReason() == Reason.BAD_VERSION) {
throw TASK_HELPER.newBadArgs("err.bad.version", file);
}
// Non-specific error during processing.
throw TASK_HELPER.newBadArgs("err.invalid.jimage", file, ioe.getMessage());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,20 @@ main.usage=\
Usage: {0} <extract | info | list | verify> <options> jimage...\n\
\n\
\ extract - Extract all jimage entries and place in a directory specified\n\
\ by the --dir=<directory> (default='.') option.\n\
\ by the --dir=<directory> (default=''.'') option.\n\
\n\
\ info - Prints detailed information contained in the jimage header.\n\
\n\
\ list - Prints the names of all the entries in the jimage. When used with\n\
\ --verbose, list will also print entry size and offset attributes.\n\
\n\
\ verify - Reports on any .class entries that don't verify as classes.\n\
\ verify - Reports on any .class entries that don''t verify as classes.\n\
\n\
Possible options include:

main.usage.extract=\
\ extract - Extract all jimage entries and place in a directory specified\n\
\ by the --dir=<directory> (default='.') option.
\ by the --dir=<directory> (default=''.'') option.

main.usage.info=\
\ info - Prints detailed information contained in the jimage header.
Expand All @@ -54,7 +54,7 @@ main.usage.list=\
\ --verbose, list will also print entry size and offset attributes.

main.usage.verify=\
\ verify - Reports errors on any .class entries that don't verify as classes.
\ verify - Reports errors on any .class entries that don''t verify as classes.

error.prefix=Error:
warn.prefix=Warning:
Expand Down Expand Up @@ -101,3 +101,5 @@ err.no.jimage=no jimage provided
err.option.unsupported={0} not supported: {1}
err.unknown.option=unknown option: {0}
err.cannot.create.dir=cannot create directory {0}
err.bad.version=Unable to open {0}: mismatched file and tool version\n\
Use ''<JAVA_HOME>/bin/jimage'' for the JDK associated with this jimage file.