<commit_before>/*************************************************************************/
/*  gd_mono.cpp                                                          */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                      https://godotengine.org                          */
/*************************************************************************/
/* Copyright (c) 2007-2022 <NAME>, <NAME>.                 */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/

#include "gd_mono.h"

#include <mono/metadata/environment.h>
#include <mono/metadata/exception.h>
#include <mono/metadata/mono-config.h>
#include <mono/metadata/mono-debug.h>
#include <mono/metadata/mono-gc.h>
#include <mono/metadata/profiler.h>

#include "core/os/dir_access.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
#include "core/os/thread.h"
#include "core/project_settings.h"

#include "../csharp_script.h"
#include "../godotsharp_dirs.h"
#include "../utils/path_utils.h"
#include "gd_mono_cache.h"
#include "gd_mono_class.h"
#include "gd_mono_marshal.h"
#include "gd_mono_utils.h"

#ifdef TOOLS_ENABLED
#include "main/main.h"
#endif

#ifdef ANDROID_ENABLED
#include "android_mono_config.h"
#include "support/android_support.h"
#elif defined(IPHONE_ENABLED)
#include "support/ios_support.h"
#endif

#if defined(TOOL_ENABLED) && defined(GD_MONO_SINGLE_APPDOMAIN)
// This will no longer be the case if we replace appdomains with AssemblyLoadContext
#error "Editor build requires support for multiple appdomains"
#endif

#if defined(GD_MONO_HOT_RELOAD) && defined(GD_MONO_SINGLE_APPDOMAIN)
#error "Hot reloading requires multiple appdomains"
#endif

// TODO:
// This has turn into a gigantic mess. There's too much going on here. Too much #ifdef as well.
// It's just painful to read... It needs to be re-structured. Please, clean this up, future me.

GDMono *GDMono::singleton = NULL;

namespace {

#if defined(JAVASCRIPT_ENABLED)
extern "C" {
void mono_wasm_load_runtime(const char *managed_path, int enable_debugging);
}
#endif

#if !defined(JAVASCRIPT_ENABLED)

void gd_mono_setup_runtime_main_args() {
	CharString execpath = OS::get_singleton()->get_executable_path().utf8();

	List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();

	List<CharString> cmdline_args_utf8;
	Vector<char *> main_args;
	main_args.resize(cmdline_args.size() + 1);

	main_args.write[0] = execpath.ptrw();

	int i = 1;
	for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) {
		CharString &stored = cmdline_args_utf8.push_back(E->get().utf8())->get();
		main_args.write[i] = stored.ptrw();
		i++;
	}

	mono_runtime_set_main_args(main_args.size(), main_args.ptrw());
}

void gd_mono_profiler_init() {
	String profiler_args = GLOBAL_DEF("mono/profiler/args", "log:calls,alloc,sample,output=output.mlpd");
	bool profiler_enabled = GLOBAL_DEF("mono/profiler/enabled", false);
	if (profiler_enabled) {
		mono_profiler_load(profiler_args.utf8());
		return;
	}

	const String env_var_name = "MONO_ENV_OPTIONS";
	if (OS::get_singleton()->has_environment(env_var_name)) {
		const auto mono_env_ops = OS::get_singleton()->get_environment(env_var_name);
		// Usually MONO_ENV_OPTIONS looks like:   --profile=jb:prof=timeline,ctl=remote,host=127.0.0.1:55467
		const String prefix = "--profile=";
		if (mono_env_ops.begins_with(prefix)) {
			const auto ops = mono_env_ops.substr(prefix.length(), mono_env_ops.length());
			mono_profiler_load(ops.utf8());
		}
	}
}

void gd_mono_debug_init() {
	CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();

	if (da_args.length()) {
		// Clear to avoid passing it to child processes
		OS::get_singleton()->set_environment("GODOT_MONO_DEBUGGER_AGENT", String());
	} else {
		// Try with command line arguments. This is useful on platforms where it's difficult to pass
		// environment variables. The command line arguments can be specified in the export options.

		String da_cmdline_arg;
		List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();

		for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) {
			const String &arg = E->get();

			if (arg.begins_with("--mono-debugger-agent=")) {
				da_cmdline_arg = arg;
				break;
			}
		}

		if (da_cmdline_arg.length()) {
			da_cmdline_arg.replace_first("--mono-debugger-agent=", "--debugger-agent=");
			da_args = da_cmdline_arg.utf8();
		}
	}

#ifdef TOOLS_ENABLED
	if (Engine::get_singleton()->is_editor_hint() ||
			ProjectSettings::get_singleton()->get_resource_path().empty() ||
			Main::is_project_manager()) {
		if (da_args.size() == 0)
			return;
	}

	if (da_args.length() == 0) {
		// Use project settings defaults for the editor player

		int da_port = GLOBAL_DEF("mono/debugger_agent/port", 23685);
		bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false);
		int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000);

		da_args = String("--debugger-agent=transport=dt_socket,address=127.0.0.1:" + itos(da_port) +
				",embedding=1,server=y,suspend=" + (da_suspend ? "y,timeout=" + itos(da_timeout) : "n"))
						  .utf8();
	}
#else
	if (da_args.length() == 0)
		return; // Exported games don't use the project settings to setup the debugger agent
#endif

	// Debugging enabled

	mono_debug_init(MONO_DEBUG_FORMAT_MONO);

	// --debugger-agent=help<commit_msg>C#: Fix debugger options missing from Project Settings<commit_after>/*************************************************************************/
/*  gd_mono.cpp                                                          */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                      https://godotengine.org                          */
/*************************************************************************/
/* Copyright (c) 2007-2022 <NAME>, <NAME>.                 */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/

