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 // Private typedef for function pointer casting 52 #ifndef USE_JLI_LAUNCH 53 #define LAUNCH_FUNC "JNI_CreateJavaVM" 54 typedef jint (JNICALL *JVM_CREATE)(JavaVM ** jvm, JNIEnv ** env, void *); 55 #else 56 #define LAUNCH_FUNC "JLI_Launch" 57 typedef int (JNICALL *JVM_CREATE)(int argc, char ** argv, 58 int jargc, const char** jargv, 59 int appclassc, const char** appclassv, 60 const char* fullversion, 61 const char* dotversion, 62 const char* pname, 63 const char* lname, 64 jboolean javaargs, 65 jboolean cpwildcard, 66 jboolean javaw, 67 jint ergo); 68 #endif //USE_JLI_LAUNCH 69 70 class JavaLibrary : public Library { 71 JVM_CREATE FCreateProc; 72 73 JavaLibrary(const TString &FileName); 74 75 public: 76 JavaLibrary() : Library() { 77 FCreateProc = NULL; 78 } 79 80 #ifndef USE_JLI_LAUNCH 81 bool JavaVMCreate(JavaVM** jvm, JNIEnv** env, void* jvmArgs) { 82 bool result = true; 83 84 if (FCreateProc == NULL) { 85 FCreateProc = (JVM_CREATE)GetProcAddress(LAUNCH_FUNC); 86 } 87 88 if (FCreateProc == NULL) { 89 Platform& platform = Platform::GetInstance(); 90 Messages& messages = Messages::GetInstance(); 91 platform.ShowMessage(messages.GetMessage(FAILED_LOCATING_JVM_ENTRY_POINT)); 92 return false; 93 } 94 95 if ((*FCreateProc)(jvm, env, jvmArgs) < 0) { 96 Platform& platform = Platform::GetInstance(); 97 Messages& messages = Messages::GetInstance(); 98 platform.ShowMessage(messages.GetMessage(FAILED_CREATING_JVM)); 99 return false; 100 } 101 102 return result; 103 } 104 #else 105 bool JavaVMCreate(size_t argc, char *argv[]) { 106 if (FCreateProc == NULL) { 107 FCreateProc = (JVM_CREATE)GetProcAddress(LAUNCH_FUNC); 108 } 109 110 if (FCreateProc == NULL) { 111 Platform& platform = Platform::GetInstance(); 112 Messages& messages = Messages::GetInstance(); 113 platform.ShowMessage(messages.GetMessage(FAILED_LOCATING_JVM_ENTRY_POINT)); 114 return false; 115 } 116 117 return FCreateProc((int)argc, argv, 118 0, NULL, 119 0, NULL, 120 "", 121 "", 122 "java", 123 "java", 124 false, 125 false, 126 false, 127 0) == 0; 128 } 129 #endif //USE_JLI_LAUNCH 130 }; 131 132 #ifndef USE_JLI_LAUNCH 133 //debug hook to print JVM messages into console. 134 static jint JNICALL vfprintfHook(FILE *fp, const char *format, va_list args) { 135 #ifdef WINDOWS 136 char buffer[20480]; 137 int len; 138 HANDLE hConsole; 139 DWORD wasWritten; 140 141 len = _vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer), format, args); 142 143 if (len <= 0) { 144 return len; 145 } 146 147 hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 148 149 if (hConsole == INVALID_HANDLE_VALUE) { 150 return false; 151 } 152 153 //JVM will always pass us ASCII 154 WriteConsoleA(hConsole, buffer, strlen(buffer), &wasWritten, NULL); 155 156 return (jint) len; 157 #endif //WINDOWS 158 #ifdef LINUX 159 return 0; 160 #endif //LINUX 161 } 162 #endif //USE_JLI_LAUNCH 163 164 //-------------------------------------------------------------------------------------------------- 165 166 struct JavaOptionItem { 167 TString name; 168 TString value; 169 void* extraInfo; 170 }; 171 172 173 class JavaOptions { 174 private: 175 std::list<JavaOptionItem> FItems; 176 JavaVMOption* FOptions; 177 178 public: 179 JavaOptions() { 180 FOptions = NULL; 181 182 #ifndef USE_JLI_LAUNCH 183 #ifdef DEBUG 184 Platform& platform = Platform::GetInstance(); 185 186 if (platform.GetDebugState() == dsNative) { 187 AppendValue(_T("vfprintf"), _T(""), (void*)vfprintfHook); 188 } 189 #endif //DEBUG 190 #endif //USE_JLI_LAUNCH 191 } 192 193 ~JavaOptions() { 194 if (FOptions != NULL) { 195 for (unsigned int index = 0; index < GetCount(); index++) { 196 delete[] FOptions[index].optionString; 197 } 198 199 delete[] FOptions; 200 } 201 } 202 203 void AppendValue(const TString Key, TString Value) { 204 AppendValue(Key, Value, NULL); 205 } 206 207 void AppendValue(const TString Key, TString Value, void* Extra) { 208 JavaOptionItem item; 209 item.name = Key; 210 item.value = Value; 211 item.extraInfo = Extra; 212 FItems.push_back(item); 213 } 214 215 void AppendValues(TOrderedMap Values) { 216 std::list<TString> orderedKeys = Helpers::GetOrderedKeysFromMap(Values); 217 218 for (std::list<TString>::const_iterator iterator = orderedKeys.begin(); iterator != orderedKeys.end(); iterator++) { 219 TString name = *iterator; 220 TValueIndex value = Values[name]; 221 AppendValue(name, value.value); 222 } 223 } 224 225 void ReplaceValue(const TString Key, TString Value) { 226 for (std::list<JavaOptionItem>::iterator iterator = FItems.begin(); 227 iterator != FItems.end(); iterator++) { 228 229 TString lkey = iterator->name; 230 231 if (lkey == Key) { 232 JavaOptionItem item = *iterator; 233 item.value = Value; 234 iterator = FItems.erase(iterator); 235 FItems.insert(iterator, item); 236 break; 237 } 238 } 239 } 240 241 #ifndef USE_JLI_LAUNCH 242 JavaVMOption* ToJavaOptions() { 243 FOptions = new JavaVMOption[FItems.size()]; 244 memset(FOptions, 0, sizeof(JavaVMOption) * FItems.size()); 245 Macros& macros = Macros::GetInstance(); 246 unsigned int index = 0; 247 248 for (std::list<JavaOptionItem>::const_iterator iterator = FItems.begin(); 249 iterator != FItems.end(); iterator++) { 250 TString key = iterator->name; 251 TString value = iterator->value; 252 TString option = Helpers::NameValueToString(key, value); 253 option = macros.ExpandMacros(option); 254 #ifdef DEBUG 255 printf("%s\n", PlatformString(option).c_str()); 256 #endif //DEBUG 257 FOptions[index].optionString = PlatformString::duplicate(PlatformString(option).c_str()); 258 FOptions[index].extraInfo = iterator->extraInfo; 259 index++; 260 } 261 262 return FOptions; 263 } 264 #else 265 std::list<TString> ToList() { 266 std::list<TString> result; 267 Macros& macros = Macros::GetInstance(); 268 269 for (std::list<JavaOptionItem>::const_iterator iterator = FItems.begin(); 270 iterator != FItems.end(); iterator++) { 271 TString key = iterator->name; 272 TString value = iterator->value; 273 TString option = Helpers::NameValueToString(key, value); 274 option = macros.ExpandMacros(option); 275 result.push_back(option); 276 } 277 278 return result; 279 } 280 #endif //USE_JLI_LAUNCH 281 282 size_t GetCount() { 283 return FItems.size(); 284 } 285 }; 286 287 // jvmuserargs can have a trailing equals in the key. This needs to be removed to use 288 // other parts of the launcher. 289 TOrderedMap RemoveTrailingEquals(TOrderedMap Map) { 290 TOrderedMap result; 291 292 for (TOrderedMap::const_iterator iterator = Map.begin(); iterator != Map.end(); iterator++) { 293 TString name = iterator->first; 294 TValueIndex value = iterator->second; 295 296 // If the last character of the key is an equals, then remove it. If there is no 297 // equals then combine the two as a key. 298 TString::iterator i = name.end(); 299 i--; 300 301 if (*i == '=') { 302 name = name.substr(0, name.size() - 1); 303 } 304 else { 305 i = value.value.begin(); 306 307 if (*i == '=') { 308 value.value = value.value.substr(1, value.value.size() - 1); 309 } 310 else { 311 name = name + value.value; 312 value.value = _T(""); 313 } 314 } 315 316 result.insert(TOrderedMap::value_type(name, value)); 317 } 318 319 return result; 320 } 321 322 //-------------------------------------------------------------------------------------------------- 323 324 JavaVirtualMachine::JavaVirtualMachine() { 325 #ifndef USE_JLI_LAUNCH 326 FEnv = NULL; 327 FJvm = NULL; 328 #endif //USE_JLI_LAUNCH 329 } 330 331 JavaVirtualMachine::~JavaVirtualMachine(void) { 332 } 333 334 bool JavaVirtualMachine::StartJVM() { 335 Platform& platform = Platform::GetInstance(); 336 Package& package = Package::GetInstance(); 337 338 TString classpath = package.GetClassPath(); 339 340 JavaOptions options; 341 options.AppendValue(_T("-Djava.class.path"), classpath); 342 options.AppendValue(_T("-Djava.library.path"), package.GetPackageAppDirectory() + FilePath::PathSeparator() + package.GetPackageLauncherDirectory()); 343 options.AppendValue(_T("-Djava.launcher.path"), package.GetPackageLauncherDirectory()); 344 options.AppendValue(_T("-Dapp.preferences.id"), package.GetAppID()); 345 options.AppendValues(package.GetJVMArgs()); 346 options.AppendValues(RemoveTrailingEquals(package.GetJVMUserArgs())); 347 348 #ifdef DEBUG 349 if (package.Debugging() == dsJava) { 350 options.AppendValue(_T("-Xdebug"), _T("")); 351 options.AppendValue(_T("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=localhost:8000"), _T("")); 352 platform.ShowMessage(_T("localhost:8000")); 353 } 354 #endif //DEBUG 355 356 TString maxHeapSizeOption; 357 TString minHeapSizeOption; 358 359 360 if (package.GetMemoryState() == PackageBootFields::msAuto) { 361 TPlatformNumber memorySize = package.GetMemorySize(); 362 TString memory = PlatformString((size_t)memorySize).toString() + _T("m"); 363 maxHeapSizeOption = TString(_T("-Xmx")) + memory; 364 options.AppendValue(maxHeapSizeOption, _T("")); 365 366 if (memorySize > 256) 367 minHeapSizeOption = _T("-Xms256m"); 368 else 369 minHeapSizeOption = _T("-Xms") + memory; 370 371 options.AppendValue(minHeapSizeOption, _T("")); 372 } 373 374 TString mainClassName = package.GetMainClassName(); 375 376 if (mainClassName.empty() == true) { 377 Messages& messages = Messages::GetInstance(); 378 platform.ShowMessage(messages.GetMessage(NO_MAIN_CLASS_SPECIFIED)); 379 return false; 380 } 381 382 JavaLibrary javaLibrary; 383 javaLibrary.AddDependencies(platform.FilterOutRuntimeDependenciesForPlatform(platform.GetLibraryImports(package.GetJVMLibraryFileName()))); 384 javaLibrary.Load(package.GetJVMLibraryFileName()); 385 386 #ifndef USE_JLI_LAUNCH 387 if (package.HasSplashScreen() == true) { 388 options.AppendValue(TString(_T("-splash:")) + package.GetSplashScreenFileName(), _T("")); 389 } 390 391 // Set up the VM init args 392 JavaVMInitArgs jvmArgs; 393 memset(&jvmArgs, 0, sizeof(JavaVMInitArgs)); 394 jvmArgs.version = JNI_VERSION_1_6; 395 jvmArgs.options = options.ToJavaOptions(); 396 jvmArgs.nOptions = (jint)options.GetCount(); 397 jvmArgs.ignoreUnrecognized = JNI_TRUE; 398 399 if (javaLibrary.JavaVMCreate(&FJvm, &FEnv, &jvmArgs) == true) { 400 try { 401 JavaClass mainClass(FEnv, Helpers::ConvertIdToJavaPath(mainClassName)); 402 JavaStaticMethod mainMethod = mainClass.GetStaticMethod(_T("main"), _T("([Ljava/lang/String;)V")); 403 std::list<TString> appargs = package.GetArgs(); 404 JavaStringArray largs(FEnv, appargs); 405 406 package.FreeBootFields(); 407 408 mainMethod.CallVoidMethod(1, largs.GetData()); 409 return true; 410 } 411 catch (JavaException& exception) { 412 platform.ShowMessage(PlatformString(exception.what()).toString()); 413 return false; 414 } 415 } 416 417 return false; 418 } 419 #else 420 // Initialize the arguments to JLI_Launch() 421 // 422 // On Mac OS X JLI_Launch spawns a new thread that actually starts the JVM. This 423 // new thread simply re-runs main(argc, argv). Therefore we do not want 424 // to add new args if we are still in the original main thread so we 425 // will treat them as command line args provided by the user ... 426 // Only propagate original set of args first time. 427 428 options.AppendValue(Helpers::ConvertPathToId(mainClassName), _T("")); 429 430 std::list<TString> vmargs; 431 vmargs.push_back(package.GetCommandName()); 432 433 // Mac adds a ProcessSerialNumber to args when launched from .app 434 // filter out the psn since they it's not expected in the app 435 if (platform.IsMainThread() == false) { 436 //TODO shows a splash screen, does not work on Windows, and it does not go away and 437 // it hangs the process. 438 if (package.HasSplashScreen() == true) { 439 options.AppendValue(TString(_T("-splash:")) + package.GetSplashScreenFileName(), _T("")); 440 } 441 442 std::list<TString> loptions = options.ToList(); 443 vmargs.splice(vmargs.end(), loptions, loptions.begin(), loptions.end()); 444 } 445 446 std::list<TString> largs = package.GetArgs(); 447 vmargs.splice(vmargs.end(), largs, largs.begin(), largs.end()); 448 size_t argc = vmargs.size(); 449 DynamicBuffer<char*> argv(argc+1); 450 unsigned int index = 0; 451 452 for (std::list<TString>::const_iterator iterator = vmargs.begin(); 453 iterator != vmargs.end(); iterator++) { 454 TString item = *iterator; 455 std::string arg = PlatformString(item).toStdString(); 456 #ifdef DEBUG 457 printf("%i %s\n", index, arg.c_str()); 458 #endif //DEBUG 459 argv[index] = PlatformString::duplicate(arg.c_str()); 460 index++; 461 } 462 463 argv[argc] = NULL; 464 465 // On Mac we can only free the boot fields if the calling thread is not the main thread. 466 #ifdef MAC 467 if (platform.IsMainThread() == false) { 468 package.FreeBootFields(); 469 } 470 #endif //MAC 471 472 if (javaLibrary.JavaVMCreate(argc, argv.GetData()) == true) { 473 return true; 474 } 475 476 for (index = 0; index < argc; index++) { 477 if (argv[index] != NULL) { 478 delete[] argv[index]; 479 } 480 } 481 482 return false; 483 } 484 #endif //USE_JLI_LAUNCH 485 486 void JavaVirtualMachine::ShutdownJVM() { 487 #ifndef USE_JLI_LAUNCH 488 if (FJvm != NULL) { 489 // If application main() exits quickly but application is run on some other thread 490 // (e.g. Swing app performs invokeLater() in main and exits) 491 // then if we return execution to tWinMain it will exit. 492 // This will cause process to exit and application will not actually run. 493 // 494 // To avoid this we are trying to detach jvm from current thread (java.exe does the same) 495 // Because we are doing this on the main JVM thread (i.e. one that was used to create JVM) 496 // this call will spawn "Destroy Java VM" java thread that will shut JVM once there are 497 // no non-daemon threads running, and then return control here. 498 // I.e. this will happen when EDT and other app thread will exit. 499 if (FJvm->DetachCurrentThread() != JNI_OK) { 500 Platform& platform = Platform::GetInstance(); 501 platform.ShowMessage(_T("Detach failed.")); 502 } 503 504 FJvm->DestroyJavaVM(); 505 } 506 #endif //USE_JLI_LAUNCH 507 }