Categories: Assembly, OpenJDK

Selecting the Proper OpenJDK Windows AArch64 Assembler

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:

From build\windows-aarch64-server-slowdebug\make-support\failure-logs\hotspot_variant-server_libjvm_objs_safefetch_windows_aarch64.obj.cmdline

/cygdrive/c/java/forks/dups2/openjdk/jdk/build/windows-aarch64-server-slowdebug/fixpath exec /cygdrive/c/progra~1/micros~1/2022/enterprise/vc/tools/msvc/14.44.35207/bin/hostarm64/arm64/ml64.exe -nologo -c -I/cygdrive/c/java/forks/dups2/openjdk/jdk/src/hotspot/os_cpu/windows_aarch64 -I/cygdrive/c/java/forks/dups2/openjdk/jdk/src/hotspot/os/windows -I/cygdrive/c/java/forks/dups2/openjdk/jdk/src/hotspot/os/windows -Fo/cygdrive/c/java/forks/dups2/openjdk/jdk/build/windows-aarch64-server-slowdebug/hotspot/variant-server/libjvm/objs/safefetch_windows_aarch64.obj -Ta /cygdrive/c/java/forks/dups2/openjdk/jdk/src/hotspot/os_cpu/windows_aarch64/safefetch_windows_aarch64.S

I could manually reproduce the failure by running the command in a developer command prompt:

C:\dev\temp> ml64 -nologo -Ta safefetch_windows_aarch64.S
 Assembling: safefetch_windows_aarch64.S
safefetch_windows_aarch64.S(31) : error A2008:syntax error : .
safefetch_windows_aarch64.S(32) : error A2008:syntax error : .
safefetch_windows_aarch64.S(33) : error A2008:syntax error : .type
safefetch_windows_aarch64.S(34) : error A2034:must be in segment block
safefetch_windows_aarch64.S(36) : error A2034:must be in segment block
safefetch_windows_aarch64.S(37) : error A2034:must be in segment block
safefetch_windows_aarch64.S(38) : error A2008:syntax error : ldr
safefetch_windows_aarch64.S(39) : error A2034:must be in segment block
safefetch_windows_aarch64.S(41) : error A2008:syntax error : .
safefetch_windows_aarch64.S(42) : error A2008:syntax error : .
safefetch_windows_aarch64.S(43) : error A2008:syntax error : .type
safefetch_windows_aarch64.S(44) : error A2034:must be in segment block
safefetch_windows_aarch64.S(45) : error A2034:must be in segment block
safefetch_windows_aarch64.S(46) : error A2034:must be in segment block
safefetch_windows_aarch64.S(53) : error A2008:syntax error : .
safefetch_windows_aarch64.S(54) : error A2008:syntax error : .
safefetch_windows_aarch64.S(55) : error A2008:syntax error : .type
safefetch_windows_aarch64.S(56) : error A2034:must be in segment block
safefetch_windows_aarch64.S(58) : error A2008:syntax error : .
safefetch_windows_aarch64.S(59) : error A2008:syntax error : .
safefetch_windows_aarch64.S(60) : error A2008:syntax error : .type
safefetch_windows_aarch64.S(61) : error A2034:must be in segment block
safefetch_windows_aarch64.S(62) : error A2034:must be in segment block
safefetch_windows_aarch64.S(63) : error A2008:syntax error : ldr
safefetch_windows_aarch64.S(64) : error A2034:must be in segment block
safefetch_windows_aarch64.S(66) : error A2008:syntax error : .
safefetch_windows_aarch64.S(67) : error A2008:syntax error : .
safefetch_windows_aarch64.S(68) : error A2008:syntax error : .type
safefetch_windows_aarch64.S(69) : error A2034:must be in segment block
safefetch_windows_aarch64.S(70) : error A2034:must be in segment block
safefetch_windows_aarch64.S(71) : error A2034:must be in segment block
safefetch_windows_aarch64.S(71) : error A2088:END directive required at end of file

As it turns out, ml64.exe is the x64 assembler – see ML and ML64 command-line reference | Microsoft Learn. I switched to armasm64.exe (see ARM Assembler command-line reference | Microsoft Learn) but it failed with identical arguments.

"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.44.35207\bin\Hostx64\arm64\armasm64.exe" -nologo -c -Ta safefetch_windows_aarch64.S
error A2029: unknown command-line argument or argument value -c

 Usage:      armasm [<options>] sourcefile objectfile
             armasm [<options>] -o objectfile sourcefile
             armasm -h              for help

Removing only -c gives:

error A2029: unknown command-line argument or argument value -Ta

/Ta filenameAssembles source file whose name doesn’t end with the .asm extension.

ML and ML64 command-line reference | Microsoft Learn

/cAssembles only. Does no linking.

ML and ML64 command-line reference | Microsoft Learn

The -Ta flag in the command line comes from jdk/make/common/native/CompileFile.gmk and the -c flag causing an error is from jdk/make/autoconf/flags-other.m4. I first tried moving the -Ta flag but that broke the build:

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.

I opened 8365579: ml64.exe is not the right assembler for Windows aarch64 by swesonga · Pull Request #26791 · openjdk/jdk to fix this issue. I branched off from openjdk/jdk at b0f98df75aee1e94a8c4b3eb8d0b1f4e715011ae for my changes. I initially removed the -Ta argument because it appeared to be a no-op. Code review feedback indicated that I need the -Ta flag unless I could document that it is a no-op. I discovered that it is essential e.g. if your assembly sources are in a .obj file!

