1 /* 2 * Copyright (c) 2014, 2015, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 34 #include "JavaVirtualMachine.h" 35 #include "Platform.h" 36 #include "PlatformString.h" 37 #include "FilePath.h" 38 #include "Package.h" 39 #include "Java.h" 40 #include "Helpers.h" 41 #include "Messages.h" 42 #include "Macros.h" 43 #include "PlatformThread.h" 44 45 #include "jni.h" 46 47 #include <map> 48 #include <list> 49 50 51 bool RunVM() { 52 bool result = false; 53 JavaVirtualMachine javavm; 54 55 if (javavm.StartJVM() == true) { 56 result = true; 57 javavm.ShutdownJVM(); 58 } 59 else { 60 Platform& platform = Platform::GetInstance(); 61 platform.ShowMessage(_T("Failed to launch JVM\n")); 62 } 63 64 return result; 65 } 66 67 68 // Private typedef for function pointer casting 69 #ifndef USE_JLI_LAUNCH 70 #define LAUNCH_FUNC "JNI_CreateJavaVM" 71 typedef jint (JNICALL *JVM_CREATE)(JavaVM ** jvm, JNIEnv ** env, void *); 72 #else 73 #define LAUNCH_FUNC "JLI_Launch" 74 typedef int (JNICALL *JVM_CREATE)(int argc, char ** argv, 75 int jargc, const char** jargv, 76 int appclassc, const char** appclassv, 77 const char* fullversion, 78 const char* dotversion, 79 const char* pname, 80 const char* lname, 81 jboolean javaargs, 82 jboolean cpwildcard, 83 jboolean javaw, 84 jint ergo); 85 #endif //USE_JLI_LAUNCH 86 87 class JavaLibrary : public Library { 88 JVM_CREATE FCreateProc; 89 90 JavaLibrary(const TString &FileName); 91 92 public: 93 JavaLibrary() : Library() { 94 FCreateProc = NULL; 95 } 96 97 #ifndef USE_JLI_LAUNCH 98 bool JavaVMCreate(JavaVM** jvm, JNIEnv** env, void* jvmArgs) { 99 bool result = true; 100 101 if (FCreateProc == NULL) { 102 FCreateProc = (JVM_CREATE)GetProcAddress(LAUNCH_FUNC); 103 } 104 105 if (FCreateProc == NULL) { 106 Platform& platform = Platform::GetInstance(); 107 Messages& messages = Messages::GetInstance(); 108 platform.ShowMessage(messages.GetMessage(FAILED_LOCATING_JVM_ENTRY_POINT)); 109 return false; 110 } 111 112 if ((*FCreateProc)(jvm, env, jvmArgs) < 0) { 113 Platform& platform = Platform::GetInstance(); 114 Messages& messages = Messages::GetInstance(); 115 platform.ShowMessage(messages.GetMessage(FAILED_CREATING_JVM)); 116 return false; 117 } 118 119 return result; 120 } 121 #else 122 bool JavaVMCreate(size_t argc, char *argv[]) { 123 if (FCreateProc == NULL) { 124 FCreateProc = (JVM_CREATE)GetProcAddress(LAUNCH_FUNC); 125 } 126 127 if (FCreateProc == NULL) { 128 Platform& platform = Platform::GetInstance(); 129 Messages& messages = Messages::GetInstance(); 130 platform.ShowMessage(messages.GetMessage(FAILED_LOCATING_JVM_ENTRY_POINT)); 131 return false; 132 } 133 134 return FCreateProc((int)argc, argv, 135 0, NULL, 136 0, NULL, 137 "", 138 "", 139 "java", 140 "java", 141 false, 142 false, 143 false, 144 0) == 0; 145 } 146 #endif //USE_JLI_LAUNCH 147 }; 148 149 #ifndef USE_JLI_LAUNCH 150 //debug hook to print JVM messages into console. 151 static jint JNICALL vfprintfHook(FILE *fp, const char *format, va_list args) { 152 #ifdef WINDOWS 153 char buffer[20480]; 154 int len; 155 HANDLE hConsole; 156 DWORD wasWritten; 157 158 len = _vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer), format, args); 159 160 if (len <= 0) { 161 return len; 162 } 163 164 hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 165 166 if (hConsole == INVALID_HANDLE_VALUE) { 167 return false; 168 } 169 170 //JVM will always pass us ASCII 171 WriteConsoleA(hConsole, buffer, strlen(buffer), &wasWritten, NULL); 172 173 return (jint) len; 174 #endif //WINDOWS 175 #ifdef LINUX 176 return 0; 177 #endif //LINUX 178 } 179 #endif //USE_JLI_LAUNCH 180 181 //-------------------------------------------------------------------------------------------------- 182 183 struct JavaOptionItem { 184 TString name; 185 TString value; 186 void* extraInfo; 187 }; 188 189 190 class JavaOptions { 191 private: 192 std::list<JavaOptionItem> FItems; 193 JavaVMOption* FOptions; 194 195 public: 196 JavaOptions() { 197 FOptions = NULL; 198 199 #ifndef USE_JLI_LAUNCH 200 #ifdef DEBUG 201 Platform& platform = Platform::GetInstance(); 202 203 if (platform.GetDebugState() == dsNative) { 204 AppendValue(_T("vfprintf"), _T(""), (void*)vfprintfHook); 205 } 206 #endif //DEBUG 207 #endif //USE_JLI_LAUNCH 208 } 209 210 ~JavaOptions() { 211 if (FOptions != NULL) { 212 for (unsigned int index = 0; index < GetCount(); index++) { 213 delete[] FOptions[index].optionString; 214 } 215 216 delete[] FOptions; 217 } 218 } 219 220 void AppendValue(const TString Key, TString Value, void* Extra) { 221 JavaOptionItem item; 222 item.name = Key; 223 item.value = Value; 224 item.extraInfo = Extra; 225 FItems.push_back(item); 226 } 227 228 void AppendValue(const TString Key, TString Value) { 229 AppendValue(Key, Value, NULL); 230 } 231 232 void AppendValues(OrderedMap<TString, TString> Values) { 233 std::vector<TString> orderedKeys = Values.GetKeys(); 234 235 for (std::vector<TString>::const_iterator iterator = orderedKeys.begin(); iterator != orderedKeys.end(); iterator++) { 236 TString name = *iterator; 237 TString value; 238 239 if (Values.GetValue(name, value) == true) { 240 AppendValue(name, value); 241 } 242 } 243 } 244 245 void ReplaceValue(const TString Key, TString Value) { 246 for (std::list<JavaOptionItem>::iterator iterator = FItems.begin(); 247 iterator != FItems.end(); iterator++) { 248 249 TString lkey = iterator->name; 250 251 if (lkey == Key) { 252 JavaOptionItem item = *iterator; 253 item.value = Value; 254 iterator = FItems.erase(iterator); 255 FItems.insert(iterator, item); 256 break; 257 } 258 } 259 } 260 261 #ifndef USE_JLI_LAUNCH 262 JavaVMOption* ToJavaOptions() { 263 FOptions = new JavaVMOption[FItems.size()]; 264 memset(FOptions, 0, sizeof(JavaVMOption) * FItems.size()); 265 Macros& macros = Macros::GetInstance(); 266 unsigned int index = 0; 267 268 for (std::list<JavaOptionItem>::const_iterator iterator = FItems.begin(); 269 iterator != FItems.end(); iterator++) { 270 TString key = iterator->name; 271 TString value = iterator->value; 272 TString option = Helpers::NameValueToString(key, value); 273 option = macros.ExpandMacros(option); 274 #ifdef DEBUG 275 printf("%s\n", PlatformString(option).c_str()); 276 #endif //DEBUG 277 FOptions[index].optionString = PlatformString::duplicate(PlatformString(option).c_str()); 278 FOptions[index].extraInfo = iterator->extraInfo; 279 index++; 280 } 281 282 return FOptions; 283 } 284 #else 285 std::list<TString> ToList() { 286 std::list<TString> result; 287 Macros& macros = Macros::GetInstance(); 288 289 for (std::list<JavaOptionItem>::const_iterator iterator = FItems.begin(); 290 iterator != FItems.end(); iterator++) { 291 TString key = iterator->name; 292 TString value = iterator->value; 293 TString option = Helpers::NameValueToString(key, value); 294 option = macros.ExpandMacros(option); 295 result.push_back(option); 296 } 297 298 return result; 299 } 300 #endif //USE_JLI_LAUNCH 301 302 size_t GetCount() { 303 return FItems.size(); 304 } 305 }; 306 307 // jvmuserargs can have a trailing equals in the key. This needs to be removed to use 308 // other parts of the launcher. 309 OrderedMap<TString, TString> RemoveTrailingEquals(OrderedMap<TString, TString> Map) { 310 OrderedMap<TString, TString> result; 311 312 std::vector<TString> keys = Map.GetKeys(); 313 314 for (size_t index = 0; index < keys.size(); index++) { 315 TString name = keys[index]; 316 TString value; 317 318 if (Map.GetValue(name, value) == true) { 319 // If the last character of the key is an equals, then remove it. If there is no 320 // equals then combine the two as a key. 321 TString::iterator i = name.end(); 322 i--; 323 324 if (*i == '=') { 325 name = name.substr(0, name.size() - 1); 326 } 327 else { 328 i = value.begin(); 329 330 if (*i == '=') { 331 value = value.substr(1, value.size() - 1); 332 } 333 else { 334 name = name + value; 335 value = _T(""); 336 } 337 } 338 339 result.Append(name, value); 340 } 341 } 342 343 return result; 344 } 345 346 //-------------------------------------------------------------------------------------------------- 347 348 JavaVirtualMachine::JavaVirtualMachine() { 349 #ifndef USE_JLI_LAUNCH 350 FEnv = NULL; 351 FJvm = NULL; 352 #endif //USE_JLI_LAUNCH 353 } 354 355 JavaVirtualMachine::~JavaVirtualMachine(void) { 356 } 357 358 bool JavaVirtualMachine::StartJVM() { 359 Platform& platform = Platform::GetInstance(); 360 Package& package = Package::GetInstance(); 361 362 TString classpath = package.GetClassPath(); 363 364 JavaOptions options; 365 options.AppendValue(_T("-Djava.class.path"), classpath); 366 options.AppendValue(_T("-Djava.library.path"), package.GetPackageAppDirectory() + FilePath::PathSeparator() + package.GetPackageLauncherDirectory()); 367 options.AppendValue(_T("-Djava.launcher.path"), package.GetPackageLauncherDirectory()); 368 options.AppendValue(_T("-Dapp.preferences.id"), package.GetAppID()); 369 options.AppendValues(package.GetJVMArgs()); 370 options.AppendValues(RemoveTrailingEquals(package.GetJVMUserArgs())); 371 372 #ifdef DEBUG 373 if (package.Debugging() == dsJava) { 374 options.AppendValue(_T("-Xdebug"), _T("")); 375 options.AppendValue(_T("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=localhost:8000"), _T("")); 376 platform.ShowMessage(_T("localhost:8000")); 377 } 378 #endif //DEBUG 379 380 TString maxHeapSizeOption; 381 TString minHeapSizeOption; 382 383 384 if (package.GetMemoryState() == PackageBootFields::msAuto) { 385 TPlatformNumber memorySize = package.GetMemorySize(); 386 TString memory = PlatformString((size_t)memorySize).toString() + _T("m"); 387 maxHeapSizeOption = TString(_T("-Xmx")) + memory; 388 options.AppendValue(maxHeapSizeOption, _T("")); 389 390 if (memorySize > 256) 391 minHeapSizeOption = _T("-Xms256m"); 392 else 393 minHeapSizeOption = _T("-Xms") + memory; 394 395 options.AppendValue(minHeapSizeOption, _T("")); 396 } 397 398 TString mainClassName = package.GetMainClassName(); 399 400 if (mainClassName.empty() == true) { 401 Messages& messages = Messages::GetInstance(); 402 platform.ShowMessage(messages.GetMessage(NO_MAIN_CLASS_SPECIFIED)); 403 return false; 404 } 405 406 JavaLibrary javaLibrary; 407 javaLibrary.AddDependencies(platform.FilterOutRuntimeDependenciesForPlatform(platform.GetLibraryImports(package.GetJVMLibraryFileName()))); 408 javaLibrary.Load(package.GetJVMLibraryFileName()); 409 410 #ifndef USE_JLI_LAUNCH 411 if (package.HasSplashScreen() == true) { 412 options.AppendValue(TString(_T("-splash:")) + package.GetSplashScreenFileName(), _T("")); 413 } 414 415 // Set up the VM init args 416 JavaVMInitArgs jvmArgs; 417 memset(&jvmArgs, 0, sizeof(JavaVMInitArgs)); 418 jvmArgs.version = JNI_VERSION_1_6; 419 jvmArgs.options = options.ToJavaOptions(); 420 jvmArgs.nOptions = (jint)options.GetCount(); 421 jvmArgs.ignoreUnrecognized = JNI_TRUE; 422 423 if (javaLibrary.JavaVMCreate(&FJvm, &FEnv, &jvmArgs) == true) { 424 try { 425 JavaClass mainClass(FEnv, Helpers::ConvertIdToJavaPath(mainClassName)); 426 JavaStaticMethod mainMethod = mainClass.GetStaticMethod(_T("main"), _T("([Ljava/lang/String;)V")); 427 std::list<TString> appargs = package.GetArgs(); 428 JavaStringArray largs(FEnv, appargs); 429 430 package.FreeBootFields(); 431 432 mainMethod.CallVoidMethod(1, largs.GetData()); 433 return true; 434 } 435 catch (JavaException& exception) { 436 platform.ShowMessage(exception.GetMessage()); 437 return false; 438 } 439 } 440 441 return false; 442 } 443 #else 444 // Initialize the arguments to JLI_Launch() 445 // 446 // On Mac OS X JLI_Launch spawns a new thread that actually starts the JVM. This 447 // new thread simply re-runs main(argc, argv). Therefore we do not want 448 // to add new args if we are still in the original main thread so we 449 // will treat them as command line args provided by the user ... 450 // Only propagate original set of args first time. 451 452 options.AppendValue(Helpers::ConvertPathToId(mainClassName), _T("")); 453 454 std::list<TString> vmargs; 455 vmargs.push_back(package.GetCommandName()); 456 457 // Mac adds a ProcessSerialNumber to args when launched from .app 458 // filter out the psn since they it's not expected in the app 459 if (platform.IsMainThread() == false) { 460 //TODO shows a splash screen, does not work on Windows, and it does not go away and 461 // it hangs the process. 462 if (package.HasSplashScreen() == true) { 463 options.AppendValue(TString(_T("-splash:")) + package.GetSplashScreenFileName(), _T("")); 464 } 465 466 std::list<TString> loptions = options.ToList(); 467 vmargs.splice(vmargs.end(), loptions, loptions.begin(), loptions.end()); 468 } 469 470 std::list<TString> largs = package.GetArgs(); 471 vmargs.splice(vmargs.end(), largs, largs.begin(), largs.end()); 472 size_t argc = vmargs.size(); 473 DynamicBuffer<char*> argv(argc + 1); 474 unsigned int index = 0; 475 476 for (std::list<TString>::const_iterator iterator = vmargs.begin(); 477 iterator != vmargs.end(); iterator++) { 478 TString item = *iterator; 479 std::string arg = PlatformString(item).toStdString(); 480 #ifdef DEBUG 481 printf("%i %s\n", index, arg.c_str()); 482 #endif //DEBUG 483 argv[index] = PlatformString::duplicate(arg.c_str()); 484 index++; 485 } 486 487 argv[argc] = NULL; 488 489 // On Mac we can only free the boot fields if the calling thread is not the main thread. 490 #ifdef MAC 491 if (platform.IsMainThread() == false) { 492 package.FreeBootFields(); 493 } 494 #else 495 package.FreeBootFields(); 496 #endif //MAC 497 498 if (javaLibrary.JavaVMCreate(argc, argv.GetData()) == true) { 499 return true; 500 } 501 502 for (index = 0; index < argc; index++) { 503 if (argv[index] != NULL) { 504 delete[] argv[index]; 505 } 506 } 507 508 return false; 509 } 510 #endif //USE_JLI_LAUNCH 511 512 void JavaVirtualMachine::ShutdownJVM() { 513 #ifndef USE_JLI_LAUNCH 514 if (FJvm != NULL) { 515 // If application main() exits quickly but application is run on some other thread 516 // (e.g. Swing app performs invokeLater() in main and exits) 517 // then if we return execution to tWinMain it will exit. 518 // This will cause process to exit and application will not actually run. 519 // 520 // To avoid this we are trying to detach jvm from current thread (java.exe does the same) 521 // Because we are doing this on the main JVM thread (i.e. one that was used to create JVM) 522 // this call will spawn "Destroy Java VM" java thread that will shut JVM once there are 523 // no non-daemon threads running, and then return control here. 524 // I.e. this will happen when EDT and other app thread will exit. 525 if (FJvm->DetachCurrentThread() != JNI_OK) { 526 Platform& platform = Platform::GetInstance(); 527 platform.ShowMessage(_T("Detach failed.")); 528 } 529 530 FJvm->DestroyJavaVM(); 531 } 532 #endif //USE_JLI_LAUNCH 533 }