Skip to content

8055461: getNextID in ImageIcon class can lead to overflow #25666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

prsadhuk
Copy link
Contributor

@prsadhuk prsadhuk commented Jun 6, 2025

ImageIcon.getNextID uses mediaTrackerID which do not detect overflow.

Theoretically there is a possibility that there can be overflow in the long time run or for large number of created "imageIcon"

Made sure there is no overflow and treat that loadImage as ABORTED

No regression testcase as it addresses theoretical possibility..


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8055461: getNextID in ImageIcon class can lead to overflow (Enhancement - P4) ⚠️ Issue is not open.

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/25666/head:pull/25666
$ git checkout pull/25666

Update a local copy of the PR:
$ git checkout pull/25666
$ git pull https://git.openjdk.org/jdk.git pull/25666/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 25666

View PR using the GUI difftool:
$ git pr show -t 25666

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/25666.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jun 6, 2025

👋 Welcome back psadhukhan! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Jun 6, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot added the rfr Pull request is ready for review label Jun 6, 2025
@openjdk
Copy link

openjdk bot commented Jun 6, 2025

@prsadhuk The following label will be automatically applied to this pull request:

  • client

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@mlbridge
Copy link

mlbridge bot commented Jun 6, 2025

Webrevs

width = image.getWidth(imageObserver);
height = image.getHeight(imageObserver);
return;
}
mTracker.addImage(image, id);
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm. Is there any acceptable logging we could use here?

(I'm just thinking: from the perspective of a developer trying to debug a customer complaint, this would be a lot easier to identify in System.err mentioned it...? Otherwise I'd start by trying to look for potential memory leaks or other red herrings.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I dont think there is any logging for this nor do we want to log...In all probability, the image will consume the heap memory so practically OOM will be raised by the time this overflow happens but it can happen if maybe all 1x1 images are used and subsequent needed memory reserved for Java process

Copy link
Contributor

Choose a reason for hiding this comment

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

OK. I don't feel like I really understand this fix (or the need for it), but I also am not too worried about it.

FWIW I was able to trigger this condition in an app that ran for about 30 minutes. (Maybe if I turned up the thread priority it could be faster?) I'm not going to keep exploring this, though, unless a pointed question comes up.

import javax.swing.*;
import java.awt.*;

public class ImageIconTest {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame f = new JFrame();
                ImageIcon icon = new ImageIcon();
                Image image = Toolkit.getDefaultToolkit().createImage("onepixel.gif");
                icon.setImage(image);
                JLabel label = new JLabel(icon);
                f.getContentPane().add(label);
                f.pack();
                f.setVisible(true);

                Thread t = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while (true) {
                            icon.setImage(image);
                        }
                    }
                });
                t.start();
            }
        });
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

trigger this condition in an app that ran for about 30 minutes.

What condition? I couldnt get OOM even after running several hours in Windows and Mac...id didnt reach 2147483647

This issue was not raised by me so clearly somebody felt the need for it..Also, I think it is better to get ABORTED status for the image to let the user know so that corrective action can be taken,
instead of aborting the application with OOM or whatever exception it gets thrown

Copy link
Contributor

Choose a reason for hiding this comment

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

What condition?

I just meant the int overflow condition. That test is not meant to demonstrate an OOM.

I understand why an OOM feels related to this ticket (and maybe that really is the compelling concern here), but I don't think that ticket mentioned memory consumption.

Here's a recording that (I think?) shows I reached the int overflow in about 15 minutes:
https://go.screenpal.com/watch/cT16h0nX6xx

(You may need to look at the system clock in top-right to accurately track time; parts of the video are sped up 10x. It should be pretty clear when the text cursor blinks super fast.)

We could expand that test to:
A. use an animated gif, or
B. try to load a new non-trivial image once we cross into a negative ID