Here are some of my fix attempts. I started with -Tasdf to ensure that I got an error indicating that this argument was being picked up by the build.

diff --git a/make/common/native/CompileFile.gmk b/make/common/native/CompileFile.gmk
index 26472da6d02..4d8aaef4445 100644
--- a/make/common/native/CompileFile.gmk
+++ b/make/common/native/CompileFile.gmk
@@ -234,9 +234,14 @@ define CreateCompiledNativeFileBody
            $(SED) $(DEPENDENCY_TARGET_SED_PATTERN) $$($1_DEPS_FILE) > $$($1_DEPS_TARGETS_FILE)
           else
             # For assembler calls just create empty dependency lists
+            ifeq ($(OPENJDK_TARGET_CPU), aarch64)
+              $1_NON_ASM_EXTENSION_FLAG :=
+            else
+              $1_NON_ASM_EXTENSION_FLAG := "-Tasdf "
+            endif
            $$(call ExecuteWithLog, $$@, $$(call MakeCommandRelative, \
                $$($1_COMPILER) $$($1_FLAGS) \
-               $(CC_OUT_OPTION)$$($1_OBJ) $$($1_SRC_FILE))) \
+               $(CC_OUT_OPTION)$$($1_OBJ) $$($1_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)

Attempt 2:

diff --git a/make/common/native/CompileFile.gmk b/make/common/native/CompileFile.gmk
index 26472da6d02..697adbb6e26 100644
--- a/make/common/native/CompileFile.gmk
+++ b/make/common/native/CompileFile.gmk
@@ -155,6 +155,12 @@ define CreateCompiledNativeFileBody
         endif
         $1_FLAGS := $$($1_FLAGS) -DASSEMBLY_SRC_FILE='"$$($1_REL_ASM_SRC)"' \
             -include $(TOPDIR)/make/data/autoheaders/assemblyprefix.h
+      else ifeq ($(TOOLCHAIN_TYPE), microsoft)
+        ifeq ($(OPENJDK_TARGET_CPU), aarch64)
+          $1_NON_ASM_EXTENSION_FLAG :=
+        else
+          $1_NON_ASM_EXTENSION_FLAG := "-Tasdf "
+        endif
       endif
     else ifneq ($$(filter %.cpp %.cc %.mm, $$($1_FILENAME)), )
       # Compile as a C++ or Objective-C++ file
@@ -236,7 +242,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) $$($1_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)

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 :(.

/cygdrive/d/java/forks/dups15/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 -Fo/cygdrive/d/java/forks/dups15/openjdk/jdk/build/windows-x86_64-server-slowdebug/support/native/jdk.incubator.vector/libjsvml/jsvml_d_atan2_windows_x86.obj "-Ta" /cygdrive/d/java/forks/dups15/openjdk/jdk/src/jdk.incubator.vector/windows/native/libjsvml/jsvml_d_atan2_windows_x86.S

Fortunately, the quotes don’t break assembling the source code as demonstrated by the 2 commands below:

copy test_x86_assembler.S test_x86_assembler.lib

"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x86\ml.exe" -nologo -c -Fo test_x86_assembler.obj "-Ta" test_x86_assembler.lib
 Assembling: test_x86_assembler.lib

"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x86\ml.exe" -nologo -c -Fo test_x86_assembler.obj test_x86_assembler.lib
MASM : fatal error A1017:missing source filename

I cleaned this up in 8366195: Remove unnecessary quotes around -Ta ml64 assembler argument by swesonga · Pull Request #27021 · openjdk/jdk, also moving the declaration of the argument to match -Fo as suggested in #26791.


Categories: Web Development

Printer Unfriendly HTML and CSS

I often run into websites with content that does not appear when I print certain web pages. This frustrated me enough to investigate why this happened on one of them. I created a small web page demonstrating the structure of one the offending web pages: overflow-demo-1.html. In this example, the body of the page contains a span with a fixed height of 1200px. Nested in it is a div whose height is set to 100%. This div contains content much taller than 1200px. Trying to print that web page (e.g. via the Save to PDF printer) results in a cut off PDF document without the last paragraphs. I noticed that the offending property is the overflow-y: auto style on the div! Disabling this property enabled all the content to appear on the printed page.

Unfortunately, proper web design (e.g. including printer-friendly stylesheets) appears to be less important for many websites these days. That markup also just looked unusual to me – I didn’t have a good justification for why other than the fact that I’m not used to seeing divs in spans. The HTML5 validator didn’t seem to like this structure either:

Element div not allowed as child of element span in this context.

Ready to check – Nu Html Checker

It looks like this is a common problem. The recommendation in some of these posts is to use overflow-y: visible instead.

Another scenario I have problems with is iframes on a page. I’m not sure these are easily patchable with CSS since there may be security concerns with iframes.


Categories: JavaScript, VS Code

My First VS Code Extension: Open in GitHub

When the Framework-Agnostic Feature Flags – Ezekiel Keator, PayPal talk at JSConf 2025 got into unit testing of the feature flags, I realized that I should write some TypeScript. I wanted to write something practical though. Isn’t that how VS Code extensions are written? This was the motivation I needed to write my first VS Code extension! Searching for how to write a vscode extension led me to Your First Extension | Visual Studio Code Extension API, which got me started.

Installing Node.js on macOS

# 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.

Next, I ran npm install --global yo generator-code – this is the path of “installing Yeoman globally to ease running it repeatedly” as per Your First Extension | Visual Studio Code Extension API.

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:

  1. 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)
  2. 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:

