Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse public API and generate method information #225

Merged
merged 2 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/elf/script_elf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,13 @@ bool ELFScript::_has_static_method(const StringName &p_method) const {
return false;
}
Dictionary ELFScript::_get_method_info(const StringName &p_method) const {
TypedArray<Dictionary> functions_array;
for (String function : function_names) {
for (int i = 0; i < functions.size(); i++) {
Dictionary function = functions[i];
if (StringName(function.get("name", "")) == p_method) {
return function;
}
}
for (const String &function : function_names) {
if (function == p_method) {
if constexpr (VERBOSE_ELFSCRIPT) {
printf("ELFScript::_get_method_info: method %s\n", p_method.to_ascii_buffer().ptr());
Expand Down Expand Up @@ -221,6 +226,9 @@ TypedArray<Dictionary> ELFScript::_get_script_property_list() const {

void ELFScript::_update_exports() {}
TypedArray<Dictionary> ELFScript::_get_script_method_list() const {
if (!this->functions.is_empty()) {
return this->functions;
}
TypedArray<Dictionary> functions_array;
for (String function : function_names) {
Dictionary method;
Expand All @@ -229,11 +237,11 @@ TypedArray<Dictionary> ELFScript::_get_script_method_list() const {
method["default_args"] = Array();
Dictionary type;
type["name"] = "type";
type["type"] = Variant::Type::BOOL;
type["type"] = Variant::Type::NIL;
type["class_name"] = "class";
type["hint"] = PropertyHint::PROPERTY_HINT_NONE;
type["hint_string"] = String();
type["usage"] = PROPERTY_USAGE_DEFAULT;
type["usage"] = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT;
method["return"] = type;
method["flags"] = METHOD_FLAG_VARARG;
functions_array.push_back(method);
Expand Down
57 changes: 40 additions & 17 deletions src/elf/script_instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,10 @@ Variant ELFScriptInstance::callp(
}

GDExtensionMethodInfo create_method_info(const MethodInfo &method_info) {
return GDExtensionMethodInfo{
GDExtensionMethodInfo result{
.name = stringname_alloc(method_info.name),
.return_value = GDExtensionPropertyInfo{
.type = GDEXTENSION_VARIANT_TYPE_OBJECT,
.type = (GDExtensionVariantType)method_info.return_val.type,
.name = stringname_alloc(method_info.return_val.name),
.class_name = stringname_alloc(method_info.return_val.class_name),
.hint = method_info.return_val.hint,
Expand All @@ -207,6 +207,20 @@ GDExtensionMethodInfo create_method_info(const MethodInfo &method_info) {
.default_argument_count = 0,
.default_arguments = nullptr,
};
if (!method_info.arguments.empty()) {
result.arguments = memnew_arr(GDExtensionPropertyInfo, method_info.arguments.size());
for (int i = 0; i < method_info.arguments.size(); i++) {
const PropertyInfo &arg = method_info.arguments[i];
result.arguments[i] = GDExtensionPropertyInfo{
.type = (GDExtensionVariantType)arg.type,
.name = stringname_alloc(arg.name),
.class_name = stringname_alloc(arg.class_name),
.hint = arg.hint,
.hint_string = stringname_alloc(arg.hint_string),
.usage = arg.usage };
}
}
return result;
}

void ELFScriptInstance::update_methods() const {
Expand All @@ -216,11 +230,20 @@ void ELFScriptInstance::update_methods() const {
this->has_updated_methods = true;
this->methods_info.clear();

for (const String &function : script->function_names) {
MethodInfo method_info = MethodInfo(
Variant::NIL,
StringName(function));
this->methods_info.push_back(method_info);
if (script->functions.is_empty()) {
// Fallback: Use the function names from the ELFScript
for (const String &function : script->function_names) {
MethodInfo method_info(
Variant::NIL,
StringName(function));
this->methods_info.push_back(method_info);
}
} else {
// Create highly specific MethodInfo based on 'functions' Array
for (size_t i = 0; i < script->functions.size(); i++) {
const Dictionary func = script->functions[i].operator Dictionary();
this->methods_info.push_back(MethodInfo::from_dict(func));
}
}
}

Expand All @@ -237,7 +260,7 @@ const GDExtensionMethodInfo *ELFScriptInstance::get_method_list(uint32_t *r_coun
const int size = methods_info.size();
GDExtensionMethodInfo *list = memnew_arr(GDExtensionMethodInfo, size);
int i = 0;
for (godot::MethodInfo &method_info : methods_info) {
for (const godot::MethodInfo &method_info : methods_info) {
list[i] = create_method_info(method_info);
i++;
}
Expand Down Expand Up @@ -352,16 +375,10 @@ bool ELFScriptInstance::has_method(const StringName &p_name) const {
if (script.is_null()) {
return true;
}
bool result = false;
for (const std::string &function : godot_functions) {
if (p_name == StringName(function.c_str())) {
result = true;
break;
}
}
bool result = script->function_names.has(p_name);
if (!result) {
for (const String &function : script->function_names) {
if (p_name == StringName(function)) {
for (const std::string &function : godot_functions) {
if (p_name == StringName(function.c_str())) {
result = true;
break;
}
Expand All @@ -376,6 +393,12 @@ bool ELFScriptInstance::has_method(const StringName &p_name) const {

void ELFScriptInstance::free_method_list(const GDExtensionMethodInfo *p_list, uint32_t p_count) const {
if (p_list) {
for (uint32_t i = 0; i < p_count; i++) {
const GDExtensionMethodInfo &method_info = p_list[i];
if (method_info.arguments) {
memdelete_arr(method_info.arguments);
}
}
memdelete_arr(p_list);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/sandbox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ bool Sandbox::load(const PackedByteArray *buffer, const std::vector<std::string>
// We can't read them without having loaded the program first
// If the functions Array in the ELFScript object is empty, we will look for the API functions
if (this->m_program_data->functions.is_empty()) {
Array api = this->get_public_api_functions(machine());
Array api = this->get_public_api_functions();
if (!api.is_empty()) {
// Set the public API functions on the ELFScript object
this->m_program_data->set_public_api_functions(std::move(api));
Expand Down
2 changes: 1 addition & 1 deletion src/sandbox.h
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ class Sandbox : public Node {
void reset_machine();
void set_program_data_internal(Ref<ELFScript> program);
bool load(const PackedByteArray *vbuf, const std::vector<std::string> *argv = nullptr);
static Array get_public_api_functions(const machine_t&);
Array get_public_api_functions() const;
static PackedStringArray get_public_functions(const machine_t&);
void read_program_properties(bool editor) const;
void handle_exception(gaddr_t);
Expand Down
135 changes: 119 additions & 16 deletions src/sandbox_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1236,10 +1236,87 @@ static bool is_excluded_function(const std::string_view function) {
return false;
}

Array Sandbox::get_public_api_functions(const machine_t& machine) {
Array result;
static Variant::Type convert_guest_type_to_variant(const String &type) {
if (type == "bool") {
return Variant::BOOL;
} else if (type == "int" || type == "int32_t" || type == "int64_t" || type == "uint32_t" || type == "uint64_t" || type == "long" || type == "unsigned") {
return Variant::INT;
} else if (type == "float" || type == "double") {
return Variant::FLOAT;
} else if (type == "String" || type == "StringName") {
return Variant::STRING;
} else if (type == "Vector2") {
return Variant::VECTOR2;
} else if (type == "Vector3") {
return Variant::VECTOR3;
} else if (type == "Vector4") {
return Variant::VECTOR4;
} else if (type == "Vector2i") {
return Variant::VECTOR2I;
} else if (type == "Vector3i") {
return Variant::VECTOR3I;
} else if (type == "Vector4i") {
return Variant::VECTOR4I;
} else if (type == "Rect2") {
return Variant::RECT2;
} else if (type == "Rect2i") {
return Variant::RECT2I;
} else if (type == "Transform2D") {
return Variant::TRANSFORM2D;
} else if (type == "Plane") {
return Variant::PLANE;
} else if (type == "Quaternion") {
return Variant::QUATERNION;
} else if (type == "AABB") {
return Variant::AABB;
} else if (type == "Basis") {
return Variant::BASIS;
} else if (type == "Transform3D") {
return Variant::TRANSFORM3D;
} else if (type == "Color") {
return Variant::COLOR;
} else if (type == "NodePath") {
return Variant::NODE_PATH;
} else if (type == "RID") {
return Variant::RID;
} else if (type == "Object" || type == "Node" || type == "Node2D" || type == "Node3D") {
return Variant::OBJECT;
} else if (type == "Dictionary") {
return Variant::DICTIONARY;
} else if (type == "Array") {
return Variant::ARRAY;
} else if (type == "Callable") {
return Variant::CALLABLE;
} else if (type == "Signal") {
return Variant::SIGNAL;
} else if (type == "PackedByteArray") {
return Variant::PACKED_BYTE_ARRAY;
} else if (type == "PackedInt32Array") {
return Variant::PACKED_INT32_ARRAY;
} else if (type == "PackedInt64Array") {
return Variant::PACKED_INT64_ARRAY;
} else if (type == "PackedFloat32Array") {
return Variant::PACKED_FLOAT32_ARRAY;
} else if (type == "PackedFloat64Array") {
return Variant::PACKED_FLOAT64_ARRAY;
} else if (type == "PackedStringArray") {
return Variant::PACKED_STRING_ARRAY;
} else if (type == "PackedVector2Array") {
return Variant::PACKED_VECTOR2_ARRAY;
} else if (type == "PackedVector3Array") {
return Variant::PACKED_VECTOR3_ARRAY;
} else if (type == "PackedVector4Array") {
return Variant::PACKED_VECTOR4_ARRAY;
} else if (type == "PackedColorArray") {
return Variant::PACKED_COLOR_ARRAY;
}
return Variant::NIL;
}

Array Sandbox::get_public_api_functions() const {
TypedArray<Dictionary> result;
try {
gaddr_t public_api_addr = machine.address_of("public_api");
gaddr_t public_api_addr = machine().address_of("public_api");
if (public_api_addr != 0x0) {
// Get the public API from this address instead of the symbol table.
// It's an array of structs, ending with a null pointer.
Expand All @@ -1253,44 +1330,70 @@ Array Sandbox::get_public_api_functions(const machine_t& machine) {

// View up to 32 public functions, however we will stop at the first null pointer.
static constexpr size_t MAX_PAPI = 32;
const PublicAPI *api = machine.memory.memarray<PublicAPI>(public_api_addr, MAX_PAPI);
const PublicAPI *api = machine().memory.memarray<PublicAPI>(public_api_addr, MAX_PAPI);
for (size_t i = 0; i < MAX_PAPI; i++) {
const PublicAPI &entry = api[i];
if (entry.name == 0x0) {
break;
}
std::string_view name = machine.memory.memstring_view(entry.name);
std::string_view name = machine().memory.memstring_view(entry.name);
if (name.empty() || name.size() > 64 || entry.address == 0x0) {
ERR_PRINT("Sandbox: Invalid public API address.");
return result;
}
Dictionary func;
func["name"] = String::utf8(name.begin(), name.size());
String godot_name = String::utf8(name.begin(), name.size());
func["name"] = godot_name;
func["address"] = entry.address;
func["flags"] = METHOD_FLAG_NORMAL;

Dictionary return_value;
if (entry.return_type != 0x0) {
std::string_view return_type = machine.memory.memstring_view(entry.return_type);
func["return_type"] = String::utf8(return_type.begin(), return_type.size());
std::string_view return_type = machine().memory.memstring_view(entry.return_type);
return_value["type"] = convert_guest_type_to_variant(String::utf8(return_type.begin(), return_type.size()));
} else {
return_value["type"] = Variant::NIL;
}
func["return"] = std::move(return_value);

if (entry.description != 0x0) {
std::string_view description = machine.memory.memstring_view(entry.description);
std::string_view description = machine().memory.memstring_view(entry.description);
func["description"] = String::utf8(description.begin(), description.size());
}

Array args;
TypedArray<Dictionary> args;
if (entry.args != 0x0) {
std::string_view arg_list = machine.memory.memstring_view(entry.args);
std::string_view arg_list = machine().memory.memstring_view(entry.args);
PackedStringArray arg_names = String::utf8(arg_list.begin(), arg_list.size()).split(", ");
PackedStringArray arg_name_and_type;
for (const String &arg : arg_names) {
args.append(arg);
arg_name_and_type.clear();
arg_name_and_type = arg.split(" ");
// Convert the argument name and type to a dictionary.
String arg_name = arg_name_and_type[0];
String arg_type = "Variant";
if (arg_name_and_type.size() > 1) {
arg_type = arg_name_and_type[0];
arg_name = arg_name_and_type[1];
}

Dictionary argument;
argument["name"] = arg_name;
argument["type"] = convert_guest_type_to_variant(arg_type);
argument["class_name"] = "Variant";
argument["usage"] = PROPERTY_USAGE_NIL_IS_VARIANT;

args.append(std::move(argument));
}
} else {
ERR_PRINT("Sandbox: Invalid function arguments.");
return result;
}

func["args"] = args;
result.append(func);
func["args"] = std::move(args);
result.append(std::move(func));

// Since this public function was accepted, cache the address under the function name.
this->m_lookup.insert_or_assign(godot_name.hash(), entry.address);
}
}
} catch (const std::exception &e) {
Expand Down Expand Up @@ -1324,7 +1427,7 @@ PackedStringArray Sandbox::get_public_functions(const machine_t& machine) {

Array Sandbox::get_functions() const {
// Check if the guest program has a public API.
Array result = get_public_api_functions(machine());
Array result = this->get_public_api_functions();
if (!result.is_empty()) {
return result;
}
Expand Down
Loading