Skip to content
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

hotswap crashes JVM (bad stackmap) #227

Closed
trancexpress opened this issue Mar 29, 2023 · 7 comments
Closed

hotswap crashes JVM (bad stackmap) #227

trancexpress opened this issue Mar 29, 2023 · 7 comments
Assignees
Labels
bug Something isn't working

Comments

@trancexpress
Copy link
Contributor

trancexpress commented Mar 29, 2023

See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=565537

To reproduce, debug the snippet below with breakpoint at the only println() (line 23). Then change the string in the line above from "ab" to "abc" and save the file.

Crash: hs_err_pid148440.log

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  Internal Error (/data/git/jdk/jdk17u/src/hotspot/share/oops/generateOopMap.cpp:2165), pid=148440, tid=148458
#  fatal error: Illegal class file encountered. Try running with -Xverify:all in method setText
#
# JRE version: OpenJDK Runtime Environment (17.0.7) (slowdebug build 17.0.7-internal+0-adhoc.sandreev.jdk17u)
# Java VM: OpenJDK 64-Bit Server VM (slowdebug 17.0.7-internal+0-adhoc.sandreev.jdk17u, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64)
# Problematic frame:
# V  [libjvm.so+0xa9e82b]  GenerateOopMap::error_work(char const*, __va_list_tag*)+0x125
#
# Core dump will be written. Default location: Core dumps may be processed with "/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h" (or dumping to /data/workspaces/runtime-Eclipse/TestBug565537/core.148440)
#
# If you would like to submit a bug report, please visit:
#   https://bugreport.java.com/bugreport/crash.jsp
#

---------------  S U M M A R Y ------------

Command Line: -XX:+ShowCodeDetailsInExceptionMessages -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:45451 -javaagent:/data/workspaces/contributor_workspace/.metadata/.plugins/org.eclipse.pde.core/Eclipse/org.eclipse.osgi/246/0/.cp/lib/javaagent-shaded.jar -Dfile.encoding=UTF-8 EclipseHotswapCrash

Host: hotride, AMD Ryzen 9 5950X 16-Core Processor, 32 cores, 31G, Arch Linux
Time: Wed Mar 29 15:09:44 2023 EEST elapsed time: 6.348149 seconds (0d 0h 0m 6s)

---------------  T H R E A D  ---------------

Current thread (0x00007f1fd8214bb0):  VMThread "VM Thread" [stack: 0x00007f1f9ed1d000,0x00007f1f9ee1d000] [id=148458]

Stack: [0x00007f1f9ed1d000,0x00007f1f9ee1d000],  sp=0x00007f1f9ee1ac30,  free space=1015k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [libjvm.so+0xa9e82b]  GenerateOopMap::error_work(char const*, __va_list_tag*)+0x125
V  [libjvm.so+0xa9e92c]  GenerateOopMap::report_error(char const*, ...)+0xa6
V  [libjvm.so+0xa9e9a7]  GenerateOopMap::verify_error(char const*, ...)+0x77
V  [libjvm.so+0xa9a8ed]  GenerateOopMap::init_basic_blocks()+0x243
V  [libjvm.so+0xa9a630]  GenerateOopMap::do_interpretation()+0xb8
V  [libjvm.so+0xa9e678]  GenerateOopMap::compute_map(Thread*)+0x4be
V  [libjvm.so+0x103d106]  OopMapForCacheEntry::compute_map(Thread*)+0xfe
V  [libjvm.so+0x103dd39]  OopMapCacheEntry::fill(methodHandle const&, int)+0xdd
V  [libjvm.so+0x103ebed]  OopMapCache::compute_one_oop_map(methodHandle const&, int, InterpreterOopMap*)+0x4d
V  [libjvm.so+0xf99f24]  Method::mask_for(int, InterpreterOopMap*)+0x78
V  [libjvm.so+0x13c296d]  interpretedVFrame::stack_data(bool) const+0x77
V  [libjvm.so+0x13c28d3]  interpretedVFrame::locals() const+0x1d
V  [libjvm.so+0xdd9d2d]  VM_GetOrSetLocal::check_slot_type_no_lvt(javaVFrame*)+0xbf
V  [libjvm.so+0xdd9fa2]  VM_GetOrSetLocal::doit()+0x118
V  [libjvm.so+0x13e4031]  VM_Operation::evaluate()+0xe1
V  [libjvm.so+0x14484c4]  VMThread::evaluate_operation(VM_Operation*)+0x62
V  [libjvm.so+0x1448cb9]  VMThread::inner_execute(VM_Operation*)+0x30f
V  [libjvm.so+0x1449065]  VMThread::loop()+0x117
V  [libjvm.so+0x1448062]  VMThread::run()+0xfc
V  [libjvm.so+0x133b207]  Thread::call_run()+0x195
V  [libjvm.so+0x104fc85]  thread_native_entry(Thread*)+0x199
import java.util.ArrayList;