How it works:

  1. When a user right-clicks on a file and selects “Open in GitHub”
  2. The extension determines the workspace folder and Git repository
  3. It executes Git commands to get the remote URL and current branch
  4. Converts the Git URL to a GitHub web URL
  5. Constructs the full GitHub file URL with the correct branch and file path
  6. 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'

I looked at git rebase getting invalid upstream – Stack Overflow before realizing that I needed to use HEAD~4 since I couldn’t include the first commit.

Reinstalling Dependencies

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

See Sonnet 4’s implementation in [From Claude Sonnet 4] Enable opening GitHub URL at current commit · swesonga/vscode-extensions@810b779.

Packaging the Extension

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.


Categories: OpenJDK

Native Stack Printing on Windows AArch64

Another test failure I investigated last month was in the runtime/jni/nativeStack/TestNativeStack.java test. This is the setup I used to run the test in my MSYS environment.

export JDKARCH=aarch64
export DEBUGLEVEL=slowdebug
export JDKSRCPATH=/c/java/forks/openjdk/jdk
export JDKBUILDPATH="${JDKSRCPATH}/build/windows-${JDKARCH}-server-${DEBUGLEVEL}"
export JDKTOTEST="${JDKBUILDPATH}/images/jdk"
export JTREGNATIVEPATH1="${JDKBUILDPATH}/support/test/hotspot/jtreg/native/lib"
export JTREGNATIVEPATH2="${JDKBUILDPATH}/support/test/jdk/jtreg/native/lib"
export JTREGNATIVEPATH3="${JDKBUILDPATH}/support/test/lib/native/lib"
export GTESTPATH="${JDKBUILDPATH}/images/test/hotspot/gtest/server"
export JTREGBINPATH=/c/java/binaries/jtreg/jtreg-8+2

export TESTTORUN=test/hotspot/jtreg/runtime/jni/nativeStack/TestNativeStack.java

./run-jtreg-test.sh $JDKSRCPATH $JDKTOTEST $JTREGBINPATH/lib/jtreg.jar $TESTTORUN -nativepath:$JTREGNATIVEPATH1

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

The failure path is as follows. HAVE_PLATFORM_PRINT_NATIVE_STACK is not defined on Windows AArch64. Consequently, Windows AArch64 uses the implementation of os::platform_print_native_stack that simply returns false. This results in NativeStackPrinter::print_stack having to call NativeStackPrinter::print_stack_from_frame instead. However, the context is null. As a result, the frame used for printing the stack is obtained from os::current_frame(), which returns an empty frame. The frame’s pc() method returns nullptr and the “Native frames: <unavailable>” message is printed.

The fix for this issue is to define the os::platform_print_native_stack method for Windows AArch64 and share the implementation of the Windows x64 os::win32::platform_print_native_stack method with Windows AArch64. I opened [JDK-8369322] Implement native stack printing for Windows-AArch64 – Java Bug System and the associated PR 8369322: Implement native stack printing for Windows-AArch64 by swesonga · Pull Request #27680 · openjdk/jdk fixing this failure. With this change, the native frames are now printed as shown below:

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 . . .

Categories: Networks

Troubleshooting VPN Connectivity

Earlier this year I had trouble connecting to the VPN on one of my devices (Windows 11 Pro 24H2). I also got this error from Teams: We Couldn’t authenticate you. This could be due to a device or network problem, or Teams could be experiencing a technical issue – Microsoft Q&A. It was an interesting learning experience, getting to see parts of the OS/networking stack in action that I don’t usually even think about. The troubleshooting process started with the usual tasks: install all Windows updates, including the optional updates. That option (from my desktop) is shown below:

After all updates had been installed and the computer rebooted, I needed to sync the device from the Access Work or School settings. Sync enrolled device for Windows | Microsoft Learn explains how to do so. See also How to manually sync a Windows 11 device with Intune – Microsoft Q&A. The sync was successful and the device was up to date.

The Azure VPN Client had this error message: Failure in acquiring Microsoft Entra Token: Provider Error <number>: The server or proxy was not found. Someone else ran into this issue in Azure P2S VPN Disconnecting – Microsoft Q&A. This was unusual in my case because the Access work or school pane said that I was connected.

The next step was to run dsregcmd /status and review the device state and details. See Troubleshoot devices by using the dsregcmd command – Microsoft Entra ID | Microsoft Learn. The device had AzureAdJoined : YES but its DeviceAuthStatus was FAILED. Error: 0x...

Next, we examined this registry key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig. Here is a screenshot from my desktop (not the device in question).

It turned out that there was a subkey with outdated configuration information! Deleting that subkey resolved all the connectivity issues. This was interesting to me because I assumed that general internet connectivity meant that there shouldn’t be any DNS issues whatsoever. I clearly didn’t have a good picture of how DNS is set up in these configurations and how these setups can go wrong.


Categories: OpenJDK

Why did Windows gtest catch a std::exception-derived exception

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.

gtest Death Test Structure

One of the failing tests is jdk/test/hotspot/gtest/utilities/test_vmerror.cpp.

TEST_VM_ASSERT_MSG(vmErrorTest, assert1, "assert.str == nullptr. failed: expected null") {
  vmassert(str == nullptr, "expected null");
}

The TEST_VM_ASSERT_MSG macro is defined as follows

