[wpiutil] Check MSVC Runtime (#7301)

This commit is contained in:
Thad House
2024-11-05 08:51:48 -08:00
committed by GitHub
parent 63e623d70b
commit f2d2500d1d
9 changed files with 202 additions and 2 deletions

View File

@@ -10,8 +10,10 @@
#include <hal/DriverStation.h>
#include <hal/HALBase.h>
#include <hal/Main.h>
#include <wpi/RuntimeCheck.h>
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include <wpi/string.h>
#include "frc/Errors.h"
#include "frc/RuntimeType.h"
@@ -53,6 +55,21 @@ void RunRobot(wpi::mutex& m, Robot** robot) {
template <class Robot>
int StartRobot() {
uint32_t foundMajor;
uint32_t foundMinor;
uint32_t expectedMajor;
uint32_t expectedMinor;
WPI_String runtimePath;
if (!WPI_IsRuntimeValid(&foundMajor, &foundMinor, &expectedMajor,
&expectedMinor, &runtimePath)) {
// We could make this error better, however unlike Java, there is only a
// single scenario that could be occuring. The entirety of VS is too out
// of date. In most cases the linker should detect this, but not always.
fmt::println(
"Your copy of Visual Studio is out of date. Please update it.\n");
return 1;
}
int halInit = RunHALInitialization();
if (halInit != 0) {
return halInit;

View File

@@ -397,6 +397,9 @@ public abstract class RobotBase implements AutoCloseable {
* @param robotSupplier Function that returns an instance of the robot subclass.
*/
public static <T extends RobotBase> void startRobot(Supplier<T> robotSupplier) {
// Check that the MSVC runtime is valid.
WPIUtilJNI.checkMsvcRuntime();
if (!HAL.initialize(500, 0)) {
throw new IllegalStateException("Failed to initialize. Terminating");
}

View File

@@ -9,7 +9,7 @@ ext {
groupId = 'edu.wpi.first.wpiutil'
nativeName = 'wpiutil'
devMain = 'edu.wpi.first.wpiutil.DevMain'
devMain = 'edu.wpi.first.util.DevMain'
def generateTask = createGenerateResourcesTask('main', 'WPI', 'wpi', project)
splitSetup = {

View File

@@ -9,6 +9,7 @@ public final class DevMain {
public static void main(String[] args) {
System.out.println("Hello World!");
System.out.println(CombinedRuntimeLoader.getPlatformPath());
WPIUtilJNI.checkMsvcRuntime();
}
private DevMain() {}

View File

@@ -0,0 +1,51 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
package edu.wpi.first.util;
/** Exception thrown due to a bad MSVC Runtime. */
public class MsvcRuntimeException extends RuntimeException {
private static final long serialVersionUID = -9155939328084105142L;
private static String generateMessage(
int foundMajor, int foundMinor, int expectedMajor, int expectedMinor, String runtimePath) {
String jvmLocation = ProcessHandle.current().info().command().orElse("Unknown");
StringBuffer builder = new StringBuffer(100);
builder
.append("Invalid MSVC Runtime Detected.\n")
.append(
String.format(
"Expected at least %d.%d, but found %d.%d\n",
expectedMajor, expectedMinor, foundMajor, foundMinor))
.append(String.format("JVM Location: %s\n", jvmLocation))
.append(String.format("Runtime DLL Location: %s\n", runtimePath))
.append("See https://wpilib.org/jvmruntime for more information\n");
return builder.toString();
}
/**
* Constructs a runtime exception.
*
* @param foundMajor found major
* @param foundMinor found minor
* @param expectedMajor expected major
* @param expectedMinor expected minor
* @param runtimePath path of runtime
*/
public MsvcRuntimeException(
int foundMajor, int foundMinor, int expectedMajor, int expectedMinor, String runtimePath) {
super(generateMessage(foundMajor, foundMinor, expectedMajor, expectedMinor, runtimePath));
}
/**
* Constructs a runtime exception.
*
* @param msg message
*/
public MsvcRuntimeException(String msg) {
super(msg);
}
}

View File

@@ -63,6 +63,9 @@ public class WPIUtilJNI {
libraryLoaded = true;
}
/** Checks if the MSVC runtime is valid. Throws a runtime exception if not. */
public static native void checkMsvcRuntime();
/**
* Write the given string to stderr.
*

View File

@@ -0,0 +1,73 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/RuntimeCheck.h"
#ifdef _WIN32
#include <cstdio>
#include <memory>
#include "Windows.h"
extern "C" int32_t WPI_IsRuntimeValid(uint32_t* foundMajor,
uint32_t* foundMinor,
uint32_t* expectedMajor,
uint32_t* expectedMinor,
WPI_String* runtimePath) {
HMODULE msvcRuntimeModule = GetModuleHandleW(L"msvcp140.dll");
if (!msvcRuntimeModule) {
return 1;
}
HRSRC versionResource = FindResourceW(msvcRuntimeModule, MAKEINTRESOURCEW(1),
MAKEINTRESOURCEW(16));
if (!versionResource) {
return 1;
}
HGLOBAL loadedVersion = LoadResource(msvcRuntimeModule, versionResource);
if (!loadedVersion) {
return 1;
}
// No need to unlock. Thats vestigial of windows before my time...
LPVOID lockedResource = LockResource(loadedVersion);
if (!lockedResource) {
return 1;
}
DWORD resourceSize = SizeofResource(msvcRuntimeModule, versionResource);
if (resourceSize == 0) {
return 1;
}
std::unique_ptr<char[]> resourceBuffer = // NOLINT
std::make_unique<char[]>(resourceSize); // NOLINT
std::memcpy(resourceBuffer.get(), lockedResource, resourceSize);
VS_FIXEDFILEINFO* fileInfo = nullptr;
UINT fileInfoLen = 0;
if (!VerQueryValueW(resourceBuffer.get(), L"\\",
reinterpret_cast<void**>(&fileInfo), &fileInfoLen)) {
return 1;
}
*foundMajor = HIWORD(fileInfo->dwProductVersionMS);
*foundMinor = LOWORD(fileInfo->dwProductVersionMS);
*expectedMajor = 14;
*expectedMinor = 40;
bool ValidRuntime =
*foundMajor != *expectedMajor || *foundMinor >= *expectedMinor;
if (!ValidRuntime) {
DWORD Size = MAX_PATH;
char* ValidBuffer = WPI_AllocateString(runtimePath, Size);
DWORD RetVal = GetModuleFileNameA(msvcRuntimeModule, ValidBuffer, Size);
while (RetVal == Size) {
Size *= 2;
WPI_FreeString(runtimePath);
ValidBuffer = WPI_AllocateString(runtimePath, Size);
RetVal = GetModuleFileNameA(msvcRuntimeModule, ValidBuffer, Size);
}
runtimePath->len = RetVal;
}
return ValidRuntime ? 1 : 0;
}
#else
extern "C" int32_t WPI_IsRuntimeValid(uint32_t*, uint32_t*, uint32_t*,
uint32_t*, WPI_String*) {
return 1;
}
#endif

View File

@@ -10,6 +10,7 @@
#include "wpi/DataLog.h"
#include "wpi/FileLogger.h"
#include "wpi/RawFrame.h"
#include "wpi/RuntimeCheck.h"
#include "wpi/Synchronization.h"
#include "wpi/jni_util.h"
#include "wpi/print.h"
@@ -25,13 +26,15 @@ static JException indexOobEx;
static JException interruptedEx;
static JException ioEx;
static JException nullPointerEx;
static JException msvcRuntimeEx;
static const JExceptionInit exceptions[] = {
{"java/lang/IllegalArgumentException", &illegalArgEx},
{"java/lang/IndexOutOfBoundsException", &indexOobEx},
{"java/lang/InterruptedException", &interruptedEx},
{"java/io/IOException", &ioEx},
{"java/lang/NullPointerException", &nullPointerEx}};
{"java/lang/NullPointerException", &nullPointerEx},
{"edu/wpi/first/util/MsvcRuntimeException", &msvcRuntimeEx}};
void wpi::ThrowIllegalArgumentException(JNIEnv* env, std::string_view msg) {
illegalArgEx.Throw(env, msg);
@@ -78,6 +81,34 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
}
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: checkMsvcRuntime
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_checkMsvcRuntime
(JNIEnv* env, jclass)
{
uint32_t foundMajor;
uint32_t foundMinor;
uint32_t expectedMajor;
uint32_t expectedMinor;
WPI_String runtimePath;
if (!WPI_IsRuntimeValid(&foundMajor, &foundMinor, &expectedMajor,
&expectedMinor, &runtimePath)) {
static jmethodID ctor =
env->GetMethodID(msvcRuntimeEx, "<init>", "(IIIILjava/lang/String;)V");
jstring jmsvcruntime = MakeJString(env, wpi::to_string_view(&runtimePath));
jobject exception =
env->NewObject(msvcRuntimeEx, ctor, foundMajor, foundMinor,
expectedMajor, expectedMinor, jmsvcruntime);
WPI_FreeString(&runtimePath);
env->Throw(static_cast<jthrowable>(exception));
}
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: writeStderr

View File

@@ -0,0 +1,21 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include "wpi/string.h"
#ifdef __cplusplus
extern "C" {
#endif
int32_t WPI_IsRuntimeValid(uint32_t* FoundMajor, uint32_t* FoundMinor,
uint32_t* ExpectedMajor, uint32_t* ExpectedMinor,
WPI_String* RuntimePath);
#ifdef __cplusplus
} // extern "C"
#endif