gd_mono_assembly.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. /*************************************************************************/
  2. /* gd_mono_assembly.cpp */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /*************************************************************************/
  30. #include "gd_mono_assembly.h"
  31. #include <mono/metadata/mono-debug.h>
  32. #include <mono/metadata/tokentype.h>
  33. #include "core/list.h"
  34. #include "core/os/file_access.h"
  35. #include "core/os/os.h"
  36. #include "core/project_settings.h"
  37. #include "../godotsharp_dirs.h"
  38. #include "gd_mono_class.h"
  39. bool GDMonoAssembly::no_search = false;
  40. bool GDMonoAssembly::in_preload = false;
  41. Vector<String> GDMonoAssembly::search_dirs;
  42. void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config) {
  43. const char *rootdir = mono_assembly_getrootdir();
  44. if (rootdir) {
  45. String framework_dir = String(rootdir).plus_file("mono").plus_file("4.5");
  46. r_search_dirs.push_back(framework_dir);
  47. r_search_dirs.push_back(framework_dir.plus_file("Facades"));
  48. }
  49. if (p_custom_config.length()) {
  50. r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(p_custom_config));
  51. } else {
  52. r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir());
  53. }
  54. r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir());
  55. r_search_dirs.push_back(OS::get_singleton()->get_resource_dir());
  56. r_search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir());
  57. #ifdef TOOLS_ENABLED
  58. r_search_dirs.push_back(GodotSharpDirs::get_data_editor_tools_dir());
  59. #endif
  60. }
  61. void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) {
  62. if (no_search)
  63. return;
  64. // If our search and preload hooks fail to load the assembly themselves, the mono runtime still might.
  65. // Just do Assembly.LoadFrom("/Full/Path/On/Disk.dll");
  66. // In this case, we wouldn't have the assembly known in GDMono, which causes crashes
  67. // if any class inside the assembly is looked up by Godot.
  68. // And causing a lookup like that is as easy as throwing an exception defined in it...
  69. // No, we can't make the assembly load hooks smart enough because they get passed a MonoAssemblyName* only,
  70. // not the disk path passed to say Assembly.LoadFrom().
  71. _wrap_mono_assembly(assembly);
  72. }
  73. MonoAssembly *GDMonoAssembly::assembly_search_hook(MonoAssemblyName *aname, void *user_data) {
  74. return GDMonoAssembly::_search_hook(aname, user_data, false);
  75. }
  76. MonoAssembly *GDMonoAssembly::assembly_refonly_search_hook(MonoAssemblyName *aname, void *user_data) {
  77. return GDMonoAssembly::_search_hook(aname, user_data, true);
  78. }
  79. MonoAssembly *GDMonoAssembly::assembly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data) {
  80. return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, false);
  81. }
  82. MonoAssembly *GDMonoAssembly::assembly_refonly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data) {
  83. return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, true);
  84. }
  85. MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly) {
  86. (void)user_data; // UNUSED
  87. String name = mono_assembly_name_get_name(aname);
  88. bool has_extension = name.ends_with(".dll") || name.ends_with(".exe");
  89. if (no_search)
  90. return NULL;
  91. GDMonoAssembly **loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name);
  92. if (loaded_asm)
  93. return (*loaded_asm)->get_assembly();
  94. no_search = true; // Avoid the recursion madness
  95. GDMonoAssembly *res = _load_assembly_search(name, search_dirs, refonly);
  96. no_search = false;
  97. return res ? res->get_assembly() : NULL;
  98. }
  99. static _THREAD_LOCAL_(MonoImage *) image_corlib_loading = NULL;
  100. MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) {
  101. (void)user_data; // UNUSED
  102. if (search_dirs.empty()) {
  103. fill_search_dirs(search_dirs);
  104. }
  105. {
  106. // If we find the assembly here, we load it with `mono_assembly_load_from_full`,
  107. // which in turn invokes load hooks before returning the MonoAssembly to us.
  108. // One of the load hooks is `load_aot_module`. This hook can end up calling preload hooks
  109. // again for the same assembly in certain in certain circumstances (the `do_load_image` part).
  110. // If this is the case and we return NULL due to the no_search condition below,
  111. // it will result in an internal crash later on. Therefore we need to return the assembly we didn't
  112. // get yet from `mono_assembly_load_from_full`. Luckily we have the image, which already got it.
  113. // This must be done here. If done in search hooks, it would cause `mono_assembly_load_from_full`
  114. // to think another MonoAssembly for this assembly was already loaded, making it delete its own,
  115. // when in fact both pointers were the same... This hooks thing is confusing.
  116. if (image_corlib_loading) {
  117. return mono_image_get_assembly(image_corlib_loading);
  118. }
  119. }
  120. if (no_search)
  121. return NULL;
  122. no_search = true;
  123. in_preload = true;
  124. String name = mono_assembly_name_get_name(aname);
  125. bool has_extension = name.ends_with(".dll");
  126. GDMonoAssembly *res = NULL;
  127. if (has_extension ? name == "mscorlib.dll" : name == "mscorlib") {
  128. GDMonoAssembly **stored_assembly = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name);
  129. if (stored_assembly)
  130. return (*stored_assembly)->get_assembly();
  131. res = _load_assembly_search("mscorlib.dll", search_dirs, refonly);
  132. }
  133. no_search = false;
  134. in_preload = false;
  135. return res ? res->get_assembly() : NULL;
  136. }
  137. GDMonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) {
  138. GDMonoAssembly *res = NULL;
  139. String path;
  140. bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe");
  141. for (int i = 0; i < p_search_dirs.size(); i++) {
  142. const String &search_dir = p_search_dirs[i];
  143. if (has_extension) {
  144. path = search_dir.plus_file(p_name);
  145. if (FileAccess::exists(path)) {
  146. res = _load_assembly_from(p_name.get_basename(), path, p_refonly);
  147. if (res != NULL)
  148. return res;
  149. }
  150. } else {
  151. path = search_dir.plus_file(p_name + ".dll");
  152. if (FileAccess::exists(path)) {
  153. res = _load_assembly_from(p_name, path, p_refonly);
  154. if (res != NULL)
  155. return res;
  156. }
  157. path = search_dir.plus_file(p_name + ".exe");
  158. if (FileAccess::exists(path)) {
  159. res = _load_assembly_from(p_name, path, p_refonly);
  160. if (res != NULL)
  161. return res;
  162. }
  163. }
  164. }
  165. return NULL;
  166. }
  167. GDMonoAssembly *GDMonoAssembly::_load_assembly_from(const String &p_name, const String &p_path, bool p_refonly) {
  168. GDMonoAssembly *assembly = memnew(GDMonoAssembly(p_name, p_path));
  169. Error err = assembly->load(p_refonly);
  170. if (err != OK) {
  171. memdelete(assembly);
  172. ERR_FAIL_V(NULL);
  173. }
  174. MonoDomain *domain = mono_domain_get();
  175. GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, assembly);
  176. return assembly;
  177. }
  178. void GDMonoAssembly::_wrap_mono_assembly(MonoAssembly *assembly) {
  179. String name = mono_assembly_name_get_name(mono_assembly_get_name(assembly));
  180. MonoImage *image = mono_assembly_get_image(assembly);
  181. GDMonoAssembly *gdassembly = memnew(GDMonoAssembly(name, mono_image_get_filename(image)));
  182. Error err = gdassembly->wrapper_for_image(image);
  183. if (err != OK) {
  184. memdelete(gdassembly);
  185. ERR_FAIL();
  186. }
  187. MonoDomain *domain = mono_domain_get();
  188. GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, gdassembly);
  189. }
  190. void GDMonoAssembly::initialize() {
  191. mono_install_assembly_search_hook(&assembly_search_hook, NULL);
  192. mono_install_assembly_refonly_search_hook(&assembly_refonly_search_hook, NULL);
  193. mono_install_assembly_preload_hook(&assembly_preload_hook, NULL);
  194. mono_install_assembly_refonly_preload_hook(&assembly_refonly_preload_hook, NULL);
  195. mono_install_assembly_load_hook(&assembly_load_hook, NULL);
  196. }
  197. Error GDMonoAssembly::load(bool p_refonly) {
  198. ERR_FAIL_COND_V(loaded, ERR_FILE_ALREADY_IN_USE);
  199. refonly = p_refonly;
  200. uint64_t last_modified_time = FileAccess::get_modified_time(path);
  201. Vector<uint8_t> data = FileAccess::get_file_as_array(path);
  202. ERR_FAIL_COND_V(data.empty(), ERR_FILE_CANT_READ);
  203. String image_filename = ProjectSettings::get_singleton()->globalize_path(path);
  204. MonoImageOpenStatus status = MONO_IMAGE_OK;
  205. image = mono_image_open_from_data_with_name(
  206. (char *)&data[0], data.size(),
  207. true, &status, refonly,
  208. image_filename.utf8().get_data());
  209. ERR_FAIL_COND_V(status != MONO_IMAGE_OK, ERR_FILE_CANT_OPEN);
  210. ERR_FAIL_NULL_V(image, ERR_FILE_CANT_OPEN);
  211. #ifdef DEBUG_ENABLED
  212. String pdb_path(path + ".pdb");
  213. if (!FileAccess::exists(pdb_path)) {
  214. pdb_path = path.get_basename() + ".pdb"; // without .dll
  215. if (!FileAccess::exists(pdb_path))
  216. goto no_pdb;
  217. }
  218. pdb_data.clear();
  219. pdb_data = FileAccess::get_file_as_array(pdb_path);
  220. mono_debug_open_image_from_memory(image, &pdb_data[0], pdb_data.size());
  221. no_pdb:
  222. #endif
  223. bool is_corlib_preload = in_preload && name == "mscorlib";
  224. if (is_corlib_preload)
  225. image_corlib_loading = image;
  226. assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, refonly);
  227. if (is_corlib_preload)
  228. image_corlib_loading = NULL;
  229. ERR_FAIL_COND_V(status != MONO_IMAGE_OK || assembly == NULL, ERR_FILE_CANT_OPEN);
  230. loaded = true;
  231. modified_time = last_modified_time;
  232. return OK;
  233. }
  234. Error GDMonoAssembly::wrapper_for_image(MonoImage *p_image) {
  235. ERR_FAIL_COND_V(loaded, ERR_FILE_ALREADY_IN_USE);
  236. assembly = mono_image_get_assembly(p_image);
  237. ERR_FAIL_NULL_V(assembly, FAILED);
  238. image = p_image;
  239. mono_image_addref(image);
  240. loaded = true;
  241. return OK;
  242. }
  243. void GDMonoAssembly::unload() {
  244. ERR_FAIL_COND(!loaded);
  245. #ifdef DEBUG_ENABLED
  246. if (pdb_data.size()) {
  247. mono_debug_close_image(image);
  248. pdb_data.clear();
  249. }
  250. #endif
  251. for (Map<MonoClass *, GDMonoClass *>::Element *E = cached_raw.front(); E; E = E->next()) {
  252. memdelete(E->value());
  253. }
  254. cached_classes.clear();
  255. cached_raw.clear();
  256. mono_image_close(image);
  257. assembly = NULL;
  258. image = NULL;
  259. loaded = false;
  260. }
  261. GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) {
  262. ERR_FAIL_COND_V(!loaded, NULL);
  263. ClassKey key(p_namespace, p_name);
  264. GDMonoClass **match = cached_classes.getptr(key);
  265. if (match)
  266. return *match;
  267. MonoClass *mono_class = mono_class_from_name(image, String(p_namespace).utf8(), String(p_name).utf8());
  268. if (!mono_class)
  269. return NULL;
  270. GDMonoClass *wrapped_class = memnew(GDMonoClass(p_namespace, p_name, mono_class, this));
  271. cached_classes[key] = wrapped_class;
  272. cached_raw[mono_class] = wrapped_class;
  273. return wrapped_class;
  274. }
  275. GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) {
  276. ERR_FAIL_COND_V(!loaded, NULL);
  277. Map<MonoClass *, GDMonoClass *>::Element *match = cached_raw.find(p_mono_class);
  278. if (match)
  279. return match->value();
  280. StringName namespace_name = mono_class_get_namespace(p_mono_class);
  281. StringName class_name = mono_class_get_name(p_mono_class);
  282. GDMonoClass *wrapped_class = memnew(GDMonoClass(namespace_name, class_name, p_mono_class, this));
  283. cached_classes[ClassKey(namespace_name, class_name)] = wrapped_class;
  284. cached_raw[p_mono_class] = wrapped_class;
  285. return wrapped_class;
  286. }
  287. GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) {
  288. GDMonoClass *match = NULL;
  289. if (gdobject_class_cache_updated) {
  290. Map<StringName, GDMonoClass *>::Element *result = gdobject_class_cache.find(p_class);
  291. if (result)
  292. match = result->get();
  293. } else {
  294. List<GDMonoClass *> nested_classes;
  295. int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF);
  296. for (int i = 1; i < rows; i++) {
  297. MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF);
  298. if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class))
  299. continue;
  300. GDMonoClass *current = get_class(mono_class);
  301. if (!current)
  302. continue;
  303. nested_classes.push_back(current);
  304. if (!match && current->get_name() == p_class)
  305. match = current;
  306. while (!nested_classes.empty()) {
  307. GDMonoClass *current_nested = nested_classes.front()->get();
  308. nested_classes.pop_back();
  309. void *iter = NULL;
  310. while (true) {
  311. MonoClass *raw_nested = mono_class_get_nested_types(current_nested->get_mono_ptr(), &iter);
  312. if (!raw_nested)
  313. break;
  314. GDMonoClass *nested_class = get_class(raw_nested);
  315. if (nested_class) {
  316. gdobject_class_cache.insert(nested_class->get_name(), nested_class);
  317. nested_classes.push_back(nested_class);
  318. }
  319. }
  320. }
  321. gdobject_class_cache.insert(current->get_name(), current);
  322. }
  323. gdobject_class_cache_updated = true;
  324. }
  325. return match;
  326. }
  327. GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) {
  328. GDMonoAssembly **loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
  329. if (loaded_asm)
  330. return *loaded_asm;
  331. #ifdef DEBUG_ENABLED
  332. CRASH_COND(!FileAccess::exists(p_path));
  333. #endif
  334. no_search = true;
  335. GDMonoAssembly *res = _load_assembly_from(p_name, p_path, p_refonly);
  336. no_search = false;
  337. return res;
  338. }
  339. GDMonoAssembly::GDMonoAssembly(const String &p_name, const String &p_path) {
  340. loaded = false;
  341. gdobject_class_cache_updated = false;
  342. name = p_name;
  343. path = p_path;
  344. refonly = false;
  345. modified_time = 0;
  346. assembly = NULL;
  347. image = NULL;
  348. }
  349. GDMonoAssembly::~GDMonoAssembly() {
  350. if (loaded)
  351. unload();
  352. }