Merge pull request #79893 from vnen/gdscript-validated-method-bind-call
GDScript: Replace ptrcalls on MethodBind to validated calls
This commit is contained in:
commit
2f919f0fd0
@ -400,7 +400,6 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
|
|||||||
}
|
}
|
||||||
function->_stack_size = RESERVED_STACK + max_locals + temporaries.size();
|
function->_stack_size = RESERVED_STACK + max_locals + temporaries.size();
|
||||||
function->_instruction_args_size = instr_args_max;
|
function->_instruction_args_size = instr_args_max;
|
||||||
function->_ptrcall_args_size = ptrcall_max;
|
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
function->operator_names = operator_names;
|
function->operator_names = operator_names;
|
||||||
@ -1225,75 +1224,35 @@ void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target,
|
|||||||
ct.cleanup();
|
ct.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) {
|
void GDScriptByteCodeGenerator::write_call_method_bind_validated(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) {
|
||||||
#define CASE_TYPE(m_type) \
|
Variant::Type return_type = Variant::NIL;
|
||||||
case Variant::m_type: \
|
bool has_return = p_method->has_return();
|
||||||
append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_PTRCALL_##m_type, 2 + p_arguments.size()); \
|
|
||||||
break
|
|
||||||
|
|
||||||
bool is_ptrcall = true;
|
if (has_return) {
|
||||||
|
PropertyInfo return_info = p_method->get_return_info();
|
||||||
|
return_type = return_info.type;
|
||||||
|
}
|
||||||
|
|
||||||
if (p_method->has_return()) {
|
CallTarget ct = get_call_target(p_target, return_type);
|
||||||
MethodInfo info;
|
|
||||||
ClassDB::get_method_info(p_method->get_instance_class(), p_method->get_name(), &info);
|
if (has_return) {
|
||||||
switch (info.return_val.type) {
|
Variant::Type temp_type = temporaries[ct.target.address].type;
|
||||||
CASE_TYPE(BOOL);
|
if (temp_type != return_type) {
|
||||||
CASE_TYPE(INT);
|
write_type_adjust(ct.target, return_type);
|
||||||
CASE_TYPE(FLOAT);
|
|
||||||
CASE_TYPE(STRING);
|
|
||||||
CASE_TYPE(VECTOR2);
|
|
||||||
CASE_TYPE(VECTOR2I);
|
|
||||||
CASE_TYPE(RECT2);
|
|
||||||
CASE_TYPE(RECT2I);
|
|
||||||
CASE_TYPE(VECTOR3);
|
|
||||||
CASE_TYPE(VECTOR3I);
|
|
||||||
CASE_TYPE(TRANSFORM2D);
|
|
||||||
CASE_TYPE(PLANE);
|
|
||||||
CASE_TYPE(AABB);
|
|
||||||
CASE_TYPE(BASIS);
|
|
||||||
CASE_TYPE(TRANSFORM3D);
|
|
||||||
CASE_TYPE(COLOR);
|
|
||||||
CASE_TYPE(STRING_NAME);
|
|
||||||
CASE_TYPE(NODE_PATH);
|
|
||||||
CASE_TYPE(RID);
|
|
||||||
CASE_TYPE(QUATERNION);
|
|
||||||
CASE_TYPE(OBJECT);
|
|
||||||
CASE_TYPE(CALLABLE);
|
|
||||||
CASE_TYPE(SIGNAL);
|
|
||||||
CASE_TYPE(DICTIONARY);
|
|
||||||
CASE_TYPE(ARRAY);
|
|
||||||
CASE_TYPE(PACKED_BYTE_ARRAY);
|
|
||||||
CASE_TYPE(PACKED_INT32_ARRAY);
|
|
||||||
CASE_TYPE(PACKED_INT64_ARRAY);
|
|
||||||
CASE_TYPE(PACKED_FLOAT32_ARRAY);
|
|
||||||
CASE_TYPE(PACKED_FLOAT64_ARRAY);
|
|
||||||
CASE_TYPE(PACKED_STRING_ARRAY);
|
|
||||||
CASE_TYPE(PACKED_VECTOR2_ARRAY);
|
|
||||||
CASE_TYPE(PACKED_VECTOR3_ARRAY);
|
|
||||||
CASE_TYPE(PACKED_COLOR_ARRAY);
|
|
||||||
default:
|
|
||||||
append_opcode_and_argcount(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size());
|
|
||||||
is_ptrcall = false;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_PTRCALL_NO_RETURN, 2 + p_arguments.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GDScriptFunction::Opcode code = p_method->has_return() ? GDScriptFunction::OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN : GDScriptFunction::OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN;
|
||||||
|
append_opcode_and_argcount(code, 2 + p_arguments.size());
|
||||||
|
|
||||||
for (int i = 0; i < p_arguments.size(); i++) {
|
for (int i = 0; i < p_arguments.size(); i++) {
|
||||||
append(p_arguments[i]);
|
append(p_arguments[i]);
|
||||||
}
|
}
|
||||||
append(p_base);
|
append(p_base);
|
||||||
CallTarget ct = get_call_target(p_target);
|
|
||||||
append(ct.target);
|
append(ct.target);
|
||||||
append(p_arguments.size());
|
append(p_arguments.size());
|
||||||
append(p_method);
|
append(p_method);
|
||||||
ct.cleanup();
|
ct.cleanup();
|
||||||
if (is_ptrcall) {
|
|
||||||
alloc_ptrcall(p_arguments.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef CASE_TYPE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) {
|
void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) {
|
||||||
|
@ -97,7 +97,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
|
|||||||
int max_locals = 0;
|
int max_locals = 0;
|
||||||
int current_line = 0;
|
int current_line = 0;
|
||||||
int instr_args_max = 0;
|
int instr_args_max = 0;
|
||||||
int ptrcall_max = 0;
|
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
List<int> temp_stack;
|
List<int> temp_stack;
|
||||||
@ -346,12 +345,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
|
|||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
void alloc_ptrcall(int p_params) {
|
|
||||||
if (p_params >= ptrcall_max) {
|
|
||||||
ptrcall_max = p_params;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CallTarget get_call_target(const Address &p_target, Variant::Type p_type = Variant::NIL);
|
CallTarget get_call_target(const Address &p_target, Variant::Type p_type = Variant::NIL);
|
||||||
|
|
||||||
int address_of(const Address &p_address) {
|
int address_of(const Address &p_address) {
|
||||||
@ -519,7 +512,7 @@ public:
|
|||||||
virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override;
|
virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override;
|
||||||
virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) override;
|
virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) override;
|
||||||
virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
|
virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
|
||||||
virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
|
virtual void write_call_method_bind_validated(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
|
||||||
virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
|
virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
|
||||||
virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
|
virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
|
||||||
virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
|
virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
|
||||||
|
@ -129,7 +129,7 @@ public:
|
|||||||
virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
|
virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
|
||||||
virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
|
virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
|
||||||
virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
|
virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
|
||||||
virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
|
virtual void write_call_method_bind_validated(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
|
||||||
virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
|
virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
|
||||||
virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
|
virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
|
||||||
virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
|
virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
|
||||||
|
@ -229,13 +229,13 @@ static bool _is_exact_type(const PropertyInfo &p_par_type, const GDScriptDataTyp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _can_use_ptrcall(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) {
|
static bool _can_use_validate_call(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) {
|
||||||
if (p_method->is_vararg()) {
|
if (p_method->is_vararg()) {
|
||||||
// ptrcall won't work with vararg methods.
|
// Validated call won't work with vararg methods.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (p_method->get_argument_count() != p_arguments.size()) {
|
if (p_method->get_argument_count() != p_arguments.size()) {
|
||||||
// ptrcall won't work with default arguments.
|
// Validated call won't work with default arguments.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
MethodInfo info;
|
MethodInfo info;
|
||||||
@ -636,9 +636,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
|||||||
self.mode = GDScriptCodeGenerator::Address::SELF;
|
self.mode = GDScriptCodeGenerator::Address::SELF;
|
||||||
MethodBind *method = ClassDB::get_method(codegen.script->native->get_name(), call->function_name);
|
MethodBind *method = ClassDB::get_method(codegen.script->native->get_name(), call->function_name);
|
||||||
|
|
||||||
if (_can_use_ptrcall(method, arguments)) {
|
if (_can_use_validate_call(method, arguments)) {
|
||||||
// Exact arguments, use ptrcall.
|
// Exact arguments, use validated call.
|
||||||
gen->write_call_ptrcall(result, self, method, arguments);
|
gen->write_call_method_bind_validated(result, self, method, arguments);
|
||||||
} else {
|
} else {
|
||||||
// Not exact arguments, but still can use method bind call.
|
// Not exact arguments, but still can use method bind call.
|
||||||
gen->write_call_method_bind(result, self, method, arguments);
|
gen->write_call_method_bind(result, self, method, arguments);
|
||||||
@ -686,9 +686,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
|||||||
}
|
}
|
||||||
if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) {
|
if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) {
|
||||||
MethodBind *method = ClassDB::get_method(class_name, call->function_name);
|
MethodBind *method = ClassDB::get_method(class_name, call->function_name);
|
||||||
if (_can_use_ptrcall(method, arguments)) {
|
if (_can_use_validate_call(method, arguments)) {
|
||||||
// Exact arguments, use ptrcall.
|
// Exact arguments, use validated call.
|
||||||
gen->write_call_ptrcall(result, base, method, arguments);
|
gen->write_call_method_bind_validated(result, base, method, arguments);
|
||||||
} else {
|
} else {
|
||||||
// Not exact arguments, but still can use method bind call.
|
// Not exact arguments, but still can use method bind call.
|
||||||
gen->write_call_method_bind(result, base, method, arguments);
|
gen->write_call_method_bind(result, base, method, arguments);
|
||||||
@ -733,7 +733,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
|
|||||||
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype(), codegen.script));
|
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype(), codegen.script));
|
||||||
|
|
||||||
MethodBind *get_node_method = ClassDB::get_method("Node", "get_node");
|
MethodBind *get_node_method = ClassDB::get_method("Node", "get_node");
|
||||||
gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args);
|
gen->write_call_method_bind_validated(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} break;
|
} break;
|
||||||
|
@ -670,10 +670,29 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
|
|||||||
|
|
||||||
incr += 4 + argc;
|
incr += 4 + argc;
|
||||||
} break;
|
} break;
|
||||||
case OPCODE_CALL_PTRCALL_NO_RETURN: {
|
|
||||||
|
case OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN: {
|
||||||
|
int instr_var_args = _code_ptr[++ip];
|
||||||
|
text += "call method-bind validated (return) ";
|
||||||
|
MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]];
|
||||||
|
int argc = _code_ptr[ip + 1 + instr_var_args];
|
||||||
|
text += DADDR(2 + argc) + " = ";
|
||||||
|
text += DADDR(1 + argc) + ".";
|
||||||
|
text += method->get_name();
|
||||||
|
text += "(";
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
if (i > 0)
|
||||||
|
text += ", ";
|
||||||
|
text += DADDR(1 + i);
|
||||||
|
}
|
||||||
|
text += ")";
|
||||||
|
incr = 5 + argc;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN: {
|
||||||
int instr_var_args = _code_ptr[++ip];
|
int instr_var_args = _code_ptr[++ip];
|
||||||
|
|
||||||
text += "call-ptrcall (no return) ";
|
text += "call method-bind validated (no return) ";
|
||||||
|
|
||||||
MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]];
|
MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]];
|
||||||
|
|
||||||
@ -694,65 +713,6 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
|
|||||||
incr = 5 + argc;
|
incr = 5 + argc;
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
#define DISASSEMBLE_PTRCALL(m_type) \
|
|
||||||
case OPCODE_CALL_PTRCALL_##m_type: { \
|
|
||||||
int instr_var_args = _code_ptr[++ip]; \
|
|
||||||
text += "call-ptrcall (return "; \
|
|
||||||
text += #m_type; \
|
|
||||||
text += ") "; \
|
|
||||||
MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; \
|
|
||||||
int argc = _code_ptr[ip + 1 + instr_var_args]; \
|
|
||||||
text += DADDR(2 + argc) + " = "; \
|
|
||||||
text += DADDR(1 + argc) + "."; \
|
|
||||||
text += method->get_name(); \
|
|
||||||
text += "("; \
|
|
||||||
for (int i = 0; i < argc; i++) { \
|
|
||||||
if (i > 0) \
|
|
||||||
text += ", "; \
|
|
||||||
text += DADDR(1 + i); \
|
|
||||||
} \
|
|
||||||
text += ")"; \
|
|
||||||
incr = 5 + argc; \
|
|
||||||
} break
|
|
||||||
|
|
||||||
DISASSEMBLE_PTRCALL(BOOL);
|
|
||||||
DISASSEMBLE_PTRCALL(INT);
|
|
||||||
DISASSEMBLE_PTRCALL(FLOAT);
|
|
||||||
DISASSEMBLE_PTRCALL(STRING);
|
|
||||||
DISASSEMBLE_PTRCALL(VECTOR2);
|
|
||||||
DISASSEMBLE_PTRCALL(VECTOR2I);
|
|
||||||
DISASSEMBLE_PTRCALL(RECT2);
|
|
||||||
DISASSEMBLE_PTRCALL(RECT2I);
|
|
||||||
DISASSEMBLE_PTRCALL(VECTOR3);
|
|
||||||
DISASSEMBLE_PTRCALL(VECTOR3I);
|
|
||||||
DISASSEMBLE_PTRCALL(TRANSFORM2D);
|
|
||||||
DISASSEMBLE_PTRCALL(VECTOR4);
|
|
||||||
DISASSEMBLE_PTRCALL(VECTOR4I);
|
|
||||||
DISASSEMBLE_PTRCALL(PLANE);
|
|
||||||
DISASSEMBLE_PTRCALL(AABB);
|
|
||||||
DISASSEMBLE_PTRCALL(BASIS);
|
|
||||||
DISASSEMBLE_PTRCALL(TRANSFORM3D);
|
|
||||||
DISASSEMBLE_PTRCALL(PROJECTION);
|
|
||||||
DISASSEMBLE_PTRCALL(COLOR);
|
|
||||||
DISASSEMBLE_PTRCALL(STRING_NAME);
|
|
||||||
DISASSEMBLE_PTRCALL(NODE_PATH);
|
|
||||||
DISASSEMBLE_PTRCALL(RID);
|
|
||||||
DISASSEMBLE_PTRCALL(QUATERNION);
|
|
||||||
DISASSEMBLE_PTRCALL(OBJECT);
|
|
||||||
DISASSEMBLE_PTRCALL(CALLABLE);
|
|
||||||
DISASSEMBLE_PTRCALL(SIGNAL);
|
|
||||||
DISASSEMBLE_PTRCALL(DICTIONARY);
|
|
||||||
DISASSEMBLE_PTRCALL(ARRAY);
|
|
||||||
DISASSEMBLE_PTRCALL(PACKED_BYTE_ARRAY);
|
|
||||||
DISASSEMBLE_PTRCALL(PACKED_INT32_ARRAY);
|
|
||||||
DISASSEMBLE_PTRCALL(PACKED_INT64_ARRAY);
|
|
||||||
DISASSEMBLE_PTRCALL(PACKED_FLOAT32_ARRAY);
|
|
||||||
DISASSEMBLE_PTRCALL(PACKED_FLOAT64_ARRAY);
|
|
||||||
DISASSEMBLE_PTRCALL(PACKED_STRING_ARRAY);
|
|
||||||
DISASSEMBLE_PTRCALL(PACKED_VECTOR2_ARRAY);
|
|
||||||
DISASSEMBLE_PTRCALL(PACKED_VECTOR3_ARRAY);
|
|
||||||
DISASSEMBLE_PTRCALL(PACKED_COLOR_ARRAY);
|
|
||||||
|
|
||||||
case OPCODE_CALL_BUILTIN_TYPE_VALIDATED: {
|
case OPCODE_CALL_BUILTIN_TYPE_VALIDATED: {
|
||||||
int instr_var_args = _code_ptr[++ip];
|
int instr_var_args = _code_ptr[++ip];
|
||||||
int argc = _code_ptr[ip + 1 + instr_var_args];
|
int argc = _code_ptr[ip + 1 + instr_var_args];
|
||||||
|
@ -241,45 +241,8 @@ public:
|
|||||||
OPCODE_CALL_METHOD_BIND_RET,
|
OPCODE_CALL_METHOD_BIND_RET,
|
||||||
OPCODE_CALL_BUILTIN_STATIC,
|
OPCODE_CALL_BUILTIN_STATIC,
|
||||||
OPCODE_CALL_NATIVE_STATIC,
|
OPCODE_CALL_NATIVE_STATIC,
|
||||||
// ptrcall have one instruction per return type.
|
OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN,
|
||||||
OPCODE_CALL_PTRCALL_NO_RETURN,
|
OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN,
|
||||||
OPCODE_CALL_PTRCALL_BOOL,
|
|
||||||
OPCODE_CALL_PTRCALL_INT,
|
|
||||||
OPCODE_CALL_PTRCALL_FLOAT,
|
|
||||||
OPCODE_CALL_PTRCALL_STRING,
|
|
||||||
OPCODE_CALL_PTRCALL_VECTOR2,
|
|
||||||
OPCODE_CALL_PTRCALL_VECTOR2I,
|
|
||||||
OPCODE_CALL_PTRCALL_RECT2,
|
|
||||||
OPCODE_CALL_PTRCALL_RECT2I,
|
|
||||||
OPCODE_CALL_PTRCALL_VECTOR3,
|
|
||||||
OPCODE_CALL_PTRCALL_VECTOR3I,
|
|
||||||
OPCODE_CALL_PTRCALL_TRANSFORM2D,
|
|
||||||
OPCODE_CALL_PTRCALL_VECTOR4,
|
|
||||||
OPCODE_CALL_PTRCALL_VECTOR4I,
|
|
||||||
OPCODE_CALL_PTRCALL_PLANE,
|
|
||||||
OPCODE_CALL_PTRCALL_QUATERNION,
|
|
||||||
OPCODE_CALL_PTRCALL_AABB,
|
|
||||||
OPCODE_CALL_PTRCALL_BASIS,
|
|
||||||
OPCODE_CALL_PTRCALL_TRANSFORM3D,
|
|
||||||
OPCODE_CALL_PTRCALL_PROJECTION,
|
|
||||||
OPCODE_CALL_PTRCALL_COLOR,
|
|
||||||
OPCODE_CALL_PTRCALL_STRING_NAME,
|
|
||||||
OPCODE_CALL_PTRCALL_NODE_PATH,
|
|
||||||
OPCODE_CALL_PTRCALL_RID,
|
|
||||||
OPCODE_CALL_PTRCALL_OBJECT,
|
|
||||||
OPCODE_CALL_PTRCALL_CALLABLE,
|
|
||||||
OPCODE_CALL_PTRCALL_SIGNAL,
|
|
||||||
OPCODE_CALL_PTRCALL_DICTIONARY,
|
|
||||||
OPCODE_CALL_PTRCALL_ARRAY,
|
|
||||||
OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY,
|
|
||||||
OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY,
|
|
||||||
OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY,
|
|
||||||
OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY,
|
|
||||||
OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY,
|
|
||||||
OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY,
|
|
||||||
OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY,
|
|
||||||
OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY,
|
|
||||||
OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY,
|
|
||||||
OPCODE_AWAIT,
|
OPCODE_AWAIT,
|
||||||
OPCODE_AWAIT_RESUME,
|
OPCODE_AWAIT_RESUME,
|
||||||
OPCODE_CREATE_LAMBDA,
|
OPCODE_CREATE_LAMBDA,
|
||||||
@ -425,7 +388,6 @@ private:
|
|||||||
int _argument_count = 0;
|
int _argument_count = 0;
|
||||||
int _stack_size = 0;
|
int _stack_size = 0;
|
||||||
int _instruction_args_size = 0;
|
int _instruction_args_size = 0;
|
||||||
int _ptrcall_args_size = 0;
|
|
||||||
|
|
||||||
SelfList<GDScriptFunction> function_list{ this };
|
SelfList<GDScriptFunction> function_list{ this };
|
||||||
mutable Variant nil;
|
mutable Variant nil;
|
||||||
|
@ -236,44 +236,8 @@ void (*type_init_function_table[])(Variant *) = {
|
|||||||
&&OPCODE_CALL_METHOD_BIND_RET, \
|
&&OPCODE_CALL_METHOD_BIND_RET, \
|
||||||
&&OPCODE_CALL_BUILTIN_STATIC, \
|
&&OPCODE_CALL_BUILTIN_STATIC, \
|
||||||
&&OPCODE_CALL_NATIVE_STATIC, \
|
&&OPCODE_CALL_NATIVE_STATIC, \
|
||||||
&&OPCODE_CALL_PTRCALL_NO_RETURN, \
|
&&OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN, \
|
||||||
&&OPCODE_CALL_PTRCALL_BOOL, \
|
&&OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN, \
|
||||||
&&OPCODE_CALL_PTRCALL_INT, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_FLOAT, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_STRING, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_VECTOR2, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_VECTOR2I, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_RECT2, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_RECT2I, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_VECTOR3, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_VECTOR3I, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_TRANSFORM2D, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_VECTOR4, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_VECTOR4I, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_PLANE, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_QUATERNION, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_AABB, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_BASIS, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_TRANSFORM3D, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_PROJECTION, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_COLOR, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_STRING_NAME, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_NODE_PATH, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_RID, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_OBJECT, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_CALLABLE, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_SIGNAL, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_DICTIONARY, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_ARRAY, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY, \
|
|
||||||
&&OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, \
|
|
||||||
&&OPCODE_AWAIT, \
|
&&OPCODE_AWAIT, \
|
||||||
&&OPCODE_AWAIT_RESUME, \
|
&&OPCODE_AWAIT_RESUME, \
|
||||||
&&OPCODE_CREATE_LAMBDA, \
|
&&OPCODE_CREATE_LAMBDA, \
|
||||||
@ -489,7 +453,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
Variant retvalue;
|
Variant retvalue;
|
||||||
Variant *stack = nullptr;
|
Variant *stack = nullptr;
|
||||||
Variant **instruction_args = nullptr;
|
Variant **instruction_args = nullptr;
|
||||||
const void **call_args_ptr = nullptr;
|
|
||||||
int defarg = 0;
|
int defarg = 0;
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
@ -578,12 +541,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_ptrcall_args_size) {
|
|
||||||
call_args_ptr = (const void **)alloca(_ptrcall_args_size * sizeof(void *));
|
|
||||||
} else {
|
|
||||||
call_args_ptr = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p_instance) {
|
if (p_instance) {
|
||||||
memnew_placement(&stack[ADDR_STACK_SELF], Variant(p_instance->owner));
|
memnew_placement(&stack[ADDR_STACK_SELF], Variant(p_instance->owner));
|
||||||
script = p_instance->script.ptr();
|
script = p_instance->script.ptr();
|
||||||
@ -1954,106 +1911,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
}
|
}
|
||||||
DISPATCH_OPCODE;
|
DISPATCH_OPCODE;
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
OPCODE(OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN) {
|
||||||
#define OPCODE_CALL_PTR(m_type) \
|
|
||||||
OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \
|
|
||||||
LOAD_INSTRUCTION_ARGS \
|
|
||||||
CHECK_SPACE(3 + instr_arg_count); \
|
|
||||||
ip += instr_arg_count; \
|
|
||||||
int argc = _code_ptr[ip + 1]; \
|
|
||||||
GD_ERR_BREAK(argc < 0); \
|
|
||||||
GET_INSTRUCTION_ARG(base, argc); \
|
|
||||||
GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count); \
|
|
||||||
MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \
|
|
||||||
bool freed = false; \
|
|
||||||
Object *base_obj = base->get_validated_object_with_check(freed); \
|
|
||||||
if (freed) { \
|
|
||||||
err_text = METHOD_CALL_ON_FREED_INSTANCE_ERROR(method); \
|
|
||||||
OPCODE_BREAK; \
|
|
||||||
} else if (!base_obj) { \
|
|
||||||
err_text = METHOD_CALL_ON_NULL_VALUE_ERROR(method); \
|
|
||||||
OPCODE_BREAK; \
|
|
||||||
} \
|
|
||||||
const void **argptrs = call_args_ptr; \
|
|
||||||
for (int i = 0; i < argc; i++) { \
|
|
||||||
GET_INSTRUCTION_ARG(v, i); \
|
|
||||||
argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \
|
|
||||||
} \
|
|
||||||
uint64_t call_time = 0; \
|
|
||||||
if (GDScriptLanguage::get_singleton()->profiling) { \
|
|
||||||
call_time = OS::get_singleton()->get_ticks_usec(); \
|
|
||||||
} \
|
|
||||||
GET_INSTRUCTION_ARG(ret, argc + 1); \
|
|
||||||
VariantInternal::initialize(ret, Variant::m_type); \
|
|
||||||
void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \
|
|
||||||
method->ptrcall(base_obj, argptrs, ret_opaque); \
|
|
||||||
if (GDScriptLanguage::get_singleton()->profiling) { \
|
|
||||||
function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; \
|
|
||||||
} \
|
|
||||||
ip += 3; \
|
|
||||||
} \
|
|
||||||
DISPATCH_OPCODE
|
|
||||||
#else
|
|
||||||
#define OPCODE_CALL_PTR(m_type) \
|
|
||||||
OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \
|
|
||||||
LOAD_INSTRUCTION_ARGS \
|
|
||||||
CHECK_SPACE(3 + instr_arg_count); \
|
|
||||||
ip += instr_arg_count; \
|
|
||||||
int argc = _code_ptr[ip + 1]; \
|
|
||||||
GET_INSTRUCTION_ARG(base, argc); \
|
|
||||||
MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \
|
|
||||||
Object *base_obj = *VariantInternal::get_object(base); \
|
|
||||||
const void **argptrs = call_args_ptr; \
|
|
||||||
for (int i = 0; i < argc; i++) { \
|
|
||||||
GET_INSTRUCTION_ARG(v, i); \
|
|
||||||
argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \
|
|
||||||
} \
|
|
||||||
GET_INSTRUCTION_ARG(ret, argc + 1); \
|
|
||||||
VariantInternal::initialize(ret, Variant::m_type); \
|
|
||||||
void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \
|
|
||||||
method->ptrcall(base_obj, argptrs, ret_opaque); \
|
|
||||||
ip += 3; \
|
|
||||||
} \
|
|
||||||
DISPATCH_OPCODE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
OPCODE_CALL_PTR(BOOL);
|
|
||||||
OPCODE_CALL_PTR(INT);
|
|
||||||
OPCODE_CALL_PTR(FLOAT);
|
|
||||||
OPCODE_CALL_PTR(STRING);
|
|
||||||
OPCODE_CALL_PTR(VECTOR2);
|
|
||||||
OPCODE_CALL_PTR(VECTOR2I);
|
|
||||||
OPCODE_CALL_PTR(RECT2);
|
|
||||||
OPCODE_CALL_PTR(RECT2I);
|
|
||||||
OPCODE_CALL_PTR(VECTOR3);
|
|
||||||
OPCODE_CALL_PTR(VECTOR3I);
|
|
||||||
OPCODE_CALL_PTR(TRANSFORM2D);
|
|
||||||
OPCODE_CALL_PTR(VECTOR4);
|
|
||||||
OPCODE_CALL_PTR(VECTOR4I);
|
|
||||||
OPCODE_CALL_PTR(PLANE);
|
|
||||||
OPCODE_CALL_PTR(QUATERNION);
|
|
||||||
OPCODE_CALL_PTR(AABB);
|
|
||||||
OPCODE_CALL_PTR(BASIS);
|
|
||||||
OPCODE_CALL_PTR(TRANSFORM3D);
|
|
||||||
OPCODE_CALL_PTR(PROJECTION);
|
|
||||||
OPCODE_CALL_PTR(COLOR);
|
|
||||||
OPCODE_CALL_PTR(STRING_NAME);
|
|
||||||
OPCODE_CALL_PTR(NODE_PATH);
|
|
||||||
OPCODE_CALL_PTR(RID);
|
|
||||||
OPCODE_CALL_PTR(CALLABLE);
|
|
||||||
OPCODE_CALL_PTR(SIGNAL);
|
|
||||||
OPCODE_CALL_PTR(DICTIONARY);
|
|
||||||
OPCODE_CALL_PTR(ARRAY);
|
|
||||||
OPCODE_CALL_PTR(PACKED_BYTE_ARRAY);
|
|
||||||
OPCODE_CALL_PTR(PACKED_INT32_ARRAY);
|
|
||||||
OPCODE_CALL_PTR(PACKED_INT64_ARRAY);
|
|
||||||
OPCODE_CALL_PTR(PACKED_FLOAT32_ARRAY);
|
|
||||||
OPCODE_CALL_PTR(PACKED_FLOAT64_ARRAY);
|
|
||||||
OPCODE_CALL_PTR(PACKED_STRING_ARRAY);
|
|
||||||
OPCODE_CALL_PTR(PACKED_VECTOR2_ARRAY);
|
|
||||||
OPCODE_CALL_PTR(PACKED_VECTOR3_ARRAY);
|
|
||||||
OPCODE_CALL_PTR(PACKED_COLOR_ARRAY);
|
|
||||||
OPCODE(OPCODE_CALL_PTRCALL_OBJECT) {
|
|
||||||
LOAD_INSTRUCTION_ARGS
|
LOAD_INSTRUCTION_ARGS
|
||||||
CHECK_SPACE(3 + instr_arg_count);
|
CHECK_SPACE(3 + instr_arg_count);
|
||||||
|
|
||||||
@ -2066,6 +1924,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];
|
MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];
|
||||||
|
|
||||||
GET_INSTRUCTION_ARG(base, argc);
|
GET_INSTRUCTION_ARG(base, argc);
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
bool freed = false;
|
bool freed = false;
|
||||||
Object *base_obj = base->get_validated_object_with_check(freed);
|
Object *base_obj = base->get_validated_object_with_check(freed);
|
||||||
@ -2080,12 +1939,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
Object *base_obj = *VariantInternal::get_object(base);
|
Object *base_obj = *VariantInternal::get_object(base);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const void **argptrs = call_args_ptr;
|
Variant **argptrs = instruction_args;
|
||||||
|
|
||||||
for (int i = 0; i < argc; i++) {
|
|
||||||
GET_INSTRUCTION_ARG(v, i);
|
|
||||||
argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);
|
|
||||||
}
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
uint64_t call_time = 0;
|
uint64_t call_time = 0;
|
||||||
|
|
||||||
@ -2095,16 +1950,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
GET_INSTRUCTION_ARG(ret, argc + 1);
|
GET_INSTRUCTION_ARG(ret, argc + 1);
|
||||||
VariantInternal::initialize(ret, Variant::OBJECT);
|
method->validated_call(base_obj, (const Variant **)argptrs, ret);
|
||||||
Object **ret_opaque = VariantInternal::get_object(ret);
|
|
||||||
method->ptrcall(base_obj, argptrs, ret_opaque);
|
|
||||||
if (method->is_return_type_raw_object_ptr()) {
|
|
||||||
// The Variant has to participate in the ref count since the method returns a raw Object *.
|
|
||||||
VariantInternal::object_assign(ret, *ret_opaque);
|
|
||||||
} else {
|
|
||||||
// The method, in case it returns something, returns an already encapsulated object.
|
|
||||||
VariantInternal::update_object_id(ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
if (GDScriptLanguage::get_singleton()->profiling) {
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
||||||
@ -2114,7 +1960,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
ip += 3;
|
ip += 3;
|
||||||
}
|
}
|
||||||
DISPATCH_OPCODE;
|
DISPATCH_OPCODE;
|
||||||
OPCODE(OPCODE_CALL_PTRCALL_NO_RETURN) {
|
|
||||||
|
OPCODE(OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN) {
|
||||||
LOAD_INSTRUCTION_ARGS
|
LOAD_INSTRUCTION_ARGS
|
||||||
CHECK_SPACE(3 + instr_arg_count);
|
CHECK_SPACE(3 + instr_arg_count);
|
||||||
|
|
||||||
@ -2140,12 +1987,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
#else
|
#else
|
||||||
Object *base_obj = *VariantInternal::get_object(base);
|
Object *base_obj = *VariantInternal::get_object(base);
|
||||||
#endif
|
#endif
|
||||||
const void **argptrs = call_args_ptr;
|
Variant **argptrs = instruction_args;
|
||||||
|
|
||||||
for (int i = 0; i < argc; i++) {
|
|
||||||
GET_INSTRUCTION_ARG(v, i);
|
|
||||||
argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);
|
|
||||||
}
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
uint64_t call_time = 0;
|
uint64_t call_time = 0;
|
||||||
|
|
||||||
@ -2156,7 +1998,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
|
|
||||||
GET_INSTRUCTION_ARG(ret, argc + 1);
|
GET_INSTRUCTION_ARG(ret, argc + 1);
|
||||||
VariantInternal::initialize(ret, Variant::NIL);
|
VariantInternal::initialize(ret, Variant::NIL);
|
||||||
method->ptrcall(base_obj, argptrs, nullptr);
|
method->validated_call(base_obj, (const Variant **)argptrs, nullptr);
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
if (GDScriptLanguage::get_singleton()->profiling) {
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
||||||
|
@ -7,7 +7,7 @@ func test():
|
|||||||
test_builtin_call_validated(Vector2.UP, false)
|
test_builtin_call_validated(Vector2.UP, false)
|
||||||
test_object_call(RefCounted.new(), false)
|
test_object_call(RefCounted.new(), false)
|
||||||
test_object_call_method_bind(Resource.new(), false)
|
test_object_call_method_bind(Resource.new(), false)
|
||||||
test_object_call_ptrcall(RefCounted.new(), false)
|
test_object_call_method_bind_validated(RefCounted.new(), false)
|
||||||
|
|
||||||
print("end")
|
print("end")
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ func test_object_call_method_bind(v: Resource, f):
|
|||||||
v.duplicate() # Native type method call with MethodBind.
|
v.duplicate() # Native type method call with MethodBind.
|
||||||
assert(not f) # Test unary operator reading from `nil`.
|
assert(not f) # Test unary operator reading from `nil`.
|
||||||
|
|
||||||
func test_object_call_ptrcall(v: RefCounted, f):
|
func test_object_call_method_bind_validated(v: RefCounted, f):
|
||||||
@warning_ignore("return_value_discarded")
|
@warning_ignore("return_value_discarded")
|
||||||
v.get_reference_count() # Native type method call with ptrcall.
|
v.get_reference_count() # Native type method call with validated MethodBind.
|
||||||
assert(not f) # Test unary operator reading from `nil`.
|
assert(not f) # Test unary operator reading from `nil`.
|
||||||
|
Loading…
Reference in New Issue
Block a user