Skip to content

Commit

Permalink
2.0.4 - Harden JVM discovery logic.
Browse files Browse the repository at this point in the history
Only check standard java.version property, do not collect build number.
If a JVM version is invalid, warn and skip. Do not throw.
Add error handling around -XshowSettings invocation.
  • Loading branch information
dscalzi committed Apr 13, 2023
1 parent e70e27c commit c711123
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 142 deletions.
55 changes: 38 additions & 17 deletions lib/java/JavaGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export interface JavaVersion {
major: number
minor: number
patch: number
build?: number
}

export interface AdoptiumJdk {
Expand Down Expand Up @@ -319,13 +318,26 @@ export interface HotSpotSettings {
* @param execPath The path to the Java executable.
* @returns The parsed HotSpot VM properties.
*/
export async function getHotSpotSettings(execPath: string): Promise<HotSpotSettings> {
export async function getHotSpotSettings(execPath: string): Promise<HotSpotSettings | null> {

const javaExecutable = execPath.includes('javaw.exe') ? execPath.replace('javaw.exe', 'java.exe') : execPath

if(!await pathExists(execPath)) {
log.warn(`Candidate JVM path does not exist, skipping. ${execPath}`)
return null
}

const execAsync = promisify(exec)

const { stderr } = await execAsync(`"${javaExecutable}" -XshowSettings:properties -version`)
let stderr
try {
stderr = (await execAsync(`"${javaExecutable}" -XshowSettings:properties -version`)).stderr
} catch(error) {
log.error(`Failed to resolve JVM settings for '${execPath}'`)
log.error(error)
return null
}


const listProps = [
'java.library.path'
Expand Down Expand Up @@ -368,7 +380,11 @@ export async function resolveJvmSettings(paths: string[]): Promise<{ [path: stri

for(const path of paths) {
const settings = await getHotSpotSettings(javaExecFromRoot(path))
ret[path] = settings
if(settings != null) {
ret[path] = settings
} else {
log.warn(`Skipping invalid JVM candidate: ${path}`)
}
}

return ret
Expand All @@ -389,14 +405,19 @@ export function filterApplicableJavaPaths(resolvedSettings: { [path: string]: Ho
.filter(([, settings ]) => parseInt(settings['sun.arch.data.model']) === 64) // Only allow 64-bit.
.filter(([, settings ]) => arm ? settings['os.arch'] === 'aarch64' : true) // Only allow arm on arm architecture (disallow rosetta on m2 mac)
.map(([ path, settings ]) => {
const parsedVersion = parseJavaRuntimeVersion(settings['java.runtime.version'])
const parsedVersion = parseJavaRuntimeVersion(settings['java.version'])
if(parsedVersion == null) {
log.error(`Failed to parse JDK version at location '${path}' (Vendor: ${settings['java.vendor']})`)
return null!
}
return {
semver: parsedVersion,
semverStr: javaVersionToString(parsedVersion),
vendor: settings['java.vendor'],
path
}
})
.filter(x => x != null)

// Now filter by options.
const jvmDetails = jvmDetailsUnfiltered
Expand Down Expand Up @@ -701,7 +722,7 @@ export async function loadMojangLauncherData(): Promise<LauncherJson | null> {
* @param {string} verString Full version string to parse.
* @returns Object containing the version information.
*/
export function parseJavaRuntimeVersion(verString: string): JavaVersion {
export function parseJavaRuntimeVersion(verString: string): JavaVersion | null {
if(verString.startsWith('1.')){
return parseJavaRuntimeVersionLegacy(verString)
} else {
Expand All @@ -716,21 +737,21 @@ export function parseJavaRuntimeVersion(verString: string): JavaVersion {
* @param {string} verString Full version string to parse.
* @returns Object containing the version information.
*/
export function parseJavaRuntimeVersionLegacy(verString: string): JavaVersion {
export function parseJavaRuntimeVersionLegacy(verString: string): JavaVersion | null {
// 1.{major}.0_{update}-b{build}
// ex. 1.8.0_152-b16
const regex = /1.(\d+).(\d+)_(\d+)(?:-b(\d+))?/
const match = regex.exec(verString)!

if(match == null) {
throw new Error(`Failed to parse legacy Java version: ${verString}`)
log.error(`Failed to parse legacy Java version: ${verString}`)
return null
}

return {
major: parseInt(match[1]),
minor: parseInt(match[2]),
patch: parseInt(match[3]),
build: match[4] != undefined ? parseInt(match[4]) : undefined
patch: parseInt(match[3])
}
}

Expand All @@ -741,26 +762,26 @@ export function parseJavaRuntimeVersionLegacy(verString: string): JavaVersion {
* @param {string} verString Full version string to parse.
* @returns Object containing the version information.
*/
export function parseJavaRuntimeVersionSemver(verString: string): JavaVersion {
export function parseJavaRuntimeVersionSemver(verString: string): JavaVersion | null {
// {major}.{minor}.{patch}+{build}
// ex. 10.0.2+13 or 10.0.2.13
const regex = /(\d+)\.(\d+).(\d+)(?:[+.](\d+))?/
const match = regex.exec(verString)!

if(match == null) {
throw new Error(`Failed to parse semver Java version: ${verString}`)
log.error(`Failed to parse semver Java version: ${verString}`)
return null
}

return {
major: parseInt(match[1]),
minor: parseInt(match[2]),
patch: parseInt(match[3]),
build: match[4] != undefined ? parseInt(match[4]) : undefined
patch: parseInt(match[3])
}
}

export function javaVersionToString({ major, minor, patch, build }: JavaVersion): string {
return `${major}.${minor}.${patch}${build != null ? `+${build}` : ''}`
export function javaVersionToString({ major, minor, patch }: JavaVersion): string {
return `${major}.${minor}.${patch}`
}

export interface JavaDiscoverer {
Expand Down Expand Up @@ -898,7 +919,7 @@ export class Win32RegistryJavaDiscoverer implements JavaDiscoverer {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if(isNaN(vKey as any)) {
// Should be a semver key.
major = parseJavaRuntimeVersion(vKey).major
major = parseJavaRuntimeVersion(vKey)?.major ?? -1
} else {
// This is an abbreviated version, ie 1.8 or 17.
const asNum = parseFloat(vKey)
Expand Down
Loading

0 comments on commit c711123

Please sign in to comment.