#define TEST_VM_ASSERT_MSG(category, name, msg)                     \
  static void test_  ## category ## _ ## name ## _();               \
                                                                    \
  static void child_ ## category ## _ ## name ## _() {              \
    ::testing::GTEST_FLAG(throw_on_failure) = true;                 \
    test_ ## category ## _ ## name ## _();                          \
    gtest_exit_from_child_vm(0);                                    \
  }                                                                 \
                                                                    \
  TEST(category, CONCAT(name, _vm_assert)) {                        \
    ASSERT_EXIT(child_ ## category ## _ ## name ## _(),             \
                ::testing::ExitedWithCode(1),                       \
                "assert failed: " msg);                             \
  }                                                                 \
                                                                    \
  void test_ ## category ## _ ## name ## _()

gtest_exit_from_child_vm(0) cleanly exits the JVM after calling test_vmErrorTest_assert1(). The ASSERT_EXIT macro expects the JVM to crash. The overall design of the death tests is documented in googletest/googletest/include/gtest/gtest-death-test.h at v1.14.0 · google/googletest. The key takeaway is that a child process is started, it executes the death test, and its exit code and stderr are compared with the expected code and message (the latter via regex matching).

// 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_)

The GTEST_DEATH_TEST_ macro creates a DeathTest instance and executes the death test statement. The WindowsDeathTest::AssumeRole() method, which is key in this, is described as follows: it

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 GTEST_EXECUTE_DEATH_TEST_STATEMENT_ macro was the source of the error message!

#define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test)           \
  try {                                                                      \
    GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement);               \
  } catch (const ::std::exception& gtest_exception) {                        \
    fprintf(                                                                 \
        stderr,                                                              \
        "\n%s: Caught std::exception-derived exception escaping the "        \
        "death test statement. Exception message: %s\n",                     \
        ::testing::internal::FormatFileLocation(__FILE__, __LINE__).c_str(), \
        gtest_exception.what());                                             \
    fflush(stderr);                                                          \
    death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \
  } catch (...) {                                                            \
    death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \
  }

Root Causing the std::exception

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.

I then searched for cl.exe .+jvm.lib to get the compiler command line but this gave the compiler commands for gtest-all.cc and gmock-all.cc. The -EHsc flag (/EH (Exception handling model) | Microsoft Learn) was present for these 2 files though! Next, I searched for “gtestLauncher” and found the compiler command generating gtestLauncher.obj. Notice it didn’t have -EHsc!

I also realized that I should have searched for jvm.obj! Here is the single occurence of the jvm\.obj regex. It doesn’t have -EHsc either! Hmm, strange: jdk/make/hotspot/lib/CompileGtest.gmk says gtests should have it! I then searched the make folder for the regex CFLAGS_[^\w] and the primary suspect (of the 3 results) is the SetupCompilerFlags target in jdk/make/common/native/Flags.gmk. That target was last modified in 8325877: Split up NativeCompilation.gmk · openjdk/jdk@0d51b76.

Next, I examined build\windows-x86_64-server-slowdebug\configure-support\config.log and found these lines:

OPENJDK_TARGET_OS='windows'
...
OPENJDK_TARGET_OS_TYPE='windows'
...
TOOLCHAIN_TYPE='microsoft'

I thought that this snippet from jdk/make/common/native/Flags.gmk should have picked them up!

define SetupCompilerFlags
  # Pickup extra OPENJDK_TARGET_OS_TYPE, OPENJDK_TARGET_OS, TOOLCHAIN_TYPE and
  # OPENJDK_TARGET_OS plus OPENJDK_TARGET_CPU pair dependent variables for CFLAGS.
  $1_EXTRA_CFLAGS := $$($1_CFLAGS_$(OPENJDK_TARGET_OS_TYPE)) $$($1_CFLAGS_$(OPENJDK_TARGET_OS)) \
      $$($1_CFLAGS_$(TOOLCHAIN_TYPE)) \
      $$($1_CFLAGS_$(OPENJDK_TARGET_OS)_$(OPENJDK_TARGET_CPU))

What was the leading $1_ prefix though? I wasn’t sure but I tried this change next:

diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..3908e94b624 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -63,7 +63,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
         unused-result zero-as-null-pointer-constant, \
     DISABLED_WARNINGS_clang := format-nonliteral undef unused-result, \
     DEFAULT_CFLAGS := false, \
-    CFLAGS := $(JVM_CFLAGS) \
+    CFLAGS := $(JVM_CFLAGS) -EHsc \
         -I$(GTEST_FRAMEWORK_SRC)/googletest \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock \
@@ -94,7 +94,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
     EXCLUDE_PATTERNS := $(JVM_EXCLUDE_PATTERNS), \
     EXTRA_OBJECT_FILES := $(BUILD_LIBJVM_ALL_OBJS), \
     DEFAULT_CFLAGS := false, \
-    CFLAGS := $(JVM_CFLAGS) \
+    CFLAGS := $(JVM_CFLAGS) -EHsc \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
         $(addprefix -I, $(GTEST_TEST_SRC)), \

The build failed with this error:

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.

I decided to find out who uses SetupCompilerFlags and found jdk/make/common/NativeCompilation.gmk. It is in turn called by SetupJdkNativeCompilation (which I had been trying to change)! The actual compilation is kicked off by jdk/make/common/NativeCompilation.gmk and done by jdk/make/common/native/CompileFile.gmk. The latter calls SetupCompileFileFlags, which is defined in jdk/make/common/native/Flags.gmk. I noticed that it includes the extra CFLAGS and CXXFLAGS in jdk/make/common/native/Flags.gmk. The most important observation though was that jdk/make/common/native/CompileFile.gmk uses the CXXFLAGS for .cpp files! I tried this change but it didn’t pass the flags to the compiler either.

diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..8241ad04cb9 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -69,6 +69,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
     CFLAGS_windows := -EHsc, \
+    CXXFLAGS_windows := -EHsc, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     OPTIMIZATION := $(JVM_OPTIMIZATION), \
     COPY_DEBUG_SYMBOLS := $(GTEST_COPY_DEBUG_SYMBOLS), \
@@ -99,6 +100,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
         $(addprefix -I, $(GTEST_TEST_SRC)), \
     CFLAGS_windows := -EHsc, \
+    CXXFLAGS_windows := -EHsc, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
         undef stringop-overflow, \

Was the code I was changing even used? I tried this change to answer that:

diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..4554b3c89f5 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -66,9 +66,11 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
     CFLAGS := $(JVM_CFLAGS) \
         -I$(GTEST_FRAMEWORK_SRC)/googletest \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
+        -I$(GTEST_FRAMEWORK_SRC)/googletest/include/saintstest \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
     CFLAGS_windows := -EHsc, \
+    CXXFLAGS_windows := -EHsc, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     OPTIMIZATION := $(JVM_OPTIMIZATION), \
     COPY_DEBUG_SYMBOLS := $(GTEST_COPY_DEBUG_SYMBOLS), \
@@ -96,9 +98,11 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
     DEFAULT_CFLAGS := false, \
     CFLAGS := $(JVM_CFLAGS) \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
+        -I$(GTEST_FRAMEWORK_SRC)/googletest/include/saintstest \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
         $(addprefix -I, $(GTEST_TEST_SRC)), \
     CFLAGS_windows := -EHsc, \
+    CXXFLAGS_windows := -EHsc, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
         undef stringop-overflow, \

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:

diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..72161d6d5d2 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -66,9 +66,11 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
     CFLAGS := $(JVM_CFLAGS) \
         -I$(GTEST_FRAMEWORK_SRC)/googletest \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
+        -I$(GTEST_FRAMEWORK_SRC)/googletest/include/saintstest1 \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
     CFLAGS_windows := -EHsc, \
+    CXXFLAGS_windows := -EHsc, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     OPTIMIZATION := $(JVM_OPTIMIZATION), \
     COPY_DEBUG_SYMBOLS := $(GTEST_COPY_DEBUG_SYMBOLS), \
@@ -96,9 +98,11 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
     DEFAULT_CFLAGS := false, \
     CFLAGS := $(JVM_CFLAGS) \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
+        -I$(GTEST_FRAMEWORK_SRC)/googletest/include/saintstest2 \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
         $(addprefix -I, $(GTEST_TEST_SRC)), \
     CFLAGS_windows := -EHsc, \
+    CXXFLAGS_windows := -EHsc, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
         undef stringop-overflow, \
@@ -150,8 +154,10 @@ $(eval $(call SetupJdkExecutable, BUILD_GTEST_LAUNCHER, \
     CFLAGS := $(JVM_CFLAGS) \
         -I$(GTEST_FRAMEWORK_SRC)/googletest \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
+        -I$(GTEST_FRAMEWORK_SRC)/googletest/include/saintstest3 \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
+    CXXFLAGS_windows := -EHsc, \
     LD_SET_ORIGIN := false, \
     LDFLAGS_unix := $(call SET_SHARED_LIBRARY_ORIGIN), \
     JDK_LIBS := gtest:libjvm, \

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:

diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk
index 6b5edc85b23..f7ae373ff17 100644
--- a/make/hotspot/lib/CompileJvm.gmk
+++ b/make/hotspot/lib/CompileJvm.gmk
@@ -179,6 +179,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \
     EXCLUDE_PATTERNS := $(JVM_EXCLUDE_PATTERNS), \
     DEFAULT_CFLAGS := false, \
     CFLAGS := $(JVM_CFLAGS), \
+    CXXFLAGS_windows := -EHsc, \
     abstract_vm_version.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
     arguments.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
     DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc), \

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);

This change failed to build with the following errors, the most notable of which is Compiler Error C2712: cannot use __try in functions that require object unwinding.

...\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!

diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..d9e73fc3847 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -68,7 +68,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
-    CFLAGS_windows := -EHsc, \
+    CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     OPTIMIZATION := $(JVM_OPTIMIZATION), \
     COPY_DEBUG_SYMBOLS := $(GTEST_COPY_DEBUG_SYMBOLS), \
@@ -98,7 +98,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
         $(addprefix -I, $(GTEST_TEST_SRC)), \
-    CFLAGS_windows := -EHsc, \
+    CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
         undef stringop-overflow, \