... but that circles back around to the broader "what are we really trying to solve" question. (And I don't think I know the answer to that question.)

This issue was not raised by me so clearly somebody felt the need for it.

Sure.

Also, I think it is better to get ABORTED status for the image to let the user know so that corrective action can be taken, instead of aborting the application with OOM or whatever exception it gets thrown

OK. This feels too theoretical for me to invest strongly in a recommendation.

On the one hand: what you're saying makes sense, and obviously avoiding an OOM is a good thing to do.

On the other hand: if the integer overflow occurs in a non-trivial real-world usage: I'm guessing the images/icons already are actually being responsibly disposed of/garbage collected over time. So what we really need is to reset the counter or use a new MediaTracker if the int overflow condition is reached. For example, suppose we use all 2147483647 images IDs, and each image is 100x100px and 3 color channels. That's going to require about 60,000 GB of data (in terms of pixels). If those images are kept in memory, then we will reach an OOM long before we reach an integer overflow. So the fact that we get here (to the integer overflow) at all may (?) mean the application is responsibly discarding images and it's able to handle thousands more over time.

Most of all I'd like to see a real-world use case explaining how this impacts a user. Until then: this PR looks good/harmless (IMO).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK. This feels too theoretical for me to invest strongly in a recommendation.

That's why I mentioned in the description itself

Theoretically there is a possibility that there can be overflow in the long time run or for large number of created "imageIcon"

Copy link
Member

Choose a reason for hiding this comment

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

What's the deal with OOM at all?

If the application doesn't keep all the images it loads, no OOM will occur. Throwing OOM has nothing to do with the stated problem.

Copy link
Member

@aivanov-jdk aivanov-jdk left a comment

Choose a reason for hiding this comment

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

I think a better solution is to create a new MediaTracker, and start anew with id of zero.

The TRACKER_KEY is used only by ImageIcon, so it shouldn't break any apps.

Comment on lines 299 to 304
if (id < 0) {
loadStatus = MediaTracker.ABORTED;
width = image.getWidth(imageObserver);
height = image.getHeight(imageObserver);
return;
}
Copy link
Member

@aivanov-jdk aivanov-jdk Jun 10, 2025

Choose a reason for hiding this comment

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

This means that the application won't be able to load images using MediaTracker, or rather create new ImageIcon instances.

Is it really a problem that the id becomes negative? Does anything stop working?

I can't see that anything breaks because of that. The id is just an id, and it's compared for equality. (Although having a negative id could look suspicious.)

It may break at MediaEntry insertion, although it would still work probably…

Another problem may occur if and when the id wraps around again to positive numbers and 0, 1… are still being used.

width = image.getWidth(imageObserver);
height = image.getHeight(imageObserver);
return;
}
mTracker.addImage(image, id);
Copy link
Member

Choose a reason for hiding this comment

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

What's the deal with OOM at all?

If the application doesn't keep all the images it loads, no OOM will occur. Throwing OOM has nothing to do with the stated problem.

@prsadhuk
Copy link
Contributor Author

I think a better solution is to create a new MediaTracker, and start anew with id of zero.

The TRACKER_KEY is used only by ImageIcon, so it shouldn't break any apps.

If we create a new MediaTracker in ImageIcon and start anew with id of zero, then if application is having its own MediaTracker to track then when it calls for tracker.checkID(0) or tracker.statusID(0) then it will be confusing to which image it is referring to,
so I guess this current PR is more simpler in the sense it will notify ABORTED status for all images which overflows so that application can start anew with new ImageIcon and then use their MediaTracker to track this new ImageIcon.

Other thing that can be done is to modify "int" to "long" for all ids to further the overflow goalpost whereby MediaTracker.checkID,addImage, statusID signature will be changed..I dont think it will break any application but it will require CSR and since this issue is earmarked as Enhancement, maybe submitter also wanted that but I am not sure of going that route unless there is consensus on it..

@aivanov-jdk
Copy link
Member

I think a better solution is to create a new MediaTracker, and start anew with id of zero.
The TRACKER_KEY is used only by ImageIcon, so it shouldn't break any apps.

If we create a new MediaTracker in ImageIcon and start anew with id of zero, then if application is having its own MediaTracker to track then when it calls for tracker.checkID(0) or tracker.statusID(0) then it will be confusing to which image it is referring to

Why is it? ImageIcon has its own instance of MediaTracker. Yes, technically, it's possible to get this instance from AppContext but no application should do it.

If an application needs to load images, it has to create its own instance of MediaTracker. Since MediaTracker API requires a caller provided id, the instance of MediaTracker can't be shared, otherwise there's a clash in ids.

…so I guess this current PR is more simpler in the sense it will notify ABORTED status for all images which overflows so that application can start anew with new ImageIcon and then use their MediaTracker to track this new ImageIcon.

Could you elaborate please?

