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 }