@@ -152,6 +152,7 @@ $(eval $(call SetupJdkExecutable, BUILD_GTEST_LAUNCHER, \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
+    CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0, \
     LD_SET_ORIGIN := false, \
     LDFLAGS_unix := $(call SET_SHARED_LIBRARY_ORIGIN), \
     JDK_LIBS := gtest:libjvm, \
diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk
index 6b5edc85b23..42f3969c775 100644
--- a/make/hotspot/lib/CompileJvm.gmk
+++ b/make/hotspot/lib/CompileJvm.gmk
@@ -179,6 +179,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \
     EXCLUDE_PATTERNS := $(JVM_EXCLUDE_PATTERNS), \
     DEFAULT_CFLAGS := false, \
     CFLAGS := $(JVM_CFLAGS), \
+    CFLAGS_windows := -DGTEST_HAS_SEH=0, \
     abstract_vm_version.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
     arguments.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
     DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc), \

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:

diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..95794ff0bbe 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -152,6 +152,7 @@ $(eval $(call SetupJdkExecutable, BUILD_GTEST_LAUNCHER, \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
+    CFLAGS_windows := -DGTEST_HAS_SEH=0, \
     LD_SET_ORIGIN := false, \
     LDFLAGS_unix := $(call SET_SHARED_LIBRARY_ORIGIN), \
     JDK_LIBS := gtest:libjvm, \
diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk
index 6b5edc85b23..42f3969c775 100644
--- a/make/hotspot/lib/CompileJvm.gmk
+++ b/make/hotspot/lib/CompileJvm.gmk
@@ -179,6 +179,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \
     EXCLUDE_PATTERNS := $(JVM_EXCLUDE_PATTERNS), \
     DEFAULT_CFLAGS := false, \
     CFLAGS := $(JVM_CFLAGS), \
+    CFLAGS_windows := -DGTEST_HAS_SEH=0, \
     abstract_vm_version.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
     arguments.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
     DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc), \

The gtests built from the diff below still failed:

diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..403613c406c 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -98,7 +98,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
         $(addprefix -I, $(GTEST_TEST_SRC)), \
-    CFLAGS_windows := -EHsc, \
+    CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
         undef stringop-overflow, \
diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk
index 6b5edc85b23..42f3969c775 100644
--- a/make/hotspot/lib/CompileJvm.gmk
+++ b/make/hotspot/lib/CompileJvm.gmk
@@ -179,6 +179,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \
     EXCLUDE_PATTERNS := $(JVM_EXCLUDE_PATTERNS), \
     DEFAULT_CFLAGS := false, \
     CFLAGS := $(JVM_CFLAGS), \
+    CFLAGS_windows := -DGTEST_HAS_SEH=0, \
     abstract_vm_version.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
     arguments.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
     DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc), \

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:

diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..acf0eae159a 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -68,7 +68,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
-    CFLAGS_windows := -EHsc, \
+    CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0 -DMYTEST_LOCATION1, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     OPTIMIZATION := $(JVM_OPTIMIZATION), \
     COPY_DEBUG_SYMBOLS := $(GTEST_COPY_DEBUG_SYMBOLS), \
@@ -98,7 +98,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
         $(addprefix -I, $(GTEST_TEST_SRC)), \
-    CFLAGS_windows := -EHsc, \
+    CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0 -DMYTEST_LOCATION2, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
         undef stringop-overflow, \
@@ -152,6 +152,7 @@ $(eval $(call SetupJdkExecutable, BUILD_GTEST_LAUNCHER, \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
+    CFLAGS_windows := -EHsc -DGTEST_HAS_SEH=0 -DMYTEST_LOCATION3, \
     LD_SET_ORIGIN := false, \
     LDFLAGS_unix := $(call SET_SHARED_LIBRARY_ORIGIN), \
     JDK_LIBS := gtest:libjvm, \
diff --git a/make/hotspot/lib/CompileJvm.gmk b/make/hotspot/lib/CompileJvm.gmk
index 6b5edc85b23..5994ffc6be1 100644
--- a/make/hotspot/lib/CompileJvm.gmk
+++ b/make/hotspot/lib/CompileJvm.gmk
@@ -179,6 +179,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJVM, \
     EXCLUDE_PATTERNS := $(JVM_EXCLUDE_PATTERNS), \
     DEFAULT_CFLAGS := false, \
     CFLAGS := $(JVM_CFLAGS), \
+    CFLAGS_windows := -DGTEST_HAS_SEH=0 -DMYTEST_LOCATION0, \
     abstract_vm_version.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
     arguments.cpp_CXXFLAGS := $(CFLAGS_VM_VERSION), \
     DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc), \

Location 1 only showed up for the 2 files below (matching the INCLUDE_FILES in make/hotspot/lib/CompileGtest.gmk), which made it clear that -DGTEST_HAS_SEH=0 was needed in this section.

  1. /cygdrive/c/repos/googletest/googlemock/src/gmock-all.cc
  2. /cygdrive/c/repos/googletest/googletest/src/gtest-all.cc

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:

cd build/windows-x86_64-server-slowdebug/images/test/hotspot/gtest/server
mkdir ../server-orig
cp * ../server-orig

Microsoft.CodeCoverage.Console.exe instrument gtestLauncher.exe
Microsoft.CodeCoverage.Console.exe instrument jvm.dll

Microsoft.CodeCoverage.Console.exe collect "gtestLauncher.exe -jdk:D:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\images\jdk"

start output.coverage

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 . . .

Here’s the stack for when the breakpoint is hit:

jvm.dll!BitMap::verify_limit(unsigned __int64 bit) Line 206
jvm.dll!BitMap::to_words_align_down(unsigned __int64 bit) Line 94
jvm.dll!BitMap::word_addr(unsigned __int64 bit) Line 144
jvm.dll!BitMap::set_bit(unsigned __int64 bit) Line 37
jvm.dll!JfrEventVerifier::set_field_bit(unsigned __int64 field_idx) Line 41
jvm.dll!JfrEvent<EventTenuringDistribution>::set_field_bit(unsigned __int64 field_idx) Line 267
jvm.dll!EventObjectAllocationOutsideTLAB::set_objectClass(const Klass * new_value) Line 7304
jvm.dll!trace_flag_changed<bool,EventBooleanFlagChanged>(JVMFlag * flag, const bool old_value, const bool new_value, const JVMFlagOrigin origin) Line 39
jvm.dll!TypedFlagAccessImpl<bool,EventBooleanFlagChanged>::check_constraint_and_set(JVMFlag * flag, void * value_addr, JVMFlagOrigin origin, bool verbose) Line 78
jvm.dll!FlagAccessImpl_bool::set_impl(JVMFlag * flag, void * value_addr, JVMFlagOrigin origin) Line 98
jvm.dll!FlagAccessImpl::set(JVMFlag * flag, void * value, JVMFlagOrigin origin) Line 49
jvm.dll!JVMFlagAccess::set_impl(JVMFlag * flag, void * value, JVMFlagOrigin origin) Line 307
jvm.dll!JVMFlagAccess::set_or_assert(JVMFlagsEnum flag_enum, int type_enum, void * value, JVMFlagOrigin origin) Line 353
jvm.dll!JVMFlagAccess::set<bool,0>(JVMFlagsEnum flag_enum, bool value, JVMFlagOrigin origin) Line 101
jvm.dll!Flag_UseLargePagesIndividualAllocation_set(bool value, JVMFlagOrigin origin) Line 69
jvm.dll!os::init() Line 4436
jvm.dll!Threads::create_vm(JavaVMInitArgs * args, bool * canTryAgain) Line 463
jvm.dll!JNI_CreateJavaVM_inner(JavaVM_ * * vm, void * * penv, void * args) Line 3589
jvm.dll!JNI_CreateJavaVM(JavaVM_ * * vm, void * * penv, void * args) Line 3680
jvm.dll!init_jvm(int argc, char * * argv, bool disable_error_handling, JavaVM_ * * jvm_ptr) Line 94
jvm.dll!JVMInitializerListener::OnTestStart(const testing::TestInfo & test_info) Line 124
jvm.dll!testing::internal::TestEventRepeater::OnTestStart(const testing::TestInfo & parameter) Line 3858
jvm.dll!testing::TestInfo::Run() Line 2821
jvm.dll!testing::TestSuite::Run() Line 3015
jvm.dll!testing::internal::UnitTestImpl::RunAllTests() Line 5920
jvm.dll!testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl,bool>(testing::internal::UnitTestImpl * object, bool(testing::internal::UnitTestImpl::*)() method, const char * location) Line 2614
jvm.dll!testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl,bool>(testing::internal::UnitTestImpl * object, bool(testing::internal::UnitTestImpl::*)() method, const char * location) Line 2648
jvm.dll!testing::UnitTest::Run() Line 5484
jvm.dll!RUN_ALL_TESTS() Line 2317
jvm.dll!runUnitTestsInner(int argc, char * * argv) Line 290
jvm.dll!runUnitTests(int argc, char * * argv) Line 371
gtestLauncher.exe!main(int argc, char * * argv) Line 40
[Inline Frame] gtestLauncher.exe!invoke_main() Line 78

Pull Request Feedback

With all this information at hand, I opened 8364664: gtest death tests failing on Windows by swesonga · Pull Request #26661 · openjdk/jdk. One reviewer asked about removing the -EHsc flag from the gtests altogether. I tried it with the change below:

diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk
index d2cdc7685c9..2c6b5f23516 100644
--- a/make/hotspot/lib/CompileGtest.gmk
+++ b/make/hotspot/lib/CompileGtest.gmk
@@ -68,7 +68,6 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include, \
-    CFLAGS_windows := -EHsc, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     OPTIMIZATION := $(JVM_OPTIMIZATION), \
     COPY_DEBUG_SYMBOLS := $(GTEST_COPY_DEBUG_SYMBOLS), \
@@ -98,7 +97,6 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \
         -I$(GTEST_FRAMEWORK_SRC)/googletest/include \
         -I$(GTEST_FRAMEWORK_SRC)/googlemock/include \
         $(addprefix -I, $(GTEST_TEST_SRC)), \
-    CFLAGS_windows := -EHsc, \
     CFLAGS_macosx := -DGTEST_OS_MAC=1, \
     DISABLED_WARNINGS_gcc := $(DISABLED_WARNINGS_gcc) \
         undef stringop-overflow, \

The build failed with this error:

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

This is expected since the googletest code is using C++ exception handling. The more significant revelation for me is that other groups are running gtests on Windows with --gtest_catch_exceptions=0 which disables the inbuilt exception handler. This is done using the GTestWrapper. This comment is helpful because it has links to the earlier issues around this space and explicitly clarifies that C++ exceptions are not used in libjvm code. While this PR did not result in a patch, it was an educational investigation for me!


Categories: OpenJDK

ShowRegistersOnAssertTest Failure on Windows AArch64

I recently investigated why the ShowRegistersOnAssertTest.java test was failing on Windows AArch64. This test was originally added in [JDK-8191101] Show register content in hs-err file on assert – Java Bug System to “retrieve the current context when an assert happens and make that part of the hs-err file.” I searched the codebase for the strings Registers:" and "RAX=" (used in the test) and verified that the hserr output the test is examining is generated by the os::print_context method (see the x86_64 Windows os::print_context, Linux os::print_context, and BSD os::print_context implementations).

The aarch64 Windows os::print_context, Linux os::print_context, and BSD os::print_context implementations use different register names. BSD’s os::print_context writes x0= to the error log whereas aarch64 Windows os::print_context writes X0 =. The BSD implementation should fail this test as well but the log in Update the copyright year · swesonga/jdk@3470f00 did not show this test running! It also didn’t appear in jdk/test/hotspot/jtreg/ProblemList.txt. Actually, a search for the string ShowRegistersOnAssertTest yielded results in that test file only. That led me to review it more closely and find that it didn’t run on macos because the test requires Linux or Windows. However, the fix for the test failure on Windows was obviously using the correct pattern for Windows:

diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/ShowRegistersOnAssertTest.java b/test/hotspot/jtreg/runtime/ErrorHandling/ShowRegistersOnAssertTest.java
index 3b038ebd8a0..b0138625450 100644
--- a/test/hotspot/jtreg/runtime/ErrorHandling/ShowRegistersOnAssertTest.java
+++ b/test/hotspot/jtreg/runtime/ErrorHandling/ShowRegistersOnAssertTest.java
@@ -76,7 +76,11 @@ private static void do_test(boolean do_assert, // true - assert, false - guarant
             } else if (Platform.isX86()) {
                 pattern = new Pattern[] { Pattern.compile("Registers:"), Pattern.compile("EAX=.*")};
             } else if (Platform.isAArch64()) {
-                pattern = new Pattern[] { Pattern.compile("Registers:"), Pattern.compile("R0=.*")};
+                if (Platform.isLinux()) {
+                    pattern = new Pattern[] { Pattern.compile("Registers:"), Pattern.compile("R0=.*")};
+                } else if (Platform.isWindows()) {
+                    pattern = new Pattern[] { Pattern.compile("Registers:"), Pattern.compile("X0 =.*")};
+                }
             } else if (Platform.isS390x()) {
                 pattern = new Pattern[] { Pattern.compile("General Purpose Registers:"),
                                           Pattern.compile("^-{26}$"),

To verify that the data in the error log matched what the test expected, I used this change:

diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/ShowRegistersOnAssertTest.java b/test/hotspot/jtreg/runtime/ErrorHandling/ShowRegistersOnAssertTest.java
index 3b038ebd8a0..76917f06a02 100644
--- a/test/hotspot/jtreg/runtime/ErrorHandling/ShowRegistersOnAssertTest.java
+++ b/test/hotspot/jtreg/runtime/ErrorHandling/ShowRegistersOnAssertTest.java
@@ -87,7 +87,9 @@ private static void do_test(boolean do_assert, // true - assert, false - guarant
                 pattern = new Pattern[] { Pattern.compile("Registers:") };
             }
             // Pattern match the hs_err_pid file.
-            HsErrFileUtils.checkHsErrFileContent(hs_err_file, pattern, false);
+            boolean is_verbose = false;
+            boolean printHserrOnError = true;
+            HsErrFileUtils.checkHsErrFileContent(hs_err_file, pattern, null, true, is_verbose, printHserrOnError);
         }
     }

I filed [JDK-8366483] ShowRegistersOnAssertTest uses wrong register pattern string for Windows on AArch64 – Java Bug System and fixed the test bug in 8366483: ShowRegistersOnAssertTest uses wrong register pattern string for Windows on AArch64 by swesonga · Pull Request #27022 · openjdk/jdk.


Categories: Hardware, Printers

Printing Issues, Oh Brother

I bought new toner for my Brother printer. It’s been a while since I replaced it, but I knew I could count on someone on YouTube to walk me through the process again. The folks that upload these videos deserve an award for their service – I would have wasted time fidgeting around.

Brother HL-L2390DW Installing Ink Toner.

My next issue was figuring out why I couldn’t use double sided printing in the Microsoft Edge browser. does edge have double sided printing – Search pointed me to this helpful post: New Microsoft Edge can’t find Print on both sides & Flip on long edge function while print PDF – Microsoft Q&A. Turns out I needed to enable this feature of the printer. Find the printer in the Printers & scanners panel then open “Printer properties

The Installable Options are in the Device Settings tab. Change the Duplex Unit setting to Installed.

Brother HL-L2390DW Printer Properties

The Print on both sides setting will now be displayed in Microsoft Edge.


Script for Parsing jtreg GitHub Actions Results

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.


Simple Batch File Editing with VSCode Copilot Agent

I need to verify that all symbols are available for every executable binary in any given release. I have been modifiying scratchpad/scripts/java/symbols/verify-symbols-initial.bat at 4a9adb7dfd0e67c129320c8a3c6ee38260695b13 · swesonga/scratchpad but realized that I waste a lot of time copy/pasting repo names and tags. I thought I would experiment with Copilot in VSCode to see if it could help me clean up this script. The prompts I used (also at scratchpad/scripts/java/symbols/verify-symbols-prompts.txt at 4a9adb7dfd0e67c129320c8a3c6ee38260695b13 · swesonga/scratchpad) are in the next listing:

delete all lines from ":: jdk21u x64"
replace 2025-07 with a variable called psu
replace aarch64 and x64 with a variable called arch
replace jdk21u with a variable called jdkrepo
replace jdk-21.0.6+7 with a variable called jdktag
replace D:\java\binaries\jdk\%arch%\%psu%\windows-%jdkrepo% with a variable called psudir
output a message showing the psudir after setting it
make the variables user-specified variables
make the variables command line arguments instead

I pasted the screenshots of the result of each prompt into the slideshow below. Notice that I was using Agent mode with Anthropic’s Claude Sonnet 4 model. I was not exactly saving much time in this scenario using the VSCode agent, but I was certainly understanding its capabilities.

I created a duplicate directory with the initial .bat file then used a single prompt with all the lines above. I was pleased that I got the same result!

I was a bit confused the 2nd time I worked through these prompts because the behavior of the agent was different! I didn’t notice that I had somehow switched from Agent mode to Ask mode but I have now learned the difference between these two modes.