The mediaTrackerID is a static field in ImageIcon; MediaTracker is also a static-like shared instance that's stored in AppContext.

This means if mediaTrackerID overflows, the application can't use new ImageIcon to load any more images — the application can no longer work. The application can do nothing about it.

Other thing that can be done is to modify "int" to "long" for all ids to further the overflow goalpost whereby MediaTracker.checkID,addImage, statusID signature will be changed..I dont think it will break any application but it will require CSR and since this issue is earmarked as Enhancement, maybe submitter also wanted that but I am not sure of going that route unless there is consensus on it..

I thought about it, and using long instead of int was my first idea. Yet MediaTracker API needs to be modified.

But it won't solve the underlying problem: long can also wrap around into negative numbers. It'll take much longer, but long will wrap around.

@prsadhuk
Copy link
Contributor Author

Could you elaborate please?

The mediaTrackerID is a static field in ImageIcon; MediaTracker is also a static-like shared instance that's stored in AppContext.

This means if mediaTrackerID overflows, the application can't use new ImageIcon to load any more images — the application can no longer work. The application can do nothing about it.

WHy application cant use new ImageIcon I didnt understand?
I meant if application creates/instantiates a new ImageIcon (seeing the previous ImageIcon is giving ABORTED status for all images), the mediaTrackerID will reset from 0, this field will have no bearing from previous ImageIcon instance..am I missing something?

@prsadhuk
Copy link
Contributor Author

prsadhuk commented Jun 11, 2025

Something like this

ImageIcon icon = new ImageIcon();
                Image image = Toolkit.getDefaultToolkit().createImage("onepixel.gif");
                icon.setImage(image);
if (icon.getImageLoadStatus() == MediaTracker.ABORTED) {
   ImageIcon newIcon = new ImageIcon();
}

in this case wont mediaTrackerId starts from 0 again for "newIcon"?

@aivanov-jdk
Copy link
Member

Something like this

ImageIcon icon = new ImageIcon();
                Image image = Toolkit.getDefaultToolkit().createImage("onepixel.gif");
                icon.setImage(image);
if (icon.getImageLoadStatus == MediaTracker.ABORTED) {
   ImageIcon newIcon = new ImageIcon();
}

in this case wont mediaTrackerId starts from 0 again for "newIcon"?

No, it won't. Why will it?

mediaTrackerId is a static field, that is its value grows with each image loaded by a new instance of ImageIcon, and the value of mediaTrackerId is never reset. That's exactly the problem described in the JDK-8055461 bug report.

private static int mediaTrackerID;

private int getNextID() {
synchronized(getTracker()) {
return ++mediaTrackerID;
}
}

With the change that you propose, it will be impossible to load images with ImageIcon once the value of mediaTrackerId reaches Integer.MAX_VALUE.

@aivanov-jdk
Copy link
Member

I wonder why JDK-8055461 is an enhancement. It's a bug.

@prsadhuk
Copy link
Contributor Author

prsadhuk commented Jun 11, 2025

With the change that you propose, it will be impossible to load images with ImageIcon once the value of mediaTrackerId reaches Integer.MAX_VALUE.

OK.

I think a better solution is to create a new MediaTracker, and start anew with id of zero.

So, we need to have mediaTrackerID1 and use that but then what about when next Integer.MAX_VALUE is reached, create another MediaTracker and again use another mediaTrackerID2 ? it will be a loop then...
Won;t it better and simpler to make the id "long" which will make it use 2*64 images in one application?

@aivanov-jdk
Copy link
Member

I think a better solution is to create a new MediaTracker, and start anew with id of zero.

So, we need to have mediaTrackerID1 and use that but then what about when next Integer.MAX_VALUE is reached, create another MediaTracker and again use another mediaTrackerID2 ? it will be a loop then... Won;t it better and simpler to make the id "long" which will make it use 2*64 images in one application?

No, just one.

When mediaTrackerID reaches Integer.MAX_VALUE, the ImageIcon.getTracker method will create a new MediaTracker and put it into AppContext, then it will reset the value of mediaTrackerID to 0 before returning the new instance of the media tracker.

@aivanov-jdk
Copy link
Member

Won;t it better and simpler to make the id "long" which will make it use 2*64 images in one application?

Likely no application loads more than 2 billion images; it's even less likely that an application will load more than 2⁶⁴ images which is closed more than 10¹⁹.

