Skip to content

Commit

Permalink
C#/Mono: Check assembly version when loading
Browse files Browse the repository at this point in the history
Not sure if we should check revision too, but this is good enough for what we want.
This will be needed to load the correct Microsoft.Build when we switch to the nuget version.
  • Loading branch information
neikeq committed May 9, 2020
1 parent 61306eb commit af4acb5
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 72 deletions.
15 changes: 11 additions & 4 deletions modules/mono/editor/bindings_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1664,6 +1664,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
}

if (!p_imethod.is_internal) {
// TODO: This alone adds ~0.2 MB of bloat to the core API assembly. It would be
// better to generate a table in the C++ glue instead. That way the strings wouldn't
// add that much extra bloat as they're already used in engine code. Also, it would
// probably be much faster than looking up the attributes when fetching methods.
p_output.append(MEMBER_BEGIN "[GodotMethod(\"");
p_output.append(p_imethod.name);
p_output.append("\")]");
Expand Down Expand Up @@ -2139,7 +2143,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
if (return_type->ret_as_byref_arg) {
p_output.append("\tif (" CS_PARAM_INSTANCE " == nullptr) { *arg_ret = ");
p_output.append(fail_ret);
p_output.append("; ERR_FAIL_MSG(\"Parameter ' arg_ret ' is null.\"); }\n");
p_output.append("; ERR_FAIL_MSG(\"Parameter ' " CS_PARAM_INSTANCE " ' is null.\"); }\n");
} else {
p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE ", ");
p_output.append(fail_ret);
Expand Down Expand Up @@ -2390,6 +2394,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY)
continue;

if (property.name.find("/") >= 0) {
// Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector.
continue;
}

PropertyInterface iprop;
iprop.cname = property.name;
iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname);
Expand All @@ -2402,7 +2411,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {

bool valid = false;
iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid);
ERR_FAIL_COND_V(!valid, false);
ERR_FAIL_COND_V_MSG(!valid, false, "Invalid property: '" + itype.name + "." + String(iprop.cname) + "'.");

iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));

Expand All @@ -2414,8 +2423,6 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
iprop.proxy_name += "_";
}

iprop.proxy_name = iprop.proxy_name.replace("/", "__"); // Some members have a slash...

iprop.prop_doc = nullptr;

for (int i = 0; i < itype.class_doc->properties.size(); i++) {
Expand Down
79 changes: 43 additions & 36 deletions modules/mono/editor/godotsharp_export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,68 +32,70 @@

#include <mono/metadata/image.h>

#include "core/io/file_access_pack.h"
#include "core/os/os.h"
#include "core/project_settings.h"

#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_assembly.h"
#include "../mono_gd/gd_mono_cache.h"
#include "../utils/macros.h"

namespace GodotSharpExport {

String get_assemblyref_name(MonoImage *p_image, int index) {
struct AssemblyRefInfo {
String name;
uint16_t major;
uint16_t minor;
uint16_t build;
uint16_t revision;
};

AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) {
const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);

uint32_t cols[MONO_ASSEMBLYREF_SIZE];

mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE);

return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
return {
String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])),
(uint16_t)cols[MONO_ASSEMBLYREF_MAJOR_VERSION],
(uint16_t)cols[MONO_ASSEMBLYREF_MINOR_VERSION],
(uint16_t)cols[MONO_ASSEMBLYREF_BUILD_NUMBER],
(uint16_t)cols[MONO_ASSEMBLYREF_REV_NUMBER]
};
}

Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) {
MonoImage *image = p_assembly->get_image();

for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
String ref_name = get_assemblyref_name(image, i);
AssemblyRefInfo ref_info = get_assemblyref_name(image, i);

const String &ref_name = ref_info.name;

if (r_assembly_dependencies.has(ref_name))
continue;

GDMonoAssembly *ref_assembly = nullptr;
String path;
bool has_extension = ref_name.ends_with(".dll") || ref_name.ends_with(".exe");

for (int j = 0; j < p_search_dirs.size(); j++) {
const String &search_dir = p_search_dirs[j];

if (has_extension) {
path = search_dir.plus_file(ref_name);
if (FileAccess::exists(path)) {
GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), path, &ref_assembly, true);
if (ref_assembly != nullptr)
break;
}
} else {
path = search_dir.plus_file(ref_name + ".dll");
if (FileAccess::exists(path)) {
GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
if (ref_assembly != nullptr)
break;
}

path = search_dir.plus_file(ref_name + ".exe");
if (FileAccess::exists(path)) {
GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
if (ref_assembly != nullptr)
break;
}
}
}
GDMonoAssembly *ref_assembly = NULL;