public class EclipseHotswapCrash {
	static public void main (String[] args) throws Exception {
		BitmapFont font = new BitmapFont();
		GlyphLayout layout = new GlyphLayout();
		Color color = new Color();
		layout.setText(font, "ab", 0, 2, color, 100, 0, true, null);
	}

	static public class GlyphLayout {
		static private final Pool<GlyphRun> glyphRunPool = new Pool(GlyphRun.class);
		static private final Pool<Color> colorPool = new Pool(Color.class);
		static private final ArrayList<Color> colorStack = new ArrayList(4);

		public final ArrayList<GlyphRun> runs = new ArrayList(1);
		public float width, height;

		public void setText (BitmapFont font, CharSequence str, int start, int end, Color color, float targetWidth, int halign,
			boolean wrap, String truncate) {

			if (str.equals("ab")) //
				System.out.println(); // Set breakpoint here, modify "ab" -> "abc", save, hotswap crashes.

			ArrayList<GlyphRun> runs = this.runs;
			glyphRunPool.freeAll(runs);
			runs.clear();

			BitmapFontData fontData = font.data;
			if (start == end) { // Empty string.
				width = 0;
				height = fontData.capHeight;
				return;
			}

			if (truncate != null)
				wrap = true; // Ensures truncate code runs, doesn't actually cause wrapping.
			else if (targetWidth <= fontData.spaceXadvance * 3) //
				wrap = false; // Avoid one line per character, which is very inefficient.

			Color nextColor = color;
			boolean markupEnabled = fontData.markupEnabled;
			if (markupEnabled) {
				for (int i = 1, n = colorStack.size(); i < n; i++)
					colorPool.free(colorStack.get(i));
			}

			float x = 0, y = 0, down = fontData.down;
			Glyph lastGlyph = null;
			int runStart = start;
			outer:
			while (true) {
				// Each run is delimited by newline or left square bracket.
				int runEnd = -1;
				boolean newline = false;
				if (start == end) {
				} else {
					switch (str.charAt(start++)) {
					case '\n':
						break;
					case '[':
						break;
					}
				}

				if (runEnd != -1) {
					runEnded:
					if (runEnd != runStart) { // Eg, when a color tag is at text start or a line is "\n".
						// Store the run that has ended.
						GlyphRun run = glyphRunPool.obtain();
						run.color.set(color);
						fontData.getGlyphs(run, str, runStart, runEnd, lastGlyph);
						lastGlyph = run.glyphs.get(run.glyphs.size() - 1);
						run.x = x;
						run.y = y;
						if (newline || runEnd == end) adjustLastGlyph(fontData, run);
						runs.add(run);

						int n = run.xAdvances.size();

						// Wrap or truncate.
						ArrayList<Float> xAdvances = run.xAdvances;
						x += xAdvances.get(0) + xAdvances.get(1); // X offset relative to the drawing position + first xAdvance.
						for (int i = 2; i < n; i++) {
							Glyph glyph = run.glyphs.get(i - 1);

							if (truncate != null) {
								// Truncate.
								truncate(fontData, run, targetWidth, truncate, i, glyphRunPool);
								break outer;
							}

							// Wrap.
							int wrapIndex = fontData.getWrapIndex(run.glyphs, i);
							GlyphRun next;
							if (wrapIndex == 0) { // Move entire run to next line.
								next = run;

								// Remove leading whitespace.
								for (int glyphCount = run.glyphs.size(); wrapIndex < glyphCount; wrapIndex++)
									if (!fontData.isWhitespace((char)run.glyphs.get(wrapIndex).id)) break;
								if (wrapIndex > 0) {
									for (int ii = 0; ii < wrapIndex - 1; ii++)
										run.glyphs.remove(0);
									for (int ii = 0; ii < wrapIndex - 1; ii++)
										run.xAdvances.remove(1);
								}
								xAdvances.set(0, -run.glyphs.get(0).xoffset * fontData.scaleX - fontData.padLeft);
							}
						}
					}
				}
			}
		}

