A few months ago, I was investigating some exception handling OpenJDK bugs on Windows AArch64. One of the bugs was in the safefetch implementation. I needed to switch part of the implementation to assembly language (similar to the Linux and macosx aarch64 safefetch implementations). Compilation failed after I added the new safefetch_windows_aarch64.S assembly source file. The failing command line was in the .cmdline file when the build terminated:
Command from build\windows-x86_64-server-slowdebug\make-support\failure-logs\support_native_jdk.incubator.vector_libjsvml_jsvml_d_acos_windows_x86.obj.cmdline
/cygdrive/d/java/ms/dups/openjdk-jdk/build/windows-x86_64-server-slowdebug/fixpath exec /cygdrive/c/progra~1/mib055~1/2022/enterprise/vc/tools/msvc/14.44.35207/bin/hostx64/x64/ml64.exe -nologo -c -Ta -Fo/cygdrive/d/java/ms/dups/openjdk-jdk/build/windows-x86_64-server-slowdebug/support/native/jdk.incubator.vector/libjsvml/jsvml_d_acos_windows_x86.obj /cygdrive/d/java/ms/dups/openjdk-jdk/src/jdk.incubator.vector/windows/native/libjsvml/jsvml_d_acos_windows_x86.S
From build\windows-x86_64-server-slowdebug\make-support\failure-logs\support_native_jdk.incubator.vector_libjsvml_jsvml_d_acos_windows_x86.obj.log
Assembling: -Fod:\java\ms\dups\openjdk-jdk\build\windows-x86_64-server-slowdebug\support\native\jdk.incubator.vector\libjsvml\jsvml_d_acos_windows_x86.obj
MASM : fatal error A1000:cannot open file : -Fod:\java\ms\dups\openjdk-jdk\build\windows-x86_64-server-slowdebug\support\native\jdk.incubator.vector\libjsvml\jsvml_d_acos_windows_x86.obj
I just needed to have a separate else branch to handle setting up armasm64.exe to avoid passing ml64.exe flags to armasm64.exe. This successfully assembled my AArch64 assembly source file. However, the JVM would terminate with an access violation, which clearly isn’t supposed to happen because the fetch is supposed to be safe, by definition! I asked copilot: when would the program counter pointing at this aarch64 instruction result in an access violation? mov x0, x1. One scenario:
The Program Counter (PC) is pointing to an invalid address
If the PC is pointing to a location that is not mapped in the process’s address space (e.g., due to corruption, jumping to unmapped memory, or executing data as code), then fetching the instruction itself could trigger an access violation.
Example: If the PC points to a region of memory that has been freed or is protected (e.g., read-only or non-executable), the CPU will raise a fault when trying to fetch or decode the instruction.
This gave me a hint that my assembly instructions were probably not in an executable page! I found the AREA directive details at ARM Compiler armasm Reference Guide Version 6.01. It was tricky that the first AREA argument is a name and could therefore be anything. If I recall correctly, I think the access violation was because I didn’t have the CODE attribute on the AREA. With that fixed, I was able to successfully execute the compiled JVM.
This resulted in this error, which confirmed that it was a valid place to set the flag:
=== Output from failing command(s) repeated here ===
* For target support_native_jdk.incubator.vector_libjsvml_BUILD_LIBJSVML_run_ld:
LINK : fatal error LNK1181: cannot open input file 'd:\java\forks\dups12\openjdk\jdk\build\windows-x86_64-server-slowdebug\support\native\jdk.incubator.vector\libjsvml\jsvml_d_acos_windows_x86.obj'
* For target support_native_jdk.incubator.vector_libjsvml_jsvml_d_acos_windows_x86.obj:
Assembling: sdf
MASM : fatal error A1000:cannot open file : sdf
* For target support_native_jdk.incubator.vector_libjsvml_jsvml_d_asin_windows_x86.obj:
Assembling: sdf
MASM : fatal error A1000:cannot open file : sdf
After Magnus’s feedback on 8/23, I reverted that change and tried this instead:
diff --git a/make/autoconf/flags.m4 b/make/autoconf/flags.m4
index d50538108a4..8ba1a313cb2 100644
--- a/make/autoconf/flags.m4
+++ b/make/autoconf/flags.m4
@@ -320,6 +320,11 @@ AC_DEFUN([FLAGS_SETUP_TOOLCHAIN_CONTROL],
[
if test "x$TOOLCHAIN_TYPE" = xmicrosoft; then
CC_OUT_OPTION=-Fo
+ if test "x$OPENJDK_TARGET_CPU" = xaarch64; then
+ AS_NON_ASM_EXTENSION_FLAG=
+ else
+ AS_NON_ASM_EXTENSION_FLAG=-Tazzz
+ endif
else
# The option used to specify the target .o,.a or .so file.
# When compiling, how to specify the to be created object file.
diff --git a/make/common/native/CompileFile.gmk b/make/common/native/CompileFile.gmk
index 26472da6d02..7f8e8ffeddc 100644
--- a/make/common/native/CompileFile.gmk
+++ b/make/common/native/CompileFile.gmk
@@ -236,7 +236,7 @@ define CreateCompiledNativeFileBody
# For assembler calls just create empty dependency lists
$$(call ExecuteWithLog, $$@, $$(call MakeCommandRelative, \
$$($1_COMPILER) $$($1_FLAGS) \
- $(CC_OUT_OPTION)$$($1_OBJ) $$($1_SRC_FILE))) \
+ $(CC_OUT_OPTION)$$($1_OBJ) $(AS_NON_ASM_EXTENSION_FLAG) $$($1_SRC_FILE))) \
| $(TR) -d '\r' | $(GREP) -v -e "Assembling:" || test "$$$$?" = "1" ; \
$(ECHO) > $$($1_DEPS_FILE) ; \
$(ECHO) > $$($1_DEPS_TARGETS_FILE)
The configure script failed:
Runnable configure script is not present
Generating runnable configure script at /cygdrive/d/java/forks/dups12/openjdk/jdk/build/.configure-support/generated-configure.sh
Using autoconf at /usr/bin/autoconf [autoconf (GNU Autoconf) 2.72]
-:166141: error: possibly undefined macro: AS_NON_ASM_EXTENSION_FLAG
If this token and others are legitimate, please use m4_pattern_allow.
See the Autoconf documentation.
configure: Configuration created at Sat Aug 23 15:11:36 MDT 2025.
On the prompt " recipe commences before first target" Copilot says:
The error message “recipe commences before first target” in GNU Make typically means that there’s a line in your Makefile that starts with a tab (indicating a recipe), but it appears before any target has been defined. In Makefiles, recipes (commands to execute) must follow a target and its dependencies.
I moved the check further up, which fixed the build. After the PR was closed, I got a comment about the quotes I introduced! They shouldn’t be there :(.
# Download and install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"
# Download and install Node.js:
nvm install 22
# Verify the Node.js version:
node -v # Should print "v22.20.0".
# Verify npm version:
npm -v # Should print "10.9.3".
Installing Node.js on Windows
The post on my Introduction to Tauri required me to install Node.js on my Windows (x64) desktop. Interestingly, the node and npm commands cannot be found now even though "C:\Program Files\nodejs\node" -v outputs v22.16.0. Installing the latest MSI fixes this. Running node -v from any directory outputs v22.20.0.
npm warn deprecated npmlog@2.0.4: This package is no longer supported.
npm warn deprecated gauge@1.2.7: This package is no longer supported.
npm warn deprecated are-we-there-yet@1.1.7: This package is no longer supported.
npm warn deprecated boolean@3.2.0: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
added 941 packages in 24s
151 packages are looking for funding
run `npm fund` for details
npm notice
npm notice New major version of npm available! 10.9.3 -> 11.6.2
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.6.2
npm notice To update run: npm install -g npm@11.6.2
npm notice
I then updated npm before proceeding:
npm install -g npm@11.6.2
added 1 package in 4s
28 packages are looking for funding
run `npm fund` for details
Creating my First VS Code Extension
The initial idea I had was for an extension to create a named branch and create a file in a specific subdirectory of the repo. I usually need to do this in OpenJDK development, so it seemed like a good idea for a time saving extension. Naming is hard though, so I went with the name “createfileinbranch” for my extension.
C:\repos\createfileinbranch>yo code
_-----_ ╭──────────────────────────╮
| | │ Welcome to the Visual │
|--(o)--| │ Studio Code Extension │
`---------´ │ generator! │
( _´U`_ ) ╰──────────────────────────╯
/___A___\ /
| ~ |
__'.___.'__
´ ` |° ´ Y `
? What type of extension do you want to create? (Use arrow keys)
> New Extension (TypeScript)
New Extension (JavaScript)
New Color Theme
New Language Support
New Code Snippets
New Keymap
New Extension Pack
New Language Pack (Localization)
New Web Extension (TypeScript)
New Notebook Renderer (TypeScript)
? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? fileforbranch
? What's the identifier of your extension? fileforbranch
? What's the description of your extension? Create a file from a template in a given branch
? Initialize a git repository? Yes
? Which bundler to use? (Use arrow keys)
> unbundled
webpack
esbuild
The package manager choices were npm, yarn, or pnpm. I selected npm.
C:\repos\createfileinbranch>yo code
_-----_ ╭──────────────────────────╮
| | │ Welcome to the Visual │
|--(o)--| │ Studio Code Extension │
`---------´ │ generator! │
( _´U`_ ) ╰──────────────────────────╯
/___A___\ /
| ~ |
__'.___.'__
´ ` |° ´ Y `
? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? fileforbranch
? What's the identifier of your extension? fileforbranch
? What's the description of your extension? Create a file from a template in a given branch
? Initialize a git repository? Yes
? Which bundler to use? unbundled
? Which package manager to use? npm
Writing in C:\repos\createfileinbranch\fileforbranch...
create fileforbranch\.vscode\extensions.json
create fileforbranch\.vscode\launch.json
create fileforbranch\.vscode\settings.json
create fileforbranch\.vscode\tasks.json
create fileforbranch\package.json
create fileforbranch\tsconfig.json
create fileforbranch\.vscodeignore
create fileforbranch\vsc-extension-quickstart.md
create fileforbranch\.gitignore
create fileforbranch\README.md
create fileforbranch\CHANGELOG.md
create fileforbranch\src\extension.ts
create fileforbranch\src\test\extension.test.ts
create fileforbranch\.vscode-test.mjs
create fileforbranch\eslint.config.mjs
Changes to package.json were detected.
Running npm install for you to install the required dependencies.
npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
added 260 packages, and audited 261 packages in 9s
74 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Your extension fileforbranch has been created!
To start editing with Visual Studio Code, use the following commands:
code fileforbranch
Open vsc-extension-quickstart.md inside the new extension for further instructions
on how to modify, test and publish your extension.
For more information, also visit http://code.visualstudio.com and follow us @code.
? Do you want to open the new folder with Visual Studio Code? (Use arrow keys)
> Open with `code`
Skip
VS Code asked if I wanted to install the recommended extensions for this project. I agreed, and the suggestions were ESLint – Pluggable JavaScript Linter and the Extension Test Runner. They were shown in the extensions tab with “@recommended” in the search box. I could successfully F5 the extension and obtain the message it displayed in VS Code.
The Open in GitHub Extension
I noticed though that something I do a lot is find files in on GitHub by copying their relative path and pasting it into the search box on the GitHub website. Why can’t I automatically just open that URL? This seemed like a more impactful extension to build! I initially used the fileforbranch extension to experiment with this idea.
I decided to use Copilot to see if I could move things along. I was quite surprised to find that the agent implemented a fully functional extension! Granted it’s not complex, but still, it was surprising that it just worked! The first prompt I used when I opened the extension folder in VS Code was “How to detect if opened folder is in a Git repo”
I returned to this task a few days later and told the agent that I would like to “Add this function directly into my workspace”.
Scrolling through the HTML revealed the paragraphs explaining that no server is available. Seems like the agent should handle this type of failure more gracefully.
The operation succeeded after I retried (I must have been logged out):
It was at this point that I decided to ask the agent to implement the whole thing using this prompt:
Add menu command called "Open in GitHub" when user right clicks on a file
I ran the extension and was pleased to see that it worked perfectly! It even included the current commit to give a stable link!
Creating the Extension in a New Repo
I used the agent in the first extension I had created but I wanted a separate repo to house my (potential) extension collection. I created my swesonga/vscode-extensions repo for this using the GitHub UI then cloned it and created a local folder for the extension.
I initialized the extension using the steps at the beginning of this post. This is the log from the initialization of the new extension. Notice that I selected no when asked to create a Git repo since I was already in the repo I had created in GitHub. I didn’t find any options for a TypeScript or JavaScript .gitignore file in the GitHub UI though so it didn’t have a .gitignore file. The first thing I did after the extension was created was git add . to add all the files to my repo. There were thousands of files added though, which led me to this question: node.js – Should the “node_modules” folder be included in the Git repository? – Stack Overflow. I decided to leave them in the repo for now.
I then used the same prompt to create the extension: Add a menu command called “Open in GitHub” when user right clicks on a file. The image below shows the various steps the agent took.
Claude Sonnet 4’s Summary
I’ve successfully added a “Open in GitHub” context menu command to your VS Code extension. Here’s what was implemented:
Changes Made:
Updated package.json:
Added a new command openingithub.openInGitHub with the title “Open in GitHub”
Added menu contributions for the explorer context menu that appears when right-clicking on files (not folders)
Enhanced extension.ts:
Added the command handler for the “Open in GitHub” functionality
Implemented getGitHubUrl() function that:
Gets the Git remote URL for the current workspace
Converts Git URLs (both HTTPS and SSH) to GitHub web URLs
Gets the current branch name
Constructs the full GitHub URL for the specific file
Added error handling for various scenarios
Made the command work both from explorer context menu and when no URI is provided
Features:
Right-click context menu: Users can right-click on any file in the Explorer and select “Open in GitHub”
Git integration: Automatically detects the GitHub repository URL from Git remotes
Branch awareness: Opens the file on the current Git branch
Error handling: Provides helpful error messages when GitHub URL cannot be determined
Cross-platform: Works with both SSH and HTTPS Git remotes
How it works:
When a user right-clicks on a file and selects “Open in GitHub”
The extension determines the workspace folder and Git repository
It executes Git commands to get the remote URL and current branch
Converts the Git URL to a GitHub web URL
Constructs the full GitHub file URL with the correct branch and file path
Opens the URL in the default browser
The extension will now show “Open in GitHub” in the context menu when right-clicking on files in the VS Code Explorer, and it will open the corresponding GitHub page for that file in the browser.
Committing Claude Sonnet 4’s Changes
Looking at the changed files in the Source Control view, I noticed that there were files in the out/ subdirectory that I didn’t notice in the filefrombranch extension. This was when I noticed that the filefrombranch extension had a .gitignore file. I therefore decided to remove the node_modules folder from the repo. I used git rm -r openingithub/node_modules then committed that change. Next, I just needed to rebase with fixup to get that change out of the first commit. I tried rebasing using HEAD~5 because there were now 5 commits in the repo.
$ git rebase -i HEAD~5
fatal: invalid upstream 'HEAD~5'
With the node_modules folder deleted, I needed to run npm install again before I could run the extension.
PS C:\repos\vscode-extensions\openingithub> npm install
npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
added 260 packages, and audited 261 packages in 2s
74 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Opening URLs at the Current Commit
The first iteration of this implementation (in the fileforbranch extension) had the handy feature of including the current commit in the URL that was opened. I like this feature so I added it using this prompt:
Add another context menu command to open the URL at the current commit
The final question was how to install this extension on another computer. The Publishing Extensions | Visual Studio Code Extension API page explains how to package the extension into a .vsix file and install it on another computer.
$ npm install -g @vscode/vsce
added 319 packages in 13s
88 packages are looking for funding
run `npm fund` for details
$ which vsce
/c/Users/saint/AppData/Roaming/npm/vsce
$ vsce package
Executing prepublish script 'npm run vscode:prepublish'...
> openingithub@0.0.1 vscode:prepublish
> npm run compile
> openingithub@0.0.1 compile
> tsc -p ./
ERROR It seems the README.md still contains template text. Make sure to edit the README.md file before you package or publish your extension.
I was surprised that I couldn’t publish the extension because the README file still contained template text. Neat check! After stripping it down, I was able to create a .VSIX file and install it on another computer.
Executing prepublish script 'npm run vscode:prepublish'...
> openingithub@0.0.1 vscode:prepublish
> npm run compile
> openingithub@0.0.1 compile
> tsc -p ./
WARNING A 'repository' field is missing from the 'package.json' manifest file.
Use --allow-missing-repository to bypass.
Do you want to continue? [y/N] y
WARNING LICENSE, LICENSE.md, or LICENSE.txt not found
Do you want to continue? [y/N] y
INFO Files included in the VSIX:
openingithub-0.0.1.vsix
├─ [Content_Types].xml
├─ extension.vsixmanifest
└─ extension/
├─ changelog.md [0.23 KB]
├─ initial-setup-output.txt [2.48 KB]
├─ package.json [1.54 KB]
├─ readme.md [0.25 KB]
└─ out/
├─ extension.js [8.22 KB]
└─ test/
└─ extension.test.js [1.94 KB]
DONE Packaged: C:\repos\vscode-extensions\openingithub\openingithub-0.0.1.vsix (8 files, 6.67 KB)
Key Takeaways
These AI Agents are fantastic! I went from never having written an extension to having a practical “Hello World” extension for opening files in their GitHub repos! Reading the agent’s code gave me some insight into the structure of an extension. Using the agent drastically cut down the amount of time it would have taken me to write this extension.
The test fails with the output below. The key line is Native frames: <unavailable>.
STDOUT:
Command line: [C:\java\forks\openjdk\jdk\build\windows-aarch64-server-slowdebug\images\jdk\bin\java.exe -cp C:\java\forks\openjdk\jdk\JTwork\classes\runtime\jni\nativeStack\TestNativeStack.d;C:\java\forks\openjdk\jdk\test\hotspot\jtreg\runtime\jni\nativeStack;C:\java\forks\openjdk\jdk\JTwork\classes\test\lib;C:\java\binaries\jtreg\jtreg-7.5.2\lib\javatest.jar;C:\java\binaries\jtreg\jtreg-7.5.2\lib\jtreg.jar;C:\java\binaries\jtreg\jtreg-7.5.2\lib\junit-platform-console-standalone-1.11.0.jar;C:\java\binaries\jtreg\jtreg-7.5.2\lib\testng-7.3.0.jar;C:\java\binaries\jtreg\jtreg-7.5.2\lib\guice-5.1.0.jar;C:\java\binaries\jtreg\jtreg-7.5.2\lib\jcommander-1.82.jar -Xmx512m -Xcheck:jni -Djava.library.path=C:\java\forks\openjdk\jdk\build\windows-aarch64-server-slowdebug\support\test\hotspot\jtreg\native\lib TestNativeStack$Main ]
[2025-09-04T15:54:18.312130500Z] Gathering output for process 22380
[2025-09-04T15:54:18.753851700Z] Waiting for completion for process 22380
[2025-09-04T15:54:18.756861300Z] Waiting for completion finished for process 22380
Output and diagnostic info for process 22380 was saved into 'pid-22380-output.log'
STDERR:
stdout: [Triggering a JNI warning
WARNING in native method: JNI call made without checking exceptions when required to from CallStaticObjectMethod
Native frames: <unavailable>
];
stderr: [WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::loadLibrary has been called by TestNativeStack in an unnamed module (file:/C:/java/forks/openjdk/jdk/JTwork/classes/runtime/jni/nativeStack/TestNativeStack.d/)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
]
exitValue = -2147483645
java.lang.RuntimeException: Expected to get exit value of [0], exit value is: [-2147483645]
at jdk.test.lib.process.OutputAnalyzer.shouldHaveExitValue(OutputAnalyzer.java:522)
at TestNativeStack.main(TestNativeStack.java:57)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at com.sun.javatest.regtest.agent.MainActionHelper$AgentVMRunnable.run(MainActionHelper.java:335)
at java.base/java.lang.Thread.run(Thread.java:1474)
I added a DebugBreak call to the line outputing that message to see how we got there:
KernelBase.dll!...DebugBreak() Line 2582 C++
jvm.dll!NativeStackPrinter::print_stack_from_frame(outputStream * st, frame fr, char * buf, int buf_size, bool print_source_info, int max_frames) Line 80 C++
jvm.dll!NativeStackPrinter::print_stack_from_frame(outputStream * st, char * buf, int buf_size, bool print_source_info, int max_frames) Line 104 C++
jvm.dll!NativeStackPrinter::print_stack(outputStream * st, char * buf, int buf_size, unsigned char * & lastpc, bool print_source_info, int max_frames) Line 40 C++
jvm.dll!JavaThread::print_jni_stack() Line 1775 C++
jvm.dll!check_pending_exception(JavaThread * thr) Line 192 C++
jvm.dll!functionEnter(JavaThread * thr) Line 218 C++
jvm.dll!checked_jni_CallStaticObjectMethod(JNIEnv_ * env, _jclass * clazz, _jmethodID * methodID, ...) Line 1321 C++
nativeStack.dll!generateWarning(const JNINativeInterface_ * * env) Line 66 C
nativeStack.dll!thread_start(void * unused) Line 92 C
ucrtbase.dll!00007ffb0970b028() Unknown
kernel32.dll!00007ffb0bbd8740() Unknown
ntdll.dll!RtlUserThreadStart(long(*)(void *) StartAddress, void * Argument) Line 1184 C
Triggering a JNI warning
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::loadLibrary has been called by TestNativeStack in an unnamed module (file:/C:/java/forks/openjdk/jdk/JTwork/classes/runtime/jni/nativeStack/TestNativeStack.d/)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
Native thread is running and attaching as daemon ...
About to trigger JNI Warning
WARNING in native method: JNI call made without checking exceptions when required to from CallStaticObjectMethod
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V [jvm.dll+0x10e8aa8] os::win32::platform_print_native_stack+0x58 (os_windows_aarch64.cpp:143)
V [jvm.dll+0x10598ac] os::platform_print_native_stack+0x34 (os_windows_aarch64.inline.hpp:38)
V [jvm.dll+0x1059588] NativeStackPrinter::print_stack+0x48 (nativeStackPrinter.cpp:35)
V [jvm.dll+0xba74a0] JavaThread::print_jni_stack+0x120 (javaThread.cpp:1775)
V [jvm.dll+0xcb0e84] check_pending_exception+0x84 (jniCheck.cpp:192)
V [jvm.dll+0xcb0f24] functionEnter+0x4c (jniCheck.cpp:218)
V [jvm.dll+0xcbbb70] checked_jni_CallStaticObjectMethod+0xf0 (jniCheck.cpp:1321)
C [nativeStack.dll+0x1264] generateWarning+0x13c (libnativeStack.c:66)
C [nativeStack.dll+0x1364] thread_start+0xa4 (libnativeStack.c:92)
C [ucrtbase.dll+0x2b028] (no source info available)
C [KERNEL32.DLL+0x8740] (no source info available)
C [ntdll.dll+0xd47a4] (no source info available)
Native thread terminating
C:\java\forks\openjdk\jdk\build\windows-aarch64-server-slowdebug\images\jdk\bin\java.exe (process 30308) exited with code 0 (0x0).
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .
Last month, I investigated OpenJDK gtest failures on Windows. The error message was that the gtests Caught std::exception-derived exception escaping the death test statement. I tracked the commit responsible for the failures to 8343756: CAN_SHOW_REGISTERS_ON_ASSERT for Windows · openjdk/jdk@0054bbe.
// Asserts that a given `statement` causes the program to exit, with an
// integer exit status that satisfies `predicate`, and emitting error output
// that matches `matcher`.
#define ASSERT_EXIT(statement, predicate, matcher) \
GTEST_DEATH_TEST_(statement, predicate, matcher, GTEST_FATAL_FAILURE_)
I was trying to ensure my understanding of the exit code being an exact match is correct. The line EXPECT_EXIT(_exit(1), testing::ExitedWithCode(1), ""); from googletest/googletest/test/googletest-death-test-test.cc at v1.14.0 · google/googletest supports this hypothesis. The EXPECT_EXIT macro comment (below) left me wondering how ASSERT_EXIT does not continue on to successive tests. The difference between these two macros is in the final parameter, which is GTEST_NONFATAL_FAILURE_ for the EXPECT_EXIT macro.
// Like `ASSERT_EXIT`, but continues on to successive tests in the
// test suite, if any:
#define EXPECT_EXIT(statement, predicate, matcher) \
GTEST_DEATH_TEST_(statement, predicate, matcher, GTEST_NONFATAL_FAILURE_)
creates a child process with the same executable as the current process to run the death test. The child process is given the –gtest_filter and –gtest_internal_run_death_test flags such that it knows to run the current death test only.
The question now became, why were we catching this std::exception? I asked copilot: how does a windows access violation turn into a std::exception? Part of its answer mentioned the /EHsc compiler flag so I decided to examine the flags used to compile the JVM binaries. I searched for the regex out:[^\s]+jvm.dll in the build logs and found this jvm.dll linker command. Note that 2 separate jvm.dll files get built, one for the product and another for the gtests. The /IMPLIB (Name Import Library) | Microsoft Learn flag was present, but didn’t look relevant.
checking for apk... [not found]
checking for pandoc... [not found]
/cygdrive/d/java/forks/openjdk/jdk/build/.configure-support/generated-configure.sh: line 64028: syntax error: unexpected end of file
configure exiting with result code 2
That file appeared to be truncated??? VSCode was doing something related to building the Java projects in the repo. It is possible that something in VSCode could have interrupted this but I just removed the build folder then reexamined the change.
My include path didn’t appear in the include paths for gtestLauncher.obj! I searched the repo for googlemock and the only place that path could be coming from was CompileGtest.gmk. However, I then noticed that the gtest launcher has its own configuration section. Sheesh. Here is the diff that I used to definitively see how these includes work:
gtestLauncher.exe was now being compiled with -EHsc but the gtests still failed. Since jvm.dll is compiled without -EHsc, I added it to see if the test behavior would change. I started by searching for libjvm in the codebase. This is the additional change I made:
At this point, I looked at the exception handler and it looked like what was happening was that returning EXCEPTION_CONTINUE_EXECUTION let the SEH handler in the gtests continue instead of the code down the report_vm_error path! I decided to create my own handler but needed to look up the syntax. I used Structured Exception Handling (C/C++) | Microsoft Learn.
diff --git a/src/hotspot/share/utilities/debug.hpp b/src/hotspot/share/utilities/debug.hpp
index 12724153659..e40c16c1c59 100644
--- a/src/hotspot/share/utilities/debug.hpp
+++ b/src/hotspot/share/utilities/debug.hpp
@@ -39,7 +39,21 @@ class oopDesc;
#define CAN_SHOW_REGISTERS_ON_ASSERT
extern char* g_assert_poison;
extern const char* g_assert_poison_read_only;
+#if (defined(_WINDOWS))
+// We use structured exception handling when writing to the poison variable.
+// This allows us to continue execution and perform error reporting instead of
+// bailing out to other SEH handlers such as those in the googletest code.
+#include <excpt.h>
+#define TOUCH_ASSERT_POISON \
+do { \
+ __try { \
+ (*g_assert_poison) = 'X'; \
+ } __except (EXCEPTION_CONTINUE_EXECUTION) { \
+ } \
+} while (0)
+#else
#define TOUCH_ASSERT_POISON (*g_assert_poison) = 'X';
+#endif // _WINDOWS
void initialize_assert_poison();
void disarm_assert_poison();
bool handle_assert_poison_fault(const void* ucVoid);
...\jdk\src\hotspot\share\utilities/growableArray.hpp(81): error C2712: Cannot use __try in functions that require object unwinding
...\jdk\src\hotspot\share\classfile/vmClassID.hpp(41): error C3615: constexpr function 'EnumeratorRangeImpl::end_value' cannot result in a constant expression
...\jdk\src\hotspot\share\utilities/enumIterator.hpp(97): note: failure was caused by a statement or an expression that is not valid in a constexpr context
...\jdk\src\hotspot\share\utilities/unsigned5.hpp(190): error C3615: constexpr function 'UNSIGNED5::max_encoded_in_length' cannot result in a constant expression
...\jdk\src\hotspot\share\utilities/unsigned5.hpp(191): note: failure was caused by a statement or an expression that is not valid in a constexpr context
...\jdk\src\hotspot\cpu\x86\register_x86.hpp(61): error C3615: constexpr function 'Register::RegisterImpl::encoding' cannot result in a constant expression
...\jdk\src\hotspot\cpu\x86\register_x86.hpp(61): note: failure was caused by a statement or an expression that is not valid in a constexpr context
...\jdk\src\hotspot\cpu\x86\register_x86.hpp(233): error C3615: constexpr function 'XMMRegister::XMMRegisterImpl::encoding' cannot result in a constant expression
...\jdk\src\hotspot\cpu\x86\register_x86.hpp(233): note: failure was caused by a statement or an expression that is not valid in a constexpr context
...\jdk\src\hotspot\share\runtime/park.hpp(131): error C2712: Cannot use __try in functions that require object unwinding
...\jdk\src\hotspot\share\runtime/mutexLocker.hpp(235): error C2712: Cannot use __try in functions that require object unwinding
...\jdk\src\hotspot\share\runtime/mutexLocker.hpp(240): error C2712: Cannot use __try in functions that require object unwinding
...\jdk\src\hotspot\share\runtime/mutexLocker.hpp(250): error C2712: Cannot use __try in functions that require object unwinding
...\jdk\src\hotspot\share\runtime/mutexLocker.hpp(255): error C2712: Cannot use __try in functions that require object unwinding
At this point, I realized that I needed to disable SEH at the gtest level. I turned off GTEST_HAS_SEH with this change and finally got the gtests to pass!
What was not sure of was whether the JVM reporting code was running (vs the JVM just exiting) and whether there was a narrower way to pass the GTEST_HAS_SEH define – I noticed it in thousands of lines in the compilation log, which might also explain why I was getting error C2712: Cannot use __try in functions that require object unwinding in many more places than I expected when I added the -EHsc flag when compiling jvm.obj. Therefore, it was logical to try to find the minimal diff that would fix the gtests. Here’s one I tried:
This manual approach of finding the minimal change needed was tedious so I decided to add my own defines to see which portions of the gmk files are used and for which compile/link commands:
For location 2, there were 214 lines matching the regex DMYTEST_LOCATION2.+.cpp and 213 lines matching the regex DMYTEST_LOCATION2.+/test/hotspot/gtest/.+.cpp. The location 2 define was therefore correctly scoped to the gtests only. These 213 lines compiled files like test_blocktree.cpp and test_vmerror.cpp. The line that was different between the 2 regexes was the one compiling build/windows-x86_64-server-slowdebug/hotspot/variant-server/libjvm/gtest/objs/BUILD_GTEST_LIBJVM_pch.cpp. Location 3 was only used for compiling test/hotspot/gtest/gtestLauncher.cpp. The challenging case was location 0, which seemed to appear for way more files than it should. Was it really necessary? No it wasn’t! That made life much easier for me.
Inspecting gTest Code Coverage
In the course of this investigation, I considered using time travel debugging to see which code paths were executed. An alternative was to see whether the exception filter code was covered at the end of the gtest execution! The path to the code coverage tool is C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\Microsoft\CodeCoverage.Console\Microsoft.CodeCoverage.Console.exe – it should be in the path by default in the Developer Command Prompt. I kicked it off with these commands:
Search for “exceptionfilter” in the Code Coverage Results pane to view the code coverage for the exception filter.
Verifying Execution Path
The first time I paused execution of the gtests in the debugger, stopped in jdk/src/hotspot/share/utilities/bitMap.cpp. I set a breakpoint there. I liked this location because I could move execution into the failure path (in the assembly view). This was how I saw the gtest structured exception handler kicking in without the JVM’s failure reporting code executing. With the tests now passing, I found the write to the poison location just going through without interruption. Did this mean the test was broken? Or did it mean that the exception filter ran and successfully said to continue execution? I think it has to be the latter but I’ll need time travel debugging to verify this. In the meantime, I sought to at least ensure there were multiple test processes involved.
Verifying Multiple Processes are Started
I started Process Monitor and added a filter for path containing “slowdebug”. Notice tons of PIDs for gtestlauncher.exe in the process monitor screenshot below (as expected).
I could successfully execute the error handling path by manually moving the program counter (RIP) after skipping into the failure path of BitMap::verify_range. Why didn’t the PID change in the debugger? Oh wait, was I was still stepping thereby causing recursion? This callstack did not support that hypothesis. Looks like it was just error reporting continuing to execute.
> jvm.dll!BitMap::verify_range(unsigned __int64 beg, unsigned __int64 end) Line 212 C++
jvm.dll!BitMap::clear_range(unsigned __int64 beg, unsigned __int64 end) Line 280 C++
jvm.dll!JVMFlag::printFlags(outputStream * out, bool withComments, bool printRanges, bool skipDefaults) Line 706 C++
jvm.dll!VMError::report(outputStream * st, bool _verbose) Line 1260 C++
jvm.dll!VMError::report_and_die(int id, const char * message, const char * detail_fmt, char * detail_args, Thread * thread, unsigned char * pc, const void * siginfo, const void * context, const char * filename, int lineno, unsigned __int64 size) Line 1847 C++
jvm.dll!report_vm_error(const char * file, int line, const char * error_msg, const char * detail_fmt, ...) Line 195 C++
jvm.dll!CompressedKlassPointers::check_init<int>(int var) Line 154 C++
jvm.dll!CompressedKlassPointers::shift() Line 218 C++
jvm.dll!CompressedKlassPointers::print_mode(outputStream * st) Line 301 C++
jvm.dll!VMError::report(outputStream * st, bool _verbose) Line 1196 C++
jvm.dll!VMError::report_and_die(int id, const char * message, const char * detail_fmt, char * detail_args, Thread * thread, unsigned char * pc, const void * siginfo, const void * context, const char * filename, int lineno, unsigned __int64 size) Line 1847 C++
jvm.dll!report_vm_error(const char * file, int line, const char * error_msg, const char * detail_fmt, ...) Line 195 C++
jvm.dll!BitMap::verify_limit(unsigned __int64 bit) Line 206 C++
jvm.dll!BitMap::to_words_align_down(unsigned __int64 bit) Line 94 C++
jvm.dll!BitMap::word_addr(unsigned __int64 bit) Line 144 C++
jvm.dll!BitMap::set_bit(unsigned __int64 bit) Line 37 C++
jvm.dll!JfrEventVerifier::set_field_bit(unsigned __int64 field_idx) Line 41 C++
jvm.dll!JfrEvent<EventTenuringDistribution>::set_field_bit(unsigned __int64 field_idx) Line 267 C++
jvm.dll!EventObjectAllocationOutsideTLAB::set_objectClass(const Klass * new_value) Line 7304 C++
jvm.dll!trace_flag_changed<bool,EventBooleanFlagChanged>(JVMFlag * flag, const bool old_value, const bool new_value, const JVMFlagOrigin origin) Line 39 C++
jvm.dll!TypedFlagAccessImpl<bool,EventBooleanFlagChanged>::check_constraint_and_set(JVMFlag * flag, void * value_addr, JVMFlagOrigin origin, bool verbose) Line 78 C++
jvm.dll!FlagAccessImpl_bool::set_impl(JVMFlag * flag, void * value_addr, JVMFlagOrigin origin) Line 98 C++
jvm.dll!FlagAccessImpl::set(JVMFlag * flag, void * value, JVMFlagOrigin origin) Line 49 C++
jvm.dll!JVMFlagAccess::set_impl(JVMFlag * flag, void * value, JVMFlagOrigin origin) Line 307 C++
jvm.dll!JVMFlagAccess::set_or_assert(JVMFlagsEnum flag_enum, int type_enum, void * value, JVMFlagOrigin origin) Line 353 C++
jvm.dll!JVMFlagAccess::set<bool,0>(JVMFlagsEnum flag_enum, bool value, JVMFlagOrigin origin) Line 101 C++
jvm.dll!Flag_UseLargePagesIndividualAllocation_set(bool value, JVMFlagOrigin origin) Line 69 C++
jvm.dll!os::init() Line 4436 C++
jvm.dll!Threads::create_vm(JavaVMInitArgs * args, bool * canTryAgain) Line 463 C++
jvm.dll!JNI_CreateJavaVM_inner(JavaVM_ * * vm, void * * penv, void * args) Line 3589 C++
jvm.dll!JNI_CreateJavaVM(JavaVM_ * * vm, void * * penv, void * args) Line 3680 C++
jvm.dll!init_jvm(int argc, char * * argv, bool disable_error_handling, JavaVM_ * * jvm_ptr) Line 94 C++
jvm.dll!JVMInitializerListener::OnTestStart(const testing::TestInfo & test_info) Line 124 C++
jvm.dll!testing::internal::TestEventRepeater::OnTestStart(const testing::TestInfo & parameter) Line 3858 C++
jvm.dll!testing::TestInfo::Run() Line 2821 C++
jvm.dll!testing::TestSuite::Run() Line 3015 C++
jvm.dll!testing::internal::UnitTestImpl::RunAllTests() Line 5920 C++
jvm.dll!testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl,bool>(testing::internal::UnitTestImpl * object, bool(testing::internal::UnitTestImpl::*)() method, const char * location) Line 2614 C++
jvm.dll!testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl,bool>(testing::internal::UnitTestImpl * object, bool(testing::internal::UnitTestImpl::*)() method, const char * location) Line 2648 C++
jvm.dll!testing::UnitTest::Run() Line 5484 C++
jvm.dll!RUN_ALL_TESTS() Line 2317 C++
jvm.dll!runUnitTestsInner(int argc, char * * argv) Line 290 C++
jvm.dll!runUnitTests(int argc, char * * argv) Line 371 C++
gtestLauncher.exe!main(int argc, char * * argv) Line 40 C++
[Inline Frame] gtestLauncher.exe!invoke_main() Line 78 C++
gtestLauncher.exe!__scrt_common_main_seh() Line 288 C++
kernel32.dll!00007ffdcbdce8d7() Unknown
ntdll.dll!00007ffdcc97c34c() Unknown
One advantage of the stack above is that it showed how the os::init code gets executed (which I was curious about when wondering whether the exception filter was being set up). Disabling the breakpoint just before skipping into the failure path and resuming execution now led to the JVM dying:
[==========] Running 1197 tests from 205 test suites.
[----------] Global test environment set-up.
[----------] 3 tests from AltHashingTest
[ RUN ] AltHashingTest.halfsiphash_test_ByteArray
[ OK ] AltHashingTest.halfsiphash_test_ByteArray (0 ms)
[ RUN ] AltHashingTest.halfsiphash_test_CharArray
[ OK ] AltHashingTest.halfsiphash_test_CharArray (0 ms)
[ RUN ] AltHashingTest.halfsiphash_test_FromReference
[ OK ] AltHashingTest.halfsiphash_test_FromReference (0 ms)
[----------] 3 tests from AltHashingTest (2 ms total)
[----------] 1 test from ThreadsListHandle
[ RUN ] ThreadsListHandle.sanity_vm
#
# A fatal error has been detected by the Java Runtime Environment:
#
# Internal Error (d:\java\forks\openjdk\jdk\src\hotspot\share\utilities\bitMap.cpp:208), pid=39132, tid=109872
# assert(bit <= _size) failed: BitMap limit out of bounds: 0 > 64
#
# JRE version: ((uninitialized)) (slowdebug build )
# Java VM: OpenJDK 64-Bit Server VM (slowdebug 26-internal-adhoc.saint.jdk, mixed mode, sharing, tiered, compressed class ptrs, unknown gc, windows-amd64)
# Core dump will be written. Default location: D:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\images\test\hotspot\gtest\server\hs_err_pid39132.mdmp
#
# An error report file with more information is saved as:
# D:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\images\test\hotspot\gtest\server\hs_err_pid39132.log
#
#
D:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\images\test\hotspot\gtest\server\gtestLauncher.exe (process 39132) exited with code 1 (0x1).
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .
c:\progra~1\mib055~1\2022\enterprise\vc\tools\msvc\14.44.35207\include\__msvc_ostream.hpp(781): error C2220: the following warning is treated as an error
c:\progra~1\mib055~1\2022\enterprise\vc\tools\msvc\14.44.35207\include\__msvc_ostream.hpp(781): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc
c:\progra~1\mib055~1\2022\enterprise\vc\tools\msvc\14.44.35207\include\__msvc_ostream.hpp(781): note: the template instantiation context (the oldest one first) is
c:\repos\googletest\googletest\include\gtest/gtest-message.h(118): note: see reference to function template instantiation 'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const char *)' being compiled
I was reviewing jtreg test failures in some GitHub actions last week. Since I was only interested in the failures, I decided to write a script to extract the failure details from the log files I had downloaded from GitHub. This looked like another task for the VS Code agent so I wrote the algorithm for extracting the failure details into individual text files, which would be easier to review. Below is the prompt I used.
write a python script that processes all the text files in a user-specified folder. For each text file:
- split it into sections using "--------------------------------------------------" as a separator
- generate a filename for the section from the first line after the separator. To do so, use the text after "TEST: " and replace /, #, and . with underscores and append the ".txt" extension.
- if the section contains the string "test jdk:" then write the content of the entire section to a file with the generated name.
I thought I would need to do at least some debugging of the script but I did not! The script worked flawlessly. I didn’t even need to execute it myself because the flow of using the agent included running the script on my raw folder and then generating a README file! See the script and the README at Add scripts generated by Claude Sonnet 4 VS Code agent · swesonga/scratchpad@f5e8057. The agent was using the Claude Sonnet 4 model.
As part of the jdk11u release process, I needed to run some shell tests on my Windows desktop to determine whether they failed due to a product issue or an environment issue. I defaulted to using my Git Bash environment instead of Cygwin. This post shares some errors I ran into as a result of the different shell environment. The key takeaway was to run such tests in Cygwin whenever I encountered path errors.
I confirmed that rmic.exe exists in the bin directory of the jdk.
$ ls -1 /d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/rmic*
/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/rmic.exe*
Changing the test to directly refer to rmic.exe instead of rmic (as shown in the diff below) resulted in the test passing on my machine. I concluded that this specific issue must therefore be a test bug (i.e. the test should avoid this issue on Windows). See Shell Tests in jtreg for possible ways to fix this. This is one reason why shell scripts are being discouraged for OpenJDK testing.
STDOUT:
Warning: generation and use of skeletons and static stubs for JRMP
is deprecated. Skeletons are unnecessary, and static stubs have
been superseded by dynamically generated stubs. Users are
encouraged to migrate away from using rmic to generate skeletons and static
stubs. See the documentation for java.rmi.server.UnicastRemoteObject.
This warning is output by the sun.rmi.rmic.Main.compile method. The arguments to rmic.exe are parsed by Main.parseArgs. The -v1.2 argument specifies the non-default generator to use. My local machine must be using a different default generator (or so I thought).
So what does the failing line do? I asked Copilot: what does this command do in a bash script? . ${TESTSRC-.}/common.sh
. (dot command) This is shorthand for source, which executes the contents of a file in the current shell environment rather than in a subshell. This means any variables, functions, or changes made by the script will persist in the current shell.
${TESTSRC-.} This is a parameter expansion. It means:
Use the value of the TESTSRC environment variable if it is set.
If TESTSRC is not set, use . (the current directory) as the default.
/common.sh This appends /common.sh to the path determined above.
This error was a Windows path issue again. Notepad++ could open D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/common.sh so the path itself was fine. I used this workaround:
diff --git a/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh b/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
index 6a3d4e76ff..94001967b0 100644
--- a/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
+++ b/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
@@ -30,10 +30,11 @@
# @run shell jrunscript-eTest.sh
# @summary Test that output of 'jrunscript -e' matches the dash-e.out file
-. ${TESTSRC-.}/common.sh
+comn=`/mnt/c/software/msys64/usr/bin/cygpath.exe ${TESTSRC-.}/common.sh`
+. "/mnt$comn"
setup
-${JAVA} ${TESTVMOPTS} ${TESTJAVAOPTS} -cp ${TESTCLASSES} CheckEngine
+${JAVA}.exe ${TESTVMOPTS} ${TESTJAVAOPTS} -cp ${TESTCLASSES} CheckEngine
if [ $? -eq 2 ]; then
echo "No js engine found and engine not required; test vacuously passes."
exit 0
This got me to the actual test error:
STDOUT:
Output of jrunscript -e differ from expected output. Failed.
STDERR:
Warning: Nashorn engine is planned to be removed from a future JDK release
diff: D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/dash-e.out: No such file or directory
I couldn’t see which command generated the output though, so I added set -ex to the top of the script (like run.sh in the previous test). This was the resulting output:
Aha! Notice the root cause of the filename issues: OS=Linux! This also confirmed that I was using the same diff command. I didn’t get the error message at jdk11u/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh at jdk-11.0.28+5 · openjdk/jdk11u because because of set -ex (the e means exit immediately if any command fails, which does not match the behavior this test requires). After removing the e, I got this output:
$ ls -1 `cygpath D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/dash-e.out`
/d/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/dash-e.out
I patched the script as shown in the next diff:
diff --git a/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh b/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
index 6a3d4e76ff..4c7130857e 100644
--- a/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
+++ b/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
@@ -30,10 +30,13 @@
# @run shell jrunscript-eTest.sh
# @summary Test that output of 'jrunscript -e' matches the dash-e.out file
-. ${TESTSRC-.}/common.sh
+set -x
+
+comn=`/mnt/c/software/msys64/usr/bin/cygpath.exe ${TESTSRC-.}/common.sh`
+. "/mnt$comn"
setup
-${JAVA} ${TESTVMOPTS} ${TESTJAVAOPTS} -cp ${TESTCLASSES} CheckEngine
+${JAVA}.exe ${TESTVMOPTS} ${TESTJAVAOPTS} -cp ${TESTCLASSES} CheckEngine
if [ $? -eq 2 ]; then
echo "No js engine found and engine not required; test vacuously passes."
exit 0
@@ -44,7 +47,9 @@ fi
rm -f jrunscript-eTest.out 2>/dev/null
${JRUNSCRIPT} -J-Dnashorn.args.prepend=--no-deprecation-warning -J-Djava.awt.headless=true -l nashorn -e "println('hello')" > jrunscript-eTest.out 2>&1
-$golden_diff jrunscript-eTest.out ${TESTSRC}/dash-e.out
+diffarg=`/mnt/c/software/msys64/usr/bin/cygpath.exe ${TESTSRC}/dash-e.out`
+
+$golden_diff jrunscript-eTest.out "/mnt$diffarg"
if [ $? != 0 ]
then
echo "Output of jrunscript -e differ from expected output. Failed."
Avoiding Path Issues
This madness (in the diff above) that made me realize that I needed to fix the path issues and that perhaps Cygwin was the better environment for these tests. Sure enough, the test passed the first time I executed it in Cygwin:
Executing: D:/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/java -Xmx512m -jar C:/java/binaries/jtreg/jtreg-7.4+1/lib/jtreg.jar -agentvm -ignore:quiet -automatic -xml -vmoption:-Xmx512m -timeoutFactor:4 -concurrency:1 -testjdk:D:/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6 -verbose:fail,error,summary -nativepath:D:/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6-test-image/hotspot/jtreg/native test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
XML output to D:\java\ms\dups\openjdk-jdk11u\JTwork
Passed: sun/tools/jrunscript/jrunscript-eTest.sh
Test results: passed: 1
Moral of the story: run OpenJDK shell tests on Windows in the Cygwin environment! At this point, the only outstanding question is about the difference between the local machine and the test environment the build was executed in. I thought that the fact that I didn’t get the rmic.jrmp.stubs.deprecated warning meant that a different default generator was used on my machine. However, I realized that the stdout messages were not being displayed! That warning was present in the output but I needed to open JTwork\sun\rmi\rmic\defaultStubVersion\run.jtr to see it!
----------System.out:(11/743)----------
Warning: generation and use of skeletons and static stubs for JRMP
is deprecated. Skeletons are unnecessary, and static stubs have
been superseded by dynamically generated stubs. Users are
encouraged to migrate away from using rmic to generate skeletons and static
stubs. See the documentation for java.rmi.server.UnicastRemoteObject.
Warning: generation and use of skeletons and static stubs for JRMP
is deprecated. Skeletons are unnecessary, and static stubs have
been superseded by dynamically generated stubs. Users are
encouraged to migrate away from using rmic to generate skeletons and static
stubs. See the documentation for java.rmi.server.UnicastRemoteObject.
TEST PASSED: default output identical to -v1.2 output
----------System.err:(8/706)----------
+ defdir=./default_output
+ refdir=./reference_output
+ rm -rf ./default_output ./reference_output
+ mkdir ./default_output ./reference_output
+ D:/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/rmic -classpath D:/java/ms/openjdk-jdk11u/JTwork/classes/sun/rmi/rmic/defaultStubVersion/run.d -keep -nowrite -d ./default_output G1Impl
+ D:/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/rmic -classpath D:/java/ms/openjdk-jdk11u/JTwork/classes/sun/rmi/rmic/defaultStubVersion/run.d -keep -nowrite -d ./reference_output -v1.2 G1Impl
+ diff -r ./default_output ./reference_output
+ echo 'TEST PASSED: default output identical to -v1.2 output'
This was sufficient for me to confirm that the build behaves as expected in this scenario.
time /cygdrive/c/repos/scratchpad/scripts/java/cygwin/build-jdk.sh windows x86_64 slowdebug zero
The build will fail with Error: Failed to load D:\java\forks\dups11\openjdk\jdk\build\windows-x86_64-zero-slowdebug\jdk\bin\zero\jvm.dll. However, the build is still usable (all the binaries and symbols should be present). I am saving that investigation for another day. For now, verify that the build works by running this command:
$ build/windows-x86_64-zero-slowdebug/jdk/bin/java.exe -version
java version "25-internal" 2025-09-16
Java Runtime Environment (slowdebug build 25-internal-adhoc.USERsaint.jdk)
Java HotSpot 64-Bit Zero VM (slowdebug build 25-internal-adhoc.USERsaint.jdk, interpreted mode)
checking for --enable-hsdis-bundling... disabled, default
checking what hsdis backend to use... 'none', hsdis will not be built
checking if hsdis should be bundled... no
checking for --enable-libffi-bundling... disabled, default
checking for LIBFFI... checking for ffi.h... no
configure: error: Could not find libffi!
configure exiting with result code 1
I took at look at the source of the error message in jdk/make/autoconf/lib-ffi.m4 and realized that I need to have the repo.
git clone https://github.com/libffi/libffi.git
cd libffi
git checkout v3.4.8
I then added the --with-libffi argument to the configure script.
This didn’t address the failure but closer inspection of the .m4 led me to realize that I need to build libffi. This was the genesis of the post on Building libffi for Windows x64 with Visual C++. Once I had built libffi and created the .lib file in the expected location of its repo, the configure script succeeded. I started the build with this command:
time /cygdrive/c/repos/scratchpad/scripts/java/cygwin/build-jdk.sh windows x86_64 slowdebug zero
The build failed!
* For target hotspot_variant-zero_libjvm_gtest_objs_BUILD_GTEST_LIBJVM_pch.obj:
BUILD_GTEST_LIBJVM_pch.cpp
d:\java\forks\dups11\openjdk\jdk\src\hotspot\share\runtime/globals.hpp(35): fatal error C1083: Cannot open include file: 'globals_windows_zero.hpp': No such file or directory
... (rest of output omitted)
* For target support_gensrc_java.base__SocketOptionRegistry.java:
/*
* Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved.
*
...
* 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
... (rest of output omitted)
* All command lines available in /cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/make-support/failure-logs.
=== End of repeated output ===
There was a .log file and a .cmd file in the failure-logs folder. build\windows-x86_64-zero-slowdebug\make-support\failure-logs\support_gensrc_java.base__SocketOptionRegistry.java.log contained this:
/*
* Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved.
*
* 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*
*/
/cygdrive/d/java/forks/dups11/openjdk/jdk/make/scripts/fixpath.sh: line 486: c:\progra~1\mib055~1\2022\enterp~1\vc\tools\msvc\1444~1.352\bin\hostx64\x64\cl.exe: command not found
There were several warnings (e.g. warning C4267: ‘initializing’: conversion from ‘size_t’ to ‘int’, possible loss of data) and a build error that I fixed in Fix MSVC warnings and build errors · swesonga/jdk@bd519ea. I later realized that I could have used the --disable-warnings-as-errors configure argument but it was good for me to be aware of which issues I could run into later when using the zero interpreter. At this point, the build failed with this message:
* For target buildtools_create_symbols_javac__the.COMPILE_CREATE_SYMBOLS_batch:
Error: Failed to load D:\java\forks\dups11\openjdk\jdk\build\windows-x86_64-zero-slowdebug\jdk\bin\zero\jvm.dll
* All command lines available in /cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/make-support/failure-logs.
That error in the only line in the build\windows-x86_64-zero-slowdebug\make-support\failure-logs\buildtools_create_symbols_javac__the.COMPILE_CREATE_SYMBOLS_batch.log file. This was the .cmdline file (next to the .log file):
That meant that I didn’t need to look any further into this error (at least not right away) so I moved on to building for the ARM64 platform. I initially tried cross compiling but this didn’t work. This is the configure command I tried for cross compiling.
The linker failed with a series of 19 unresolved symbols and a warning that the library machine type ‘ARM64’ conflicts with target machine type ‘x64’. Notice that the === Output from failing command(s) repeated here === section did not contain the linker warning LNK4272 since it omits the rest of the input after showing the first 14 lines. Scanning the build log is important in such cases where some of the output is omitted at the end.
...
fallbackLinker.o : error LNK2019: unresolved external symbol __imp_ffi_type_double referenced in function Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1double
fallbackLinker.o : error LNK2019: unresolved external symbol __imp_ffi_type_pointer referenced in function Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1pointer
d:\repos\dups\libffi\lib\libffi.lib : warning LNK4272: library machine type 'ARM64' conflicts with target machine type 'x64'
d:\java\forks\dups11\openjdk\jdk\build\windows-aarch64-zero-slowdebug\buildjdk\support\modules_libs\java.base\fallbackLinker.dll : fatal error LNK1120: 19 unresolved externals
At this point, I switched to my ARM64 machine (to avoid these mismatch issues) and copied the the libffi files onto it (after cloning the libffi repo). The --openjdk-target argument is no longer necessary for a native build on Windows ARM64. The build failed since the CONTEXT (x86 64-bit) struct (defined in C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um\winnt.h is a platform specific struct. This fix was needed for ARM64: Fix build errors on Windows AArch64 · swesonga/jdk@d3ec3c7. OpenJDK now built successfully, well, until the same error about being unable to load jvm.dll.
I moved on to testing the build but this time it didn’t work! I added some debug output in Display GetLastError() on failure to load DLL · swesonga/jdk@ce7143e. GetLastError was 126, i.e. “the specified module could not be found” as per the System Error Codes (0-499) (WinError.h). I verified that the path to jvm.dll was valid then used Process Monitor to record events on the system while running java.exe. Turns out I needed to copy libffi-8.dll into the directory containing jvm.dll.
The Microsoft.CodeCoverage.Console tool can be used to collect code coverage for C++ code. To use it, open the “Developer Command Prompt” – it should be in the path by default. Once the OpenJDK build completes, instrument the DLLs and EXEs as follows:
Run start output.coverage to open the code coverage file. You can use the search filter to narrow down the coverage results, e.g. by searching for “interpreter”.
Instrumenting Fails on Windows AArch64
There are warnings when instrumenting some of the DLLs on the AArch64 platform. The warning below is from running Microsoft.CodeCoverage.Console instrument java.dll in the ARM64 Native Tools Command Prompt:
My list from last year had these 3 files, which are no longer present in the build so keep in mind that you need to ensure your list is current and exhaustive:
$ alias ls
alias ls='ls -F --color=auto --show-control-chars'
$ which ls
/usr/bin/ls
$ /usr/bin/ls -1 *.exe
jabswitch.exe
jaccessinspector.exe
jaccesswalker.exe
...
I started by downloading Node.js. The Node.js — Run JavaScript Everywhere page has a link to the JSConf | LF Events page. I have never attended this conference, so it piqued my interest (I made a note to learn more about it later). Node.js informed me that it would install its prerequisites.
====================================================
Tools for Node.js Native Modules Installation Script
====================================================
This script will install Python and the Visual Studio Build Tools, necessary
to compile Node.js native modules. Note that Chocolatey and required Windows
updates will also be installed.
...
Press any key to continue . . .
The Chocolatey script failed to install since I already have a version of Chocolatey installed. Python was upgraded from 3.11 to 3.13.
WARNING: 'choco' was found at 'C:\ProgramData\chocolatey\bin\choco.exe'.
WARNING: An existing Chocolatey installation was detected. Installation will not continue. This script will not
overwrite existing installations.
If there is no Chocolatey installation at 'C:\ProgramData\chocolatey', delete the folder and attempt the installation
again.
Please use choco upgrade chocolatey to handle upgrades of Chocolatey itself.
If the existing installation is not functional or a prior installation did not complete, follow these steps:
- Backup the files at the path listed above so you can restore your previous installation if needed.
- Remove the existing installation manually.
- Rerun this installation script.
- Reinstall any packages previously installed, if needed (refer to the lib folder in the backup).
Once installation is completed, the backup folder is no longer needed and can be deleted.
Chocolatey v1.2.1
Upgrading the following packages:
python;visualstudio2019-workload-vctools
By upgrading, you accept licenses for the packages.
You have python v3.11.0 installed. Version 3.13.3 is available based on your source(s).
Progress: Downloading python3 3.13.3... 100%
Progress: Downloading python313 3.13.3... 100%
Progress: Downloading python 3.13.3... 100%
python313 v3.13.3 [Approved]
python313 package files upgrade completed. Performing other installation steps.
Installing 64-bit python313...
python313 has been installed.
Added C:\ProgramData\chocolatey\bin\python3.13.exe shim pointed to 'c:\python313\python.exe'.
Python installed to: 'C:\Python313'
WARNING: Environment variable PYTHONHOME points to different version: C:\Python311\
Restricting write permissions to Administrators
python313 can be automatically uninstalled.
Environment Vars (like PATH) have changed. Close/reopen your shell to
see the changes (or in powershell/cmd.exe just type `refreshenv`).
The upgrade of python313 was successful.
Software installed as 'exe', install location is likely default.
python3 v3.13.3 [Approved]
python3 package files upgrade completed. Performing other installation steps.
The upgrade of python3 was successful.
Software installed to 'C:\ProgramData\chocolatey\lib\python3'
python v3.13.3 [Approved]
python package files upgrade completed. Performing other installation steps.
The upgrade of python was successful.
Software installed to 'C:\ProgramData\chocolatey\lib\python'
visualstudio2019-workload-vctools v1.0.1 is the latest version available based on your source(s).
Chocolatey upgraded 3/4 packages.
See the log for details (C:\ProgramData\chocolatey\logs\chocolatey.log).
Type ENTER to exit:
Welcome to Rust!
This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.
Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:
C:\Users\saint\.rustup
This can be modified with the RUSTUP_HOME environment variable.
The Cargo home directory is located at:
C:\Users\saint\.cargo
This can be modified with the CARGO_HOME environment variable.
The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:
C:\Users\saint\.cargo\bin
This path will then be added to your PATH environment variable by
modifying the PATH registry key at HKEY_CURRENT_USER\Environment.
You can uninstall at any time with rustup self uninstall and
these changes will be reverted.
Current installation options:
default host triple: x86_64-pc-windows-msvc
default toolchain: stable (default)
profile: default
modify PATH variable: yes
1) Proceed with standard installation (default - just press enter)
2) Customize installation
3) Cancel installation
>
info: profile set to 'default'
info: default host triple is x86_64-pc-windows-msvc
info: syncing channel updates for 'stable-x86_64-pc-windows-msvc'
info: latest update on 2025-05-15, rust version 1.87.0 (17067e9ac 2025-05-09)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-std'
info: downloading component 'rustc'
73.8 MiB / 73.8 MiB (100 %) 24.2 MiB/s in 3s
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
20.2 MiB / 20.2 MiB (100 %) 1.8 MiB/s in 8s
info: installing component 'rust-std'
22.4 MiB / 22.4 MiB (100 %) 20.4 MiB/s in 1s
info: installing component 'rustc'
73.8 MiB / 73.8 MiB (100 %) 20.8 MiB/s in 3s
info: installing component 'rustfmt'
info: default toolchain set to 'stable-x86_64-pc-windows-msvc'
stable-x86_64-pc-windows-msvc installed - rustc 1.87.0 (17067e9ac 2025-05-09)
Rust is installed now. Great!
To get started you may need to restart your current shell.
This would reload its PATH environment variable to include
Cargo's bin directory (%USERPROFILE%\.cargo\bin).
Press the Enter key to continue.
Creating a Simple Tauri App
The tutorial says you can use yarn, npm, or bun but npm was the only tool available on my command line after the above installation steps. I followed the instructions to create a Tauri app:
npm create tauri-app@latest
The first prompt was for a project name. I pressed ENTER to use the default name.
> npx
> create-tauri-app
✔ Project name · tauri-app
✔ Identifier · com.tauri-app.app
? Choose which language to use for your frontend ›
❯ TypeScript / JavaScript (pnpm, yarn, npm, deno, bun)
Rust
.NET
For TypeScript/JavaScript, you need to specify your package manager. I used npm.
> npx
> create-tauri-app
✔ Project name · tauri-app
✔ Identifier · com.tauri-app.app
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, deno, bun)
? Choose your package manager ›
❯ npm
pnpm
yarn
deno
bun
I selected React as my UI template. I’m so out of it on front end development that I was surprised to see that Angular is still in use (given that I don’t hear much about it these days).
> npx
> create-tauri-app
✔ Project name · tauri-app
✔ Identifier · com.tauri-app.app
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, deno, bun)
✔ Choose your package manager · npm
? Choose your UI template ›
Vanilla
Vue
Svelte
❯ React (https://react.dev/)
Solid
Angular
Preact
Finally, I selected TypeScript as my UI flavor:
> npx
> create-tauri-app
✔ Project name · tauri-app
✔ Identifier · com.tauri-app.app
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, deno, bun)
✔ Choose your package manager · npm
✔ Choose your UI template · React - (https://react.dev/)
? Choose your UI flavor ›
❯ TypeScript
JavaScript
The template was created and instructions on how to run the app were displayed:
> npx
> create-tauri-app
✔ Project name · tauri-app
✔ Identifier · com.tauri-app.app
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, deno, bun)
✔ Choose your package manager · npm
✔ Choose your UI template · React - (https://react.dev/)
✔ Choose your UI flavor · TypeScript
Template created! To get started run:
cd tauri-app
npm install
npm run tauri android init
For Desktop development, run:
npm run tauri dev
For Android development, run:
npm run tauri android dev
I tried running npm run tauri dev without doing anything else, out of curiosity. The command failed (unsurprisingly). npm install is required.
$ cd tauri-app/
$ npm run tauri dev
> tauri-app@0.1.0 tauri
> tauri dev
'tauri' is not recognized as an internal or external command,
operable program or batch file.
$ npm install
added 74 packages, and audited 75 packages in 6s
11 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Running the app takes about 2 minutes (
$ npm run tauri dev
> tauri-app@0.1.0 tauri
> tauri dev
Running BeforeDevCommand (`npm run dev`)
> tauri-app@0.1.0 dev
> vite
VITE v6.3.5 ready in 217 ms
➜ Local: http://localhost:1420/
Running DevCommand (`cargo run --no-default-features --color always --`)
Info Watching C:\repos\learning\tauri\tauri-app\src-tauri for changes...
Updating crates.io index
Locking 474 packages to latest compatible versions
Adding proc-macro-crate v2.0.0 (available: v2.0.2)
Adding serialize-to-javascript v0.1.1 (available: v0.1.2)
Adding serialize-to-javascript-impl v0.1.1 (available: v0.1.2)
Compiling proc-macro2 v1.0.95
Compiling unicode-ident v1.0.18
Compiling cfg-if v1.0.0
Compiling windows_x86_64_msvc v0.52.6
Compiling zerocopy v0.8.25
...
Compiling keyboard-types v0.7.0
Compiling serialize-to-javascript v0.1.1
Compiling embed-resource v3.0.3
Compiling cargo_toml v0.22.1
Compiling tauri-utils v2.4.0
Compiling tauri-winres v0.3.1
Compiling muda v0.16.1
Compiling tauri-build v2.2.0
Compiling tauri-plugin v2.2.0
Compiling tauri-codegen v2.2.0
Compiling tauri v2.5.1
Compiling tauri-plugin-opener v2.2.7
Compiling tauri-macros v2.2.0
Compiling tauri-app v0.1.0 (C:\repos\learning\tauri\tauri-app\src-tauri)
Compiling tao v0.33.0
Compiling webview2-com v0.37.0
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 35s
Running `target\debug\tauri-app.exe`
The second execution of that command was much faster since most of the compilation was already complete.
$ npm run tauri dev
> tauri-app@0.1.0 tauri
> tauri dev
Running BeforeDevCommand (`npm run dev`)
> tauri-app@0.1.0 dev
> vite
Running DevCommand (`cargo run --no-default-features --color always --`)
VITE v6.3.5 ready in 214 ms
➜ Local: http://localhost:1420/
Info Watching C:\repos\learning\tauri\temp\tauri-app\src-tauri for changes...
Compiling tauri-app v0.1.0 (C:\repos\learning\tauri\tauri-app\src-tauri)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 16.97s
Running `target\debug\tauri-app.exe`
Welcome to Tauri + React Desktop Window
Installing pnp
I discovered Tauri in the En Croissant chess program. Per the en-croissant readme, it uses pnpm as the package manager. It is easy to enable as described in the pnpm install instructions.
Since v16.13, Node.js is shipping Corepack for managing package managers. This is an experimental feature, so you need to enable it by running: corepack enable pnpm
$ corepack enable pnpm
$ which pnpm
/c/Program Files/nodejs/pnpm
Building en-croissant
This was easy (compared to some of the other C++ codebases I have been looking at recently). The commands just ran successfully. It’s somewhat unsettling seeing how many packages are downloaded in these npm projects though.
$ pnpm build
> en-croissant@0.11.1 build C:\repos\en-croissant
> tauri build --no-bundle
Running beforeBuildCommand `pnpm build-vite`
> en-croissant@0.11.1 build-vite C:\repos\en-croissant
> tsc --noEmit && vite build
♻️ Generating routes...
✅ Processed routes in 167ms
vite v5.4.8 building for production...
♻️ Regenerating routes...
✅ Processed routes in 21ms
✓ 8330 modules transformed.
warnings when minifying css:
▲ [WARNING] Transforming this CSS nesting syntax is not supported in the configured target environment ("chrome87", "edge88", "es2020", "firefox78", "safari14") [unsupported-css-nesting]
<stdin>:9086:37:
9086 │ [data-mantine-color-scheme="dark"] & {
╵ ^
The nesting transform for this case must generate an ":is(...)" but the configured target environment does not support the ":is" pseudo-class.
...
warning: field `eco` is never read
--> src\opening.rs:13:5
|
12 | struct Opening {
| ------- field in this struct
13 | eco: String,
| ^^^
|
= note: `Opening` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
warning: `en-croissant` (bin "en-croissant") generated 8 warnings (run `cargo fix --bin "en-croissant"` to apply 4 suggestions)
Finished `release` profile [optimized] target(s) in 3m 16s
Built application at: C:\repos\en-croissant\src-tauri\target\release\en-croissant.exe
I like that the output of this entire process is a single executable. Here it is in action after I loaded this FEN: 1r5k/2q2p1p/p2p3B/5PQ1/n1p5/2b4P/PrB3P1/2R1R1K1 w - - 0 0. Something odd is happening here (with the claim that “Black Wins”).