{
MonoAssemblyName *ref_aname = mono_assembly_name_new("A"); // We can't allocate an empty MonoAssemblyName, hence "A"
CRASH_COND(ref_aname == nullptr);
SCOPE_EXIT {
mono_assembly_name_free(ref_aname);
mono_free(ref_aname);
};

ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
mono_assembly_get_assemblyref(image, i, ref_aname);

// Use the path we got from the search. Don't try to get the path from the loaded assembly as we can't trust it will be from the selected BCL dir.
r_assembly_dependencies[ref_name] = path;
if (!GDMono::get_singleton()->load_assembly(ref_name, ref_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) {
ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
}

r_assembly_dependencies[ref_name] = ref_assembly->get_path();
}

Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_assembly_dependencies);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'.");
Expand All @@ -113,6 +115,11 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir);

if (p_custom_bcl_dir.length()) {
// Only one mscorlib can be loaded. We need this workaround to make sure we get it from the right BCL directory.
r_assembly_dependencies["mscorlib"] = p_custom_bcl_dir.plus_file("mscorlib.dll").simplify_path();
}

for (const Variant *key = p_initial_assemblies.next(); key; key = p_initial_assemblies.next(key)) {
String assembly_name = *key;
String assembly_path = p_initial_assemblies[*key];
Expand Down
1 change: 1 addition & 0 deletions modules/mono/managed_callable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ CallableCustom::CompareLessFunc ManagedCallable::get_compare_less_func() const {
}

ObjectID ManagedCallable::get_object() const {
// TODO: If the delegate target extends Godot.Object, use that instead!
return CSharpLanguage::get_singleton()->get_managed_callable_middleman()->get_instance_id();
}

Expand Down
31 changes: 17 additions & 14 deletions modules/mono/mono_gd/gd_mono.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -515,8 +515,8 @@ void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) {

GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) {

if (p_name == "mscorlib")
return get_corlib_assembly();
if (p_name == "mscorlib" && corlib_assembly)
return corlib_assembly;

MonoDomain *domain = mono_domain_get();
uint32_t domain_id = domain ? mono_domain_get_id(domain) : 0;
Expand All @@ -526,7 +526,9 @@ GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) {

bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) {

#ifdef DEBUG_ENABLED
CRASH_COND(!r_assembly);
#endif

MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8());
bool result = load_assembly(p_name, aname, r_assembly, p_refonly);
Expand All @@ -538,26 +540,27 @@ bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bo

bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) {

#ifdef DEBUG_ENABLED
CRASH_COND(!r_assembly);
#endif

print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");

MonoImageOpenStatus status = MONO_IMAGE_OK;
MonoAssembly *assembly = mono_assembly_load_full(p_aname, nullptr, &status, p_refonly);
return load_assembly(p_name, p_aname, r_assembly, p_refonly, GDMonoAssembly::get_default_search_dirs());
}

if (!assembly)
return false;
bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs) {

ERR_FAIL_COND_V(status != MONO_IMAGE_OK, false);
#ifdef DEBUG_ENABLED
CRASH_COND(!r_assembly);
#endif

uint32_t domain_id = mono_domain_get_id(mono_domain_get());
print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");

GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name);
GDMonoAssembly *assembly = GDMonoAssembly::load(p_name, p_aname, p_refonly, p_search_dirs);

ERR_FAIL_COND_V(stored_assembly == nullptr, false);
ERR_FAIL_COND_V((*stored_assembly)->get_assembly() != assembly, false);
if (!assembly)
return false;

*r_assembly = *stored_assembly;
*r_assembly = assembly;

print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path());

Expand Down
1 change: 1 addition & 0 deletions modules/mono/mono_gd/gd_mono.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ class GDMono {

bool load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly = false);
bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false);
bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs);
bool load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly = false);

Error finalize_and_unload_domain(MonoDomain *p_domain);
Expand Down
71 changes: 57 additions & 14 deletions modules/mono/mono_gd/gd_mono_assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <mono/metadata/mono-debug.h>
#include <mono/metadata/tokentype.h>

#include "core/io/file_access_pack.h"
#include "core/list.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
Expand Down Expand Up @@ -99,7 +100,7 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin
// - The 'load' hook is called after the assembly has been loaded. Its job is to add the
// assembly to the list of loaded assemblies so that the 'search' hook can look it up.