		/** @param truncate May be empty string. */
		private void truncate (BitmapFontData fontData, GlyphRun run, float targetWidth, String truncate, int widthIndex,
			Pool<GlyphRun> glyphRunPool) {
		}

		/** Breaks a run into two runs at the specified wrapIndex.
		 * @return May be null if second run is all whitespace. */
		private GlyphRun wrap (BitmapFontData fontData, GlyphRun first, int wrapIndex, int widthIndex) {
			return first;
		}

		/** Adjusts the xadvance of the last glyph to use its width instead of xadvance. */
		private void adjustLastGlyph (BitmapFontData fontData, GlyphRun run) {
			Glyph last = run.glyphs.get(run.glyphs.size() - 1);
			if (last.fixedWidth) return;
			float width = (last.width + last.xoffset) * fontData.scaleX - fontData.padRight;
			run.xAdvances.set(run.xAdvances.size() - 1, width);
		}

	}

	/** Stores glyphs and positions for a piece of text which is a single color and does not span multiple lines.
	 * @author Nathan Sweet */
	public class GlyphRun {
		public ArrayList<Glyph> glyphs = new ArrayList();
		/** Contains glyphs.size+1 entries: First entry is X offset relative to the drawing position. Subsequent entries are the
		 * X advance relative to previous glyph position. Last entry is the width of the last glyph. */
		public ArrayList<Float> xAdvances = new ArrayList();
		public float x, y, width;
		public final Color color = new Color();
	}

	static public class BitmapFont {
		public BitmapFontData data = new BitmapFontData();
	}

	static public class BitmapFontData {
		public float capHeight;
		public int spaceXadvance;
		public boolean markupEnabled;
		public float down;
		public float scaleX = 1;
		public int padRight;
		public int padLeft;
		public float blankLineScale;

		public void getGlyphs (GlyphRun run, CharSequence str, int runStart, int runEnd, Glyph lastGlyph) {
			run.xAdvances.add(1f);
			for (int i = 0, n = str.length(); i < n; i++) {
				run.xAdvances.add(16f);
				run.glyphs.add(new Glyph(str.charAt(i)));
			}
		}

		public int getWrapIndex (ArrayList<Glyph> glyphs, int i) {
			return i;
		}

		public boolean isWhitespace (char id) {
			return false;
		}
	}

	static public class Color {
		public void set (Color color) {
		}
	}

	public static class Glyph {
		public int id;
		public int srcX;
		public int srcY;
		public int width = 16, height = 16;
		public float u, v, u2, v2;
		public int xoffset, yoffset;
		public int xadvance = 16;
		public byte[][] kerning;
		public boolean fixedWidth;

		public Glyph (int id) {
			this.id = id;
		}
	}

	public static class Pool<T> {
		private Class<T> type;

		public Pool (Class<T> type) {
			this.type = type;
		}

		public void freeAll (ArrayList<T> runs) {
		}

		public void free (T color) {
		}