In fact, it seems to me that nothing will actually break when the integer counter in mediaTrackerId overflows into negative numbers and then overflows back to positive numbers again. This id is used to identify an image in the loading queue.

Because of negative id, the order of MediaEntry elements in the list may be broken.

while (cur != null) {
if (cur.ID > me.ID) {
break;
}

It may result in malfunctioning… or not. If not, nothing bad will happen.

Having said that, I think that no fix is necessary. Yes, mediaTrackerId may overflow and wrap around in an application that loads billions of images, but is it really a problem if everything continue to work?

A problem would occur if and only if an image with id of 1, 2… still exists in the MediaTracker queue, which is highly unlikely. ImageIcon removes itself from the MediaTracker as soon as the image loads, successfully or not, which means by the time mediaTrackerId wraps around into positive numbers again, the first images are long gone from the MediaTracker queue.

@prsadhuk
Copy link
Contributor Author

prsadhuk commented Jun 11, 2025

Won;t it better and simpler to make the id "long" which will make it use 2*64 images in one application?

Likely no application loads more than 2 billion images; it's even less likely that an application will load more than 2⁶⁴ images which is closed more than 10¹⁹.

In fact, it seems to me that nothing will actually break when the integer counter in mediaTrackerId overflows into negative numbers and then overflows back to positive numbers again. This id is used to identify an image in the loading queue.

Because of negative id, the order of MediaEntry elements in the list may be broken.

while (cur != null) {
if (cur.ID > me.ID) {
break;
}

It may result in malfunctioning… or not. If not, nothing bad will happen.

Having said that, I think that no fix is necessary. Yes, mediaTrackerId may overflow and wrap around in an application that loads billions of images, but is it really a problem if everything continue to work?

A problem would occur if and only if an image with id of 1, 2… still exists in the MediaTracker queue, which is highly unlikely. ImageIcon removes itself from the MediaTracker as soon as the image loads, successfully or not, which means by the time mediaTrackerId wraps around into positive numbers again, the first images are long gone from the MediaTracker queue.

If mediaTrackerId overflows then id will increment backwards without any fix like
-2147483648, -2147483647, -2147483646, -2147483645, -2147483644 etc so it will take another Integer.MAX_VALUE+1 images to gain positive numbers..Not sure having overflown negative ids for all these images will cause any issues or not?

@aivanov-jdk
Copy link
Member

I've been trying to verify whether the fix is even necessary. My test case has been running for a few hours, and it reached only 0x30000000.

I will need to update the test case to cause the overflow from negative number to positive again. Let's see.

I still like the idea of not touching anything better than fixing this potential issue which doesn't seem to cause much trouble, at least from analysing the code.

@aivanov-jdk
Copy link
Member

Here's the test case that I used. (Thanks to @mickleness for the code, setting an image significantly increased the performance of the test; my initial test loaded an image from the disk for each instance of ImageIcon, and it took more than 6 hours to overflow mediaTrackerId into negative values.)

ImageIconOverflow.java
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.stream.IntStream;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

import static java.awt.image.BufferedImage.TYPE_INT_RGB;

public class ImageIconOverflow {
    private static final String IMAGE_FILENAME = "image.png";

    public static void main(String[] args) throws IOException {
        createImage();

        final Image image = Toolkit.getDefaultToolkit().createImage("onepixel.gif");

        IntStream.range(0, Integer.MAX_VALUE - 1)
                 .forEach((c) -> new ImageIcon(image));

        System.out.println("---");
        new ImageIcon(IMAGE_FILENAME);
        System.out.println("+++");
        new ImageIcon(IMAGE_FILENAME);
        new ImageIcon(IMAGE_FILENAME);

        System.out.println("###");
        IntStream.range(0, Integer.MAX_VALUE - 1)
                 .forEach((c) -> new ImageIcon(image));

        System.out.println("---");
        new ImageIcon(IMAGE_FILENAME);
        System.out.println("+++");
        new ImageIcon(IMAGE_FILENAME);
        new ImageIcon(IMAGE_FILENAME);
    }

    private static void createImage() throws IOException {
        BufferedImage image = new BufferedImage(1, 1, TYPE_INT_RGB);
        Graphics g = image.getGraphics();
        g.fillRect(0, 0, image.getWidth(), image.getHeight());
        g.dispose();

        File file = new File(IMAGE_FILENAME);
        ImageIO.write(image, "png", file);
        file.deleteOnExit();
    }
}

I also modified IconImage.java to print the id.

Diff for ImageIcon.java
diff --git a/src/java.desktop/share/classes/javax/swing/ImageIcon.java b/src/java.desktop/share/classes/javax/swing/ImageIcon.java
index 2bae31ba31f..8638da90567 100644
--- a/src/java.desktop/share/classes/javax/swing/ImageIcon.java
+++ b/src/java.desktop/share/classes/javax/swing/ImageIcon.java
@@ -296,6 +296,13 @@ protected void loadImage(Image image) {
         synchronized(mTracker) {
             int id = getNextID();

+            if (id <= 0x8000000F
+                || (id >= 0 && id <= 0x0000000F)
+                || id > 0x7ffffff0) {
+                System.out.println("ImageIcon.id=" + String.format("%08x", id)
+                                   + " " + id);
+            }
+
             mTracker.addImage(image, id);
             try {
                 mTracker.waitForID(id, 0);

In 25 minutes, the case overflowed twice: from positive into negative, and then from negative into positive.

ImageIcon.id=00000001 1
ImageIcon.id=00000002 2
...
ImageIcon.id=7ffffffd 2147483645
ImageIcon.id=7ffffffe 2147483646
---
ImageIcon.id=7fffffff 2147483647
+++
ImageIcon.id=80000000 -2147483648
ImageIcon.id=80000001 -2147483647
###
ImageIcon.id=80000002 -2147483646
...
ImageIcon.id=8000000f -2147483633
---
ImageIcon.id=00000000 0
+++
ImageIcon.id=00000001 1
ImageIcon.id=00000002 2

Thus, I think there's nothing to fix.

Yes, IconImage.mediaTrackerId can overflow, but it's not an issue.

Therefore, I propose to close the bug as ‘Not an Issue’ or ‘Won't Fix’.

Prasanta's second commit fd2ad26 resolves the problem with overflow the way that I suggested. But it fixes the potential problem that doesn't affect the behaviour of ImageIcon that is proved by the test in this comment.

@prsadhuk
Copy link
Contributor Author

prsadhuk commented Jun 13, 2025

But if the fix doesnt hamper anything and still fixes the problem of overflow (even if overflow itself doesn't apparently cause any issue) maybe we should consider it..
but I get popular sentiment of not fixing what ain't broke (or not proven yet to be broken)

@aivanov-jdk
Copy link
Member

But if the fix doesnt hamper anything and still fixes the problem of overflow

The fix makes the code more complicated for no reason because… in your own words

overflow itself doesn't apparently cause any issue

but I get popular sentiment of not fixing what ain't broke (or not proven yet to be broken)

Exactly! If it ain't broken and it works, don't fix it.

maybe we should consider it..

We may consider it… Let's see if anyone else has an opinion.

Yet I'm strongly inclined to not touching it because it works as expected even when IconImage.mediaTrackerId overflows from positive into negative values and then back to positive values. (There's nothing wrong with a negative id.)

@prrace
Copy link
Contributor

prrace commented Jun 13, 2025

Interesting that the MediaTracker is managed via an AppContext, but the id a static field.
Ah I see that there was a static field MediaTracker too, but it was deprecated in 1.8 and no longer used.
Probably being a protected field in a non-final class was the bigger issue there.

I don't see using a long as helping because aside from being able to still wrap around you
need an int to pass to MediaTracker and that isn't going to change.
New API and a ton of work would be needed to not fundamentally fix the issue.

I don't see a problem with a negative ID. MediaTracker doesn't require a positive value.
So the overflow doesn't matter.
The only issue is if one particular loadImage stalls and we cycle through and would re-use the ID.
restricting it to >=0 on its own just makes that problem more likely as you only have half the values.

I think that stalling is an unlikely problem.
But if it does happen then a new MediaTracker when we hit overflow would be a solution.
However then you need to manage a map of MediaTrackers .. it seems like a lot to do upfront.
I would just close this as will not fix.

@prsadhuk prsadhuk closed this Jun 14, 2025
@prsadhuk prsadhuk deleted the JDK-8055461 branch June 14, 2025 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
client [email protected] rfr Pull request is ready for review
Development

Successfully merging this pull request may close these issues.

4 participants