void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) {
void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, [[maybe_unused]] void *user_data) {

String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly)));

Expand Down Expand Up @@ -133,9 +134,7 @@ MonoAssembly *GDMonoAssembly::assembly_refonly_preload_hook(MonoAssemblyName *an
return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, true);
}

MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly) {

(void)user_data; // UNUSED
MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, [[maybe_unused]] void *user_data, bool refonly) {

String name = String::utf8(mono_assembly_name_get_name(aname));
bool has_extension = name.ends_with(".dll") || name.ends_with(".exe");
Expand All @@ -147,15 +146,13 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d
return nullptr;
}

MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) {

(void)user_data; // UNUSED
MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, [[maybe_unused]] void *user_data, bool refonly) {

String name = String::utf8(mono_assembly_name_get_name(aname));
return _load_assembly_search(name, search_dirs, refonly);
return _load_assembly_search(name, aname, refonly, search_dirs);
}

MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) {
MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {

MonoAssembly *res = nullptr;
String path;
Expand All @@ -168,21 +165,21 @@ MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const
if (has_extension) {
path = search_dir.plus_file(p_name);
if (FileAccess::exists(path)) {
res = _real_load_assembly_from(path, p_refonly);
res = _real_load_assembly_from(path, p_refonly, p_aname);
if (res != nullptr)
return res;
}
} else {
path = search_dir.plus_file(p_name + ".dll");
if (FileAccess::exists(path)) {
res = _real_load_assembly_from(path, p_refonly);
res = _real_load_assembly_from(path, p_refonly, p_aname);
if (res != nullptr)
return res;
}

path = search_dir.plus_file(p_name + ".exe");
if (FileAccess::exists(path)) {
res = _real_load_assembly_from(path, p_refonly);
res = _real_load_assembly_from(path, p_refonly, p_aname);
if (res != nullptr)
return res;
}
Expand Down Expand Up @@ -230,7 +227,7 @@ void GDMonoAssembly::initialize() {
mono_install_assembly_load_hook(&assembly_load_hook, nullptr);
}

MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly) {
MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname) {

Vector<uint8_t> data = FileAccess::get_file_as_array(p_path);
ERR_FAIL_COND_V_MSG(data.empty(), nullptr, "Could read the assembly in the specified location");
Expand All @@ -255,7 +252,33 @@ MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, boo
true, &status, p_refonly,
image_filename.utf8());

ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from the loaded data");
ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from memory: '" + p_path + "'.");

if (p_aname != nullptr) {
// Check assembly version
const MonoTableInfo *table = mono_image_get_table_info(image, MONO_TABLE_ASSEMBLY);

ERR_FAIL_NULL_V(table, nullptr);

if (mono_table_info_get_rows(table)) {
uint32_t cols[MONO_ASSEMBLY_SIZE];
mono_metadata_decode_row(table, 0, cols, MONO_ASSEMBLY_SIZE);

// Not sure about .NET's policy. We will only ensure major and minor are equal, and ignore build and revision.
uint16_t major = cols[MONO_ASSEMBLY_MAJOR_VERSION];
uint16_t minor = cols[MONO_ASSEMBLY_MINOR_VERSION];

uint16_t required_minor;
uint16_t required_major = mono_assembly_name_get_version(p_aname, &required_minor, nullptr, nullptr);

if (required_major != 0) {
if (major != required_major && minor != required_minor) {
mono_image_close(image);
return nullptr;
}
}
}
}

#ifdef DEBUG_ENABLED
Vector<uint8_t> pdb_data;
Expand Down Expand Up @@ -425,6 +448,26 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class)
return match;
}

GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {

if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll"))
return GDMono::get_singleton()->get_corlib_assembly();

// We need to manually call the search hook in this case, as it won't be called in the next step
MonoAssembly *assembly = mono_assembly_invoke_search_hook(p_aname);

if (!assembly) {
assembly = _load_assembly_search(p_name, p_aname, p_refonly, p_search_dirs);
ERR_FAIL_NULL_V(assembly, nullptr);
}

GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?");
ERR_FAIL_COND_V(loaded_asm->get_assembly() != assembly, nullptr);

return loaded_asm;
}

GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) {

if (p_name == "mscorlib" || p_name == "mscorlib.dll")
Expand Down
Loading

0 comments on commit af4acb5

Please sign in to comment.