		public T obtain () {
			try {
				return type.newInstance();
			} catch (Exception ex) {
				throw new RuntimeException(ex);
			}
		}
	}
}
@trancexpress trancexpress added the bug Something isn't working label Mar 29, 2023
@trancexpress
Copy link
Contributor Author

trancexpress commented Mar 29, 2023

Using the .class files, which Eclipse produces, in jdb doesn't result in a crash when redefining EclipseHotswapCrash$GlyphLayout (after change from "ab" to "abc")... This is also the only .class file that changes between the crashing and not crashing case. I could be doing something slightly differently though, when compared to what Eclipse does with HCR.

I'm also not finding any notable differences between the StackMapTable parts of the class files (also when comparing with javac). At least the ones found in the output folder. Will have to check what HCR uses and how the redefine is done.

The code in the JDK which results in the crash:

Thread 8 "VM Thread" hit Breakpoint 2, GenerateOopMap::init_basic_blocks (this=0x7fb65fc294d0) at /data/git/jdk/jdk17u/src/hotspot/share/oops/generateOopMap.cpp:989
989       if (bbNo !=_bb_count) {
(gdb) p bbNo
$1 = 41
(gdb) p _bb_count
$2 = 42

If I comment out line 32, there is no crash:

				//height = fontData.capHeight;

In the code block above, the values are:

Thread 8 "VM Thread" hit Breakpoint 1, GenerateOopMap::init_basic_blocks (this=0x7f4335ffe4d0) at /data/git/jdk/jdk17u/src/hotspot/share/oops/generateOopMap.cpp:989
989       if (bbNo !=_bb_count) {
(gdb) p bbNo
$1 = 42
(gdb) p _bb_count
$2 = 42

Will have to understand what exactly the JVM code is reading, to know what to check in Eclipse code...

@trancexpress
Copy link
Contributor Author

trancexpress commented Mar 30, 2023

I tried the following sequence in jdb, it seems to be the closest to what Eclipse does:

cd .../bin/ # go to the output folder
/usr/lib/jvm/java-17-openjdk/bin/jdb EclipseHotswapCrash
stop at EclipseHotswapCrash$GlyphLayout:23
run
# change "ab" to "abc" in the source file, save and let auto-build compile it
pop
redefine EclipseHotswapCrash$GlyphLayout EclipseHotswapCrash$GlyphLayout.class
step into # no crash, in Eclipse the crash occurs when this is done
cont

I have experimented with adding output, the redefine is done successfully. Eclipse redefines all the classes (outer and inner), but I tried modifying the code to only redefine the actually changed inner class, the crash still occurs in Eclipse.

The redefine is done with the bytes from the .class files in the output folder, see JavaHotCodeReplaceManager.getTypesToBytes().

I'm not sure that the compiler is doing something bad here. If it is, its hard to follow what the Eclipse debugger is doing to run into the crash.

The crash occurs when this code is executed, after the step into operation:

JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.SF_THIS_OBJECT, outBytes);
"Worker-40: Label Job" eclipse-jdt/eclipse.jdt.core#594 prio=5 os_prio=0 cpu=52.71ms elapsed=129.51s tid=0x00007f3224105590 nid=0x1cb83 runnable  [0x00007f31455fe000]
   java.lang.Thread.State: RUNNABLE
        at org.eclipse.jdi.internal.StackFrameImpl.thisObject(StackFrameImpl.java:253)
        at org.eclipse.jdt.internal.debug.core.model.JDIStackFrame.getUnderlyingThisObject(JDIStackFrame.java:1015)
        - locked <0x000000064f957ef8> (a org.eclipse.jdt.internal.debug.core.model.JDIThread)
        at org.eclipse.jdt.internal.debug.core.model.JDIStackFrame.getReceivingTypeName(JDIStackFrame.java:1094)
        at org.eclipse.jdt.internal.debug.ui.JDIModelPresentation.getStackFrameText(JDIModelPresentation.java:1871)
        at org.eclipse.jdt.internal.debug.ui.JDIModelPresentation.getText(JDIModelPresentation.java:227)
        at org.eclipse.debug.internal.ui.LazyModelPresentation.getText(LazyModelPresentation.java:188)
        at org.eclipse.debug.internal.ui.DelegatingModelPresentation.getText(DelegatingModelPresentation.java:151)
        at org.eclipse.debug.internal.ui.model.elements.DebugElementLabelProvider.getLabel(DebugElementLabelProvider.java:39)
        at org.eclipse.debug.internal.ui.model.elements.ElementLabelProvider.getLabel(ElementLabelProvider.java:297)
        at org.eclipse.debug.internal.ui.model.elements.ElementLabelProvider.retrieveLabel(ElementLabelProvider.java:200)
        at org.eclipse.jdt.internal.debug.ui.variables.JavaStackFrameLabelProvider.retrieveLabel(JavaStackFrameLabelProvider.java:44)
        at org.eclipse.debug.internal.ui.model.elements.ElementLabelProvider$LabelUpdater.run(ElementLabelProvider.java:147)
        at org.eclipse.debug.internal.ui.model.elements.ElementLabelProvider$LabelJob.run(ElementLabelProvider.java:74)
        at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)