#include "gd_mono.h"

#include <mono/metadata/environment.h>
#include <mono/metadata/exception.h>
#include <mono/metadata/mono-config.h>
#include <mono/metadata/mono-debug.h>
#include <mono/metadata/mono-gc.h>
#include <mono/metadata/profiler.h>

#include "core/os/dir_access.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
#include "core/os/thread.h"
#include "core/project_settings.h"

#include "../csharp_script.h"
#include "../godotsharp_dirs.h"
#include "../utils/path_utils.h"
#include "gd_mono_cache.h"
#include "gd_mono_class.h"
#include "gd_mono_marshal.h"
#include "gd_mono_utils.h"

#ifdef TOOLS_ENABLED
#include "main/main.h"
#endif

#ifdef ANDROID_ENABLED
#include "android_mono_config.h"
#include "support/android_support.h"
#elif defined(IPHONE_ENABLED)
#include "support/ios_support.h"
#endif

#if defined(TOOL_ENABLED) && defined(GD_MONO_SINGLE_APPDOMAIN)
// This will no longer be the case if we replace appdomains with AssemblyLoadContext
#error "Editor build requires support for multiple appdomains"
#endif

#if defined(GD_MONO_HOT_RELOAD) && defined(GD_MONO_SINGLE_APPDOMAIN)
#error "Hot reloading requires multiple appdomains"
#endif

// TODO:
// This has turn into a gigantic mess. There's too much going on here. Too much #ifdef as well.
// It's just painful to read... It needs to be re-structured. Please, clean this up, future me.

GDMono *GDMono::singleton = NULL;

namespace {

#if defined(JAVASCRIPT_ENABLED)
extern "C" {
void mono_wasm_load_runtime(const char *managed_path, int enable_debugging);
}
#endif

#if !defined(JAVASCRIPT_ENABLED)

void gd_mono_setup_runtime_main_args() {
	CharString execpath = OS::get_singleton()->get_executable_path().utf8();

	List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();

	List<CharString> cmdline_args_utf8;
	Vector<char *> main_args;
	main_args.resize(cmdline_args.size() + 1);

	main_args.write[0] = execpath.ptrw();

	int i = 1;
	for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) {
		CharString &stored = cmdline_args_utf8.push_back(E->get().utf8())->get();
		main_args.write[i] = stored.ptrw();
		i++;
	}

	mono_runtime_set_main_args(main_args.size(), main_args.ptrw());
}

void gd_mono_profiler_init() {
	String profiler_args = GLOBAL_DEF("mono/profiler/args", "log:calls,alloc,sample,output=output.mlpd");
	bool profiler_enabled = GLOBAL_DEF("mono/profiler/enabled", false);
	if (profiler_enabled) {
		mono_profiler_load(profiler_args.utf8());
		return;
	}

	const String env_var_name = "MONO_ENV_OPTIONS";
	if (OS::get_singleton()->has_environment(env_var_name)) {
		const auto mono_env_ops = OS::get_singleton()->get_environment(env_var_name);
		// Usually MONO_ENV_OPTIONS looks like:   --profile=jb:prof=timeline,ctl=remote,host=127.0.0.1:55467
		const String prefix = "--profile=";
		if (mono_env_ops.begins_with(prefix)) {
			const auto ops = mono_env_ops.substr(prefix.length(), mono_env_ops.length());
			mono_profiler_load(ops.utf8());
		}
	}
}

void gd_mono_debug_init() {
	CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();

	if (da_args.length()) {
		// Clear to avoid passing it to child processes
		OS::get_singleton()->set_environment("GODOT_MONO_DEBUGGER_AGENT", String());
	} else {
		// Try with command line arguments. This is useful on platforms where it's difficult to pass
		// environment variables. The command line arguments can be specified in the export options.

		String da_cmdline_arg;
		List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();

		for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) {
			const String &arg = E->get();

			if (arg.begins_with("--mono-debugger-agent=")) {
				da_cmdline_arg = arg;
				break;
			}
		}

		if (da_cmdline_arg.length()) {
			da_cmdline_arg.replace_first("--mono-debugger-agent=", "--debugger-agent=");
			da_args = da_cmdline_arg.utf8();
		}
	}

#ifdef TOOLS_ENABLED
	// Define debugger_agent settings even if we end up ignoring the current values. This makes sure
	// they show up in Editor Settings.
	int da_port = GLOBAL_DEF("mono/debugger_agent/port", 23685);
	bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false);
	int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000);

	if (Engine::get_singleton()->is_editor_hint() ||
			ProjectSettings::get_singleton()->get_resource_path().empty() ||
			Main::is_project_manager()) {
		if (da_args.size() == 0)
			return;
	}

	if (da_args.length() == 0) {
		// Use project settings defaults for the editor player

		da_args = String("--debugger-agent=transport=dt_socket,address=127.0.0.1:" + itos(da_port) +
				",embedding=1,server=y,suspend=" + (da_suspend ? "y,timeout=" + itos(da_timeout) : "n"))
						  .utf8();
	}
#else
	if (da_args.length() == 0)
		return; // Exported games don't use the project settings to setup the debugger agent
#endif

	// Debugging enabled

	mono_debug_init(MONO_DEBUG_FORMAT_MONO);

	// --debugger-agent=help
{'fraction_non_alphanumeric': 0.164356849362769, 'fraction_numerical': 0.008078557002576781, 'mean_word_length': 3.362089914945322, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 59, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7127912', 'n_tokens_mistral': 4722, 'n_tokens_neox': 4451, 'n_words': 1103}