This is when the Debug view is trying to read the stack frame names. The stack frame in getText() shows <unknown receiving type> directly after the crash (since SF_THIS_OBJECT crashes the JVM, I assume).

With this change in JDT debug code, there is no crash:

diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIStackFrame.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIStackFrame.java
index 07751799b..16d82ebc6 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIStackFrame.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIStackFrame.java
@@ -1088,6 +1088,9 @@ public class JDIStackFrame extends JDIDebugElement implements IJavaStackFrame {
        public String getReceivingTypeName() throws DebugException {
                if (fStackFrame == null || fReceivingTypeName == null) {
                        try {
+                               if (true) {
+                                       return JDIDebugModelMessages.JDIStackFrame__unknown_receiving_type__2;
+                               }
                                if (isObsolete()) {
                                        fReceivingTypeName = JDIDebugModelMessages.JDIStackFrame__unknown_receiving_type__2;
                                } else {

The code that Eclipse is invoking is: https://docs.oracle.com/javase/7/docs/jdk/api/jpda/jdi/com/sun/jdi/StackFrame.html#thisObject()

I don't know how to simulate a call like this with jdb... I tried running different commands in the jdb session that access this, but there is no crash.

Same crash occurs if I hold the debugger at JavaHotCodeReplaceManager.redefineTypesJDK() and replace EclipseHotswapCrash$GlyphLayout.class with the .class file produced by javac (OpenJDK 17). If I comment out line 33 (i.e. no crash), I don't see a problem with replacing the class with javac output.

Do we have a JDK bug here?

@trancexpress trancexpress self-assigned this Mar 30, 2023
@trancexpress trancexpress transferred this issue from eclipse-jdt/eclipse.jdt.core Mar 30, 2023
@trancexpress
Copy link
Contributor Author

Moved to JDT debug, I didn't see anything that indicates a compiler bug - other than so far not being able to reproduce with jdb.

@trancexpress
Copy link
Contributor Author

trancexpress commented Mar 30, 2023

I can reproduce this also in IntelliJ, using slightly adjusted snippet. I'll report this to OpenJDK maintainers.

hs_err_pid7064_intellij.log
https://user-images.githubusercontent.com/24752155/229045212-dc7ef012-896f-496a-b910-b228a1d63ff7.mp4

Steps in IntelliJ:

  1. Debug the snippet with breakpoint at line 25.
  2. On the main thread, at the paused stack frame, right-click -> Reset Frame.
  3. Change the string at line 22 from "ab" to "abc", save.
  4. Build, accept reloading classes.
  5. Step into.

hs_err_pid8664_eclipse.log
https://user-images.githubusercontent.com/24752155/229045270-5f8abb3e-0f3a-4bb0-8d86-03520ee325a0.mp4

Steps with Eclipse:

  1. Debug the snippet with breakpoint at line 25.
  2. Change the string at line 22 from "ab" to "abc", save.

Adjusted snippet:

import java.util.ArrayList;

public class EclipseHotswapCrash {
	static public void main (String[] args) throws Exception {
		BitmapFont font = new BitmapFont();
		GlyphLayout layout = new GlyphLayout();
		Color color = new Color();
		layout.setText(font, "ab", 0, 2, color, 100, 0, true, null);
	}

	static public class GlyphLayout {
		static private final Pool<GlyphRun> glyphRunPool = new Pool(GlyphRun.class);
		static private final Pool<Color> colorPool = new Pool(Color.class);
		static private final ArrayList<Color> colorStack = new ArrayList(4);

		public final ArrayList<GlyphRun> runs = new ArrayList(1);
		public float width, height;

		public void setText (BitmapFont font, CharSequence str, int start, int end, Color color, float targetWidth, int halign,
			boolean wrap, String truncate) {

			String t = "ab";
			System.out.println(t);
			if (str.equals(t)) //
				System.out.println(); // Set breakpoint here, modify "ab" -> "abc", save, hotswap crashes.
			ArrayList<GlyphRun> runs = this.runs;
			glyphRunPool.freeAll(runs);
			runs.clear();

			BitmapFontData fontData = font.data;
			if (start == end) { // Empty string.
				width = 0;
				height = fontData.capHeight;
				return;
			}

			if (truncate != null)
				wrap = true; // Ensures truncate code runs, doesn't actually cause wrapping.
			else if (targetWidth <= fontData.spaceXadvance * 3) //
				wrap = false; // Avoid one line per character, which is very inefficient.

			Color nextColor = color;
			boolean markupEnabled = fontData.markupEnabled;
			if (markupEnabled) {
				for (int i = 1, n = colorStack.size(); i < n; i++)
					colorPool.free(colorStack.get(i));
			}

			float x = 0, y = 0, down = fontData.down;
			Glyph lastGlyph = null;
			int runStart = start;
			outer:
			while (true) {
				// Each run is delimited by newline or left square bracket.
				int runEnd = -1;
				boolean newline = false;
				if (start == end) {
				} else {
					switch (str.charAt(start++)) {
					case '\n':
						break;
					case '[':
						break;
					}
				}

				if (runEnd != -1) {
					runEnded:
					if (runEnd != runStart) { // Eg, when a color tag is at text start or a line is "\n".
						// Store the run that has ended.
						GlyphRun run = glyphRunPool.obtain();
						run.color.set(color);
						fontData.getGlyphs(run, str, runStart, runEnd, lastGlyph);
						lastGlyph = run.glyphs.get(run.glyphs.size() - 1);
						run.x = x;
						run.y = y;
						if (newline || runEnd == end) adjustLastGlyph(fontData, run);
						runs.add(run);

						int n = run.xAdvances.size();

						// Wrap or truncate.
						ArrayList<Float> xAdvances = run.xAdvances;
						x += xAdvances.get(0) + xAdvances.get(1); // X offset relative to the drawing position + first xAdvance.
						for (int i = 2; i < n; i++) {
							Glyph glyph = run.glyphs.get(i - 1);

							if (truncate != null) {
								// Truncate.
								truncate(fontData, run, targetWidth, truncate, i, glyphRunPool);
								break outer;
							}

							// Wrap.
							int wrapIndex = fontData.getWrapIndex(run.glyphs, i);
							GlyphRun next;
							if (wrapIndex == 0) { // Move entire run to next line.
								next = run;

								// Remove leading whitespace.
								for (int glyphCount = run.glyphs.size(); wrapIndex < glyphCount; wrapIndex++)
									if (!fontData.isWhitespace((char)run.glyphs.get(wrapIndex).id)) break;
								if (wrapIndex > 0) {
									for (int ii = 0; ii < wrapIndex - 1; ii++)
										run.glyphs.remove(0);
									for (int ii = 0; ii < wrapIndex - 1; ii++)
										run.xAdvances.remove(1);
								}
								xAdvances.set(0, -run.glyphs.get(0).xoffset * fontData.scaleX - fontData.padLeft);
							}
						}
					}
				}
			}
		}

		/** @param truncate May be empty string. */
		private void truncate (BitmapFontData fontData, GlyphRun run, float targetWidth, String truncate, int widthIndex,
			Pool<GlyphRun> glyphRunPool) {
		}

		/** Breaks a run into two runs at the specified wrapIndex.
		 * @return May be null if second run is all whitespace. */
		private GlyphRun wrap (BitmapFontData fontData, GlyphRun first, int wrapIndex, int widthIndex) {
			return first;
		}

		/** Adjusts the xadvance of the last glyph to use its width instead of xadvance. */
		private void adjustLastGlyph (BitmapFontData fontData, GlyphRun run) {
			Glyph last = run.glyphs.get(run.glyphs.size() - 1);
			if (last.fixedWidth) return;
			float width = (last.width + last.xoffset) * fontData.scaleX - fontData.padRight;
			run.xAdvances.set(run.xAdvances.size() - 1, width);
		}

	}

	/** Stores glyphs and positions for a piece of text which is a single color and does not span multiple lines.
	 * @author Nathan Sweet */
	public class GlyphRun {
		public ArrayList<Glyph> glyphs = new ArrayList();
		/** Contains glyphs.size+1 entries: First entry is X offset relative to the drawing position. Subsequent entries are the
		 * X advance relative to previous glyph position. Last entry is the width of the last glyph. */
		public ArrayList<Float> xAdvances = new ArrayList();
		public float x, y, width;
		public final Color color = new Color();
	}

	static public class BitmapFont {
		public BitmapFontData data = new BitmapFontData();
	}

	static public class BitmapFontData {
		public float capHeight;
		public int spaceXadvance;
		public boolean markupEnabled;
		public float down;
		public float scaleX = 1;
		public int padRight;
		public int padLeft;
		public float blankLineScale;

		public void getGlyphs (GlyphRun run, CharSequence str, int runStart, int runEnd, Glyph lastGlyph) {
			run.xAdvances.add(1f);
			for (int i = 0, n = str.length(); i < n; i++) {
				run.xAdvances.add(16f);
				run.glyphs.add(new Glyph(str.charAt(i)));
			}
		}

		public int getWrapIndex (ArrayList<Glyph> glyphs, int i) {
			return i;
		}

		public boolean isWhitespace (char id) {
			return false;
		}
	}

	static public class Color {
		public void set (Color color) {
		}
	}

	public static class Glyph {
		public int id;
		public int srcX;
		public int srcY;
		public int width = 16, height = 16;
		public float u, v, u2, v2;
		public int xoffset, yoffset;
		public int xadvance = 16;
		public byte[][] kerning;
		public boolean fixedWidth;

		public Glyph (int id) {
			this.id = id;
		}
	}

	public static class Pool<T> {
		private Class<T> type;

		public Pool (Class<T> type) {
			this.type = type;
		}

		public void freeAll (ArrayList<T> runs) {
		}

		public void free (T color) {
		}

		public T obtain () {
			try {
				return type.newInstance();
			} catch (Exception ex) {
				throw new RuntimeException(ex);
			}
		}
	}
}

@trancexpress
Copy link
Contributor Author

https://bugs.openjdk.org/browse/JDK-8307331 was opened.

Since it was determined that the bug is in the JVM, I'll close this issue. If later on it turns out Eclipse needs to do more, I'll re-open.

@trancexpress trancexpress closed this as not planned Won't fix, can't repro, duplicate, stale May 3, 2023
@trancexpress
Copy link
Contributor Author

A PR was opened: openjdk/jdk#13795

@trancexpress
Copy link
Contributor Author

Fixed with OpenJDK 17.0.8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant