From 78083846c6d9214bd1bd4ccae7cd3efcda606331 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Mon, 2 Oct 2023 20:25:25 +0200 Subject: [PATCH 01/21] Fix memory leak in compile_string() When a provided variable/function/struct lookup function returns the same value for different names, the returned object leaks. --- src/prolang.y | 6 +++--- test/t-efuns.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/prolang.y b/src/prolang.y index 4f0408bd4..2a242971c 100644 --- a/src/prolang.y +++ b/src/prolang.y @@ -6680,7 +6680,7 @@ find_struct ( ident_t * ident, efun_override_t override ) lt->t_struct.def_idx = LAMBDA_STRUCTS_COUNT; assign_svalue_no_free(&ref_str, str); /* Add a reference. */ - idx = store_lambda_value(str); + idx = store_lambda_value(&ref_str); name->u.global.struct_id = LAMBDA_STRUCTS_COUNT; ADD_LAMBDA_STRUCT((lambda_struct_ident_t){ @@ -8659,7 +8659,7 @@ lookup_function (ident_t *ident, char* super, efun_override_t override) int idx; assign_svalue_no_free(&ref_fun, fun); /* Add a reference. */ - idx = store_lambda_value(fun); + idx = store_lambda_value(&ref_fun); ident->u.global.function = LAMBDA_FUNCTIONS_COUNT; ADD_LAMBDA_FUNCTION((lambda_ident_t){.kind = LAMBDA_IDENT_VALUE, .value_index = idx}); @@ -8770,7 +8770,7 @@ lookup_global_variable (ident_t *ident) int idx; assign_svalue_no_free(&ref_var, var); /* Add a reference. */ - idx = store_lambda_value(var); + idx = store_lambda_value(&ref_var); ident->u.global.variable = LAMBDA_VARIABLES_COUNT; ADD_LAMBDA_VARIABLE((lambda_ident_t){.kind = LAMBDA_IDENT_VALUE, .value_index = idx}); diff --git a/test/t-efuns.c b/test/t-efuns.c index 4f51ea1d3..6e282b13c 100644 --- a/test/t-efuns.c +++ b/test/t-efuns.c @@ -463,6 +463,15 @@ mixed *tests = }))) == "ABCZ"; :) }), + ({ "compile_string (same function multiple times from function)", 0, + (: + return funcall(compile_string(0, "fun1() + fun2() + fun3()", ( + functions: function closure(symbol name) : closure cl = function int() { return 10; } + { + return cl; + }))) == 30; + :) + }), ({ "compile_string (missing function from function)", TF_ERROR, (: compile_string(0, "fun1() + fun2(\"z\")", ( @@ -541,6 +550,15 @@ mixed *tests = }))) == "ABCX" && v2 == "X"; :) }), + ({ "compile_string (same value multiple times from function)", 0, + (: + return funcall(compile_string(0, "a+b+c", ( + variables: function mixed(symbol name) : int value = 10 + { + return &value; + }))) == 30; + :) + }), ({ "compile_string (variable, missing in function)", TF_ERROR, (: string v1 = "ABC"; @@ -628,6 +646,16 @@ mixed *tests = })); :) }), + ({ "compile_string (same struct multiple times from function)", 0, + (: + return deep_eq(funcall(compile_string(0, + "({ ( ({10})), ( ({20})), ( ({30})) })", ( + structs: function struct mixed(symbol name) : struct test_struct s = () + { + return s; + }))), ({ ( ({10})), ( ({20})), ( ({30})) })); + :) + }), ({ "compile_string (struct from object)", 0, (: return deep_eq(funcall(compile_string(0, "( ({10}))", ( use_object_structs: 1))), From 7e9cf6a472639028908e250973e8c544e5b98560 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Mon, 2 Oct 2023 20:46:46 +0200 Subject: [PATCH 02/21] Disregard negative zeros for floating point numbers As LDMud doesn't support NaN and +/-Inf anyways, supporting negative zeros is only confusing. So collapse them into normal zeroes. (This was the case before LDMud 3.5.0 with the old float format anyways.) --- src/svalue.h | 3 ++- test/t-operators.c | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/svalue.h b/src/svalue.h index 260e61696..f089389e4 100644 --- a/src/svalue.h +++ b/src/svalue.h @@ -501,7 +501,8 @@ static INLINE int32_t SPLIT_DOUBLE(double doublevalue, int *int_p) { } static INLINE void STORE_DOUBLE(svalue_t *dest, double doublevalue) { - dest->u.float_number = doublevalue; + /* Convert negative zeros to positive ones before storing. */ + dest->u.float_number = doublevalue + 0.0; } #else /* --- The portable format, used if no other format is defined */ diff --git a/test/t-operators.c b/test/t-operators.c index 570d82b55..e9eadf776 100644 --- a/test/t-operators.c +++ b/test/t-operators.c @@ -26,6 +26,8 @@ mixed *tests = ({ ({ "int* |= int*", 0, (: int *val = ({0,1,2,3}); val |= ({4,2,1}); return deep_eq(val, ({0,1,2,3,4})); :) }), ({ "int* ^= int*", 0, (: int *val = ({0,1,2,3}); val ^= ({4,2,1}); return deep_eq(val, ({0,3,4})); :) }), + ({ "negative zero", 0, (: float val = -1.0 * 0.0; return val == 0.0 && !('-' in to_string(val)); :) }), + ({ "mapping + mapping 1", 0, (: deep_eq(([0,1,2,3]) + ([4,2,1]), ([0,1,2,3,4])) :) }), ({ "mapping + mapping 2", 0, (: deep_eq(([0:"a", 1:"b", 2:"c", 3:"d"]) + ([4:"x", 2:"y", 1:"z"]), ([0:"a", 1:"z", 2:"y", 3:"d", 4: "x"])) :) }), ({ "mapping + mapping 3", 0, (: deep_eq((['a:0;"a", 'b:1;"b", 'c:2;"c", 'd:3;"d"]) + (['d:4;"x", 'e:2;"y", 'f:1;"z"]), (['a:0;"a", 'b:1;"b", 'c:2;"c", 'd:4;"x", 'e:2;"y", 'f:1;"z"])) :) }), From bcdbfd1d3b841154e112f0c310c8f5852aa5da3b Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Mon, 2 Oct 2023 21:13:08 +0200 Subject: [PATCH 03/21] Added missing docs for coroutinep() and lpctype() --- doc/efun/bytesp | 5 +++-- doc/efun/clonep | 7 ++++--- doc/efun/closurep | 5 +++-- doc/efun/coroutinep | 13 +++++++++++++ doc/efun/floatp | 5 +++-- doc/efun/intp | 5 +++-- doc/efun/lpctypep | 13 +++++++++++++ doc/efun/lwobjectp | 6 +++--- doc/efun/mappingp | 5 +++-- doc/efun/objectp | 6 +++--- doc/efun/pointerp | 5 +++-- doc/efun/referencep | 6 +++--- doc/efun/stringp | 5 +++-- doc/efun/structp | 6 +++--- doc/efun/symbolp | 6 +++--- doc/efun/typeof | 6 ++++-- 16 files changed, 70 insertions(+), 34 deletions(-) create mode 100644 doc/efun/coroutinep create mode 100644 doc/efun/lpctypep diff --git a/doc/efun/bytesp b/doc/efun/bytesp index 89e1a2a9a..a6b952473 100644 --- a/doc/efun/bytesp +++ b/doc/efun/bytesp @@ -8,5 +8,6 @@ HISTORY Introducted in LDMud 3.6.0. SEE ALSO - clonep(E), closurep(E), floatp(E), mappingp(E), objectp(E), intp(E), - referencep(E), pointerp(E), stringp(E), structp(E), symbolp(E) + clonep(E), closurep(E), coroutinep(E), floatp(E), intp(E), + lpctypep(E), lwobjectp(E), mappingp(E), objectp(E), pointerp(E), + referencep(E), stringp(E), structp(E), symbolp(E) diff --git a/doc/efun/clonep b/doc/efun/clonep index 181af784b..06534d2d7 100644 --- a/doc/efun/clonep +++ b/doc/efun/clonep @@ -24,6 +24,7 @@ HISTORY with replaced programs no longer count as clones. SEE ALSO - load_name(E), clone_object(E), clones(E), bytesp(E), closurep(E), - floatp(E), mappingp(E), objectp(E), intp(E), referencep(E), - pointerp(E), stringp(E), symbolp(E), structp(E) + load_name(E), clone_object(E), clones(E), bytesp(E), + closurep(E), coroutinep(E), floatp(E), intp(E), lpctypep(E), + lwobjectp(E), mappingp(E), objectp(E), pointerp(E), referencep(E), + stringp(E), structp(E), symbolp(E) diff --git a/doc/efun/closurep b/doc/efun/closurep index 8c7e9719e..3bb8f600b 100644 --- a/doc/efun/closurep +++ b/doc/efun/closurep @@ -8,5 +8,6 @@ HISTORY Introduced in 3.2@70 SEE ALSO - bytesp(E), clonep(E), floatp(E), mappingp(E), objectp(E), intp(E), - referencep(E), pointerp(E), stringp(E), symbolp(E), structp(E) + bytesp(E), clonep(E), coroutinep(E), floatp(E), intp(E), lpctypep(E), + lwobjectp(E), mappingp(E), objectp(E), pointerp(E), referencep(E), + stringp(E), structp(E), symbolp(E) diff --git a/doc/efun/coroutinep b/doc/efun/coroutinep new file mode 100644 index 000000000..373d218b9 --- /dev/null +++ b/doc/efun/coroutinep @@ -0,0 +1,13 @@ +SYNOPSIS + int coroutinep(mixed arg) + +DESCRIPTION + Returns 1 if the argument is a coroutine. + +HISTORY + Introduced in LDMud 3.6.5. + +SEE ALSO + bytesp(E), clonep(E), closurep(E), floatp(E), intp(E), lpctypep(E), + lwobjectp(E), mappingp(E), objectp(E), pointerp(E), referencep(E), + stringp(E), structp(E), symbolp(E) diff --git a/doc/efun/floatp b/doc/efun/floatp index 80c738fce..2bcd39613 100644 --- a/doc/efun/floatp +++ b/doc/efun/floatp @@ -5,5 +5,6 @@ DESCRIPTION Returns 1 if the arg is a floating point number, 0 else. SEE ALSO - bytesp(E), clonep(E), closurep(E), mappingp(E), objectp(E), intp(E), - referencep(E), pointerp(E), stringp(E), symbolp(E), structp(E) + bytesp(E), clonep(E), closurep(E), coroutinep(E), intp(E), + lpctypep(E), lwobjectp(E), mappingp(E), objectp(E), pointerp(E), + referencep(E), stringp(E), structp(E), symbolp(E) diff --git a/doc/efun/intp b/doc/efun/intp index b4da9caa1..5c14516b0 100644 --- a/doc/efun/intp +++ b/doc/efun/intp @@ -5,5 +5,6 @@ DESCRIPTION Return 1 if arg is an integer number. SEE ALSO - bytesp(E), clonep(E), closurep(E), floatp(E), mappingp(E), objectp(E), - referencep(E), pointerp(E), stringp(E), symbolp(E), structp(E) + bytesp(E), clonep(E), closurep(E), coroutinep(E), floatp(E), + lpctypep(E), lwobjectp(E), mappingp(E), objectp(E), pointerp(E), + referencep(E), stringp(E), structp(E), symbolp(E) diff --git a/doc/efun/lpctypep b/doc/efun/lpctypep new file mode 100644 index 000000000..2f52135b8 --- /dev/null +++ b/doc/efun/lpctypep @@ -0,0 +1,13 @@ +SYNOPSIS + int lpctypep(mixed arg) + +DESCRIPTION + Returns 1 if the argument is an LPC type. + +HISTORY + Introduced in LDMud 3.6.7. + +SEE ALSO + bytesp(E), clonep(E), closurep(E), coroutinep(E), floatp(E), intp(E), + lwobjectp(E), mappingp(E), objectp(E), pointerp(E), referencep(E), + stringp(E), structp(E), symbolp(E) diff --git a/doc/efun/lwobjectp b/doc/efun/lwobjectp index f6b4b3395..a2f426039 100644 --- a/doc/efun/lwobjectp +++ b/doc/efun/lwobjectp @@ -5,6 +5,6 @@ DESCRIPTION Return 1 if arg is a lightweight object. SEE ALSO - bytesp(E), clonep(E), closurep(E), floatp(E), intp(E), mappingp(E), - objectp(E), pointerp(E), referencep(E), stringp(E), structp(E), - symbolp(E) + bytesp(E), clonep(E), closurep(E), coroutinep(E), floatp(E), intp(E), + lpctypep(E), mappingp(E), objectp(E), pointerp(E), referencep(E), + stringp(E), structp(E), symbolp(E) diff --git a/doc/efun/mappingp b/doc/efun/mappingp index 2d261a076..2442038b4 100644 --- a/doc/efun/mappingp +++ b/doc/efun/mappingp @@ -7,5 +7,6 @@ DESCRIPTION SEE ALSO mkmapping(E), m_indices(E), m_values(E), m_add(E), m_delete(E), sizeof(E), widthof(E), bytesp(E), clonep(E), closurep(E), - floatp(E), objectp(E), intp(E), referencep(E), pointerp(E), - stringp(E), symbolp(E), structp(E) + coroutinep(E), floatp(E), intp(E), lpctypep(E), lwobjectp(E), + objectp(E), pointerp(E), referencep(E), stringp(E), structp(E), + symbolp(E) diff --git a/doc/efun/objectp b/doc/efun/objectp index ba682abfb..2633389ac 100644 --- a/doc/efun/objectp +++ b/doc/efun/objectp @@ -5,6 +5,6 @@ DESCRIPTION Return 1 if arg is an object. SEE ALSO - bytesp(E), clonep(E), closurep(E), floatp(E), intp(E), lwobjectp(E), - mappingp(E), pointerp(E), referencep(E), stringp(E), structp(E), - symbolp(E) + bytesp(E), clonep(E), closurep(E), coroutinep(E), floatp(E), intp(E), + lpctypep(E), lwobjectp(E), mappingp(E), pointerp(E), referencep(E), + stringp(E), structp(E), symbolp(E) diff --git a/doc/efun/pointerp b/doc/efun/pointerp index 6794c6f3b..ee04d0bb7 100644 --- a/doc/efun/pointerp +++ b/doc/efun/pointerp @@ -5,5 +5,6 @@ DESCRIPTION Return 1 if arg is a pointer, i.e. an array. SEE ALSO - bytesp(E), clonep(E), closurep(E), floatp(E), mappingp(E), objectp(E), - intp(E), referencep(E), stringp(E), symbolp(E), structp(E) + bytesp(E), clonep(E), closurep(E), coroutinep(E), floatp(E), intp(E), + lpctypep(E), lwobjectp(E), mappingp(E), objectp(E), referencep(E), + stringp(E), structp(E), symbolp(E) diff --git a/doc/efun/referencep b/doc/efun/referencep index d4c4d100a..96fbc26e6 100644 --- a/doc/efun/referencep +++ b/doc/efun/referencep @@ -9,6 +9,6 @@ DESCRIPTION a.g. referencep(&x). SEE ALSO - references(LPC), bytesp(E), clonep(E), closurep(E), floatp(E), - mappingp(E), objectp(E), intp(E), pointerp(E), stringp(E), - symbolp(E), structp(E) + references(LPC), bytesp(E), clonep(E), closurep(E), coroutinep(E), + floatp(E), intp(E), lpctypep(E), lwobjectp(E), mappingp(E), + objectp(E), pointerp(E), stringp(E), structp(E), symbolp(E) diff --git a/doc/efun/stringp b/doc/efun/stringp index b9fee8b1e..50e399e6d 100644 --- a/doc/efun/stringp +++ b/doc/efun/stringp @@ -5,5 +5,6 @@ DESCRIPTION Return 1 if arg is a string. SEE ALSO - bytesp(E), clonep(E), closurep(E), floatp(E), mappingp(E), objectp(E), - intp(E), referencep(E), pointerp(E), symbolp(E), structp(E) + bytesp(E), clonep(E), closurep(E), coroutinep(E), floatp(E), intp(E), + lpctypep(E), lwobjectp(E), mappingp(E), objectp(E), pointerp(E), + referencep(E), structp(E), symbolp(E) diff --git a/doc/efun/structp b/doc/efun/structp index 946f32eda..9a97fae7e 100644 --- a/doc/efun/structp +++ b/doc/efun/structp @@ -8,6 +8,6 @@ HISTORY Introducted in LDMud 3.3.273. SEE ALSO - baseof(E), bytesp(E), clonep(E), closurep(E), floatp(E), - mappingp(E), objectp(E), intp(E), referencep(E), pointerp(E), - stringp(E), symbolp(E) + baseof(E), bytesp(E), clonep(E), closurep(E), coroutinep(E), + floatp(E), intp(E), lpctypep(E), lwobjectp(E), mappingp(E), + objectp(E), pointerp(E), referencep(E), stringp(E), symbolp(E) diff --git a/doc/efun/symbolp b/doc/efun/symbolp index 1c6aaa260..66ee03afd 100644 --- a/doc/efun/symbolp +++ b/doc/efun/symbolp @@ -11,6 +11,6 @@ HISTORY Introduced in 3.2@70 SEE ALSO - quote(E), bytesp(E), clonep(E), closurep(E), floatp(E), - mappingp(E), objectp(E), intp(E), referencep(E), pointerp(E), - stringp(E), structp(E) + quote(E), bytesp(E), clonep(E), closurep(E), coroutinep(E), + floatp(E), intp(E), lpctypep(E), lwobjectp(E), mappingp(E), + objectp(E), pointerp(E), referencep(E), stringp(E), structp(E) diff --git a/doc/efun/typeof b/doc/efun/typeof index 3018eb13c..888080b1c 100644 --- a/doc/efun/typeof +++ b/doc/efun/typeof @@ -11,5 +11,7 @@ HISTORY Introduced in 3.2@63. SEE ALSO - get_type_info(E), intp(E), objectp(E), floatp(E), pointerp(E), - closurep(E), symbolp(E), stringp(E), mappingp(E), bytesp(E) + get_type_info(E), bytesp(E), clonep(E), closurep(E), coroutinep(E), + floatp(E), intp(E), lpctypep(E), lwobjectp(E), mappingp(E), + objectp(E), pointerp(E), referencep(E), stringp(E), structp(E), + symbolp(E) From 31484d09ab7ba5dd837833fd6a493d04b62ed27d Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Tue, 10 Oct 2023 23:35:25 +0200 Subject: [PATCH 04/21] Added sub-namespace for registered efuns and types ldmud.registered_efuns contains all registered Python efuns and ldmud.registered_types provides all registered Python types. Those are read-only, for manipulation there is (un)register_efun/type(). --- doc/concepts/python | 8 +- src/pkg-python.c | 352 +++++++++++++++++++++++++++++++++++++++ test/t-python/startup.py | 36 +++- 3 files changed, 394 insertions(+), 2 deletions(-) diff --git a/doc/concepts/python b/doc/concepts/python index 98d0b5e63..db2f1df2b 100644 --- a/doc/concepts/python +++ b/doc/concepts/python @@ -356,9 +356,15 @@ LDMUD MODULE - efuns This namespace contains all original efuns (without any - registered python efuns or simul-efuns). These can be called + registered Python efuns or simul-efuns). These can be called as a regular function. + - registered_efuns + Contains all registered Python efuns. + + - registered_types + Contains all registered Python types. + There is mapping of LPC values to Python values for the following types: int <-> int(, bool) diff --git a/src/pkg-python.c b/src/pkg-python.c index b3a4c442c..3db1646c6 100644 --- a/src/pkg-python.c +++ b/src/pkg-python.c @@ -11813,6 +11813,352 @@ create_efun_namespace () return (PyObject*)&ldmud_efun_namespace; } /* create_efun_namespace() */ +/*-------------------------------------------------------------------------*/ +static PyObject* +ldmud_registered_efuns_getattro (PyObject *val, PyObject *name) + +/* Return the callable for a registered efun. + */ + +{ + PyObject *result; + bool error; + string_t* efunname; + ident_t *ident; + + /* First check real attributes... */ + result = PyObject_GenericGetAttr(val, name); + if (result || !PyErr_ExceptionMatches(PyExc_AttributeError)) + return result; + + PyErr_Clear(); + + /* And now look up registered efuns. */ + efunname = find_tabled_python_string(name, "efun name", &error); + if (error) + return NULL; + + if (efunname) + { + ident = find_shared_identifier_mstr(efunname, I_TYPE_GLOBAL, 0); + while (ident && ident->type != I_TYPE_GLOBAL) + ident = ident->inferior; + + if (ident && ident->type == I_TYPE_GLOBAL && ident->u.global.python_efun != I_GLOBAL_PYTHON_EFUN_OTHER) + { + result = python_efun_table[ident->u.global.python_efun].callable; + if (result) + { + Py_INCREF(result); + return result; + } + } + } + + PyErr_Format(PyExc_AttributeError, "No such registered efun: '%U'", name); + return NULL; +} /* ldmud_registered_efuns_getattro() */ + +/*-------------------------------------------------------------------------*/ +static PyObject * +ldmud_registered_efuns_dir (PyObject *self) + +/* Returns a list of all attributes, this includes all registered efun names. + */ + +{ + PyObject *result; + PyObject *attrs = get_class_dir(self); + + if (attrs == NULL) + return NULL; + + /* Now add all registered efuns. */ + for (ident_t *ident = all_python_efuns; ident; ident = ident->next_all) + { + if (python_efun_table[ident->u.global.python_efun].callable != NULL) + { + PyObject *efunname = PyUnicode_FromStringAndSize(get_txt(ident->name), mstrsize(ident->name)); + + if (efunname == NULL) + { + PyErr_Clear(); + continue; + } + + if (PySet_Add(attrs, efunname) < 0) + PyErr_Clear(); + Py_DECREF(efunname); + } + } + + /* And return the keys of our dict. */ + result = PySequence_List(attrs); + Py_DECREF(attrs); + return result; +} /* ldmud_registered_efuns_dir() */ + +/*-------------------------------------------------------------------------*/ +static PyObject * +ldmud_registered_efuns_dict (ldmud_program_t *self, void *closure) + +/* Returns a list of all registered efuns. + */ + +{ + PyObject *result, *dict = PyDict_New(); + if (!dict) + return NULL; + + for (ident_t *ident = all_python_efuns; ident; ident = ident->next_all) + { + PyObject * callable = python_efun_table[ident->u.global.python_efun].callable; + if (callable != NULL) + { + PyObject *efunname = PyUnicode_FromStringAndSize(get_txt(ident->name), mstrsize(ident->name)); + + if (efunname == NULL) + { + PyErr_Clear(); + continue; + } + + if (PyDict_SetItem(dict, efunname, callable) < 0) + PyErr_Clear(); + Py_DECREF(efunname); + } + } + + result = PyDictProxy_New(dict); + Py_DECREF(dict); + return result; +} /* ldmud_registered_efuns_dict() */ + +/*-------------------------------------------------------------------------*/ +static PyMethodDef ldmud_registered_efuns_methods[] = +{ + { + "__dir__", + (PyCFunction)ldmud_registered_efuns_dir, METH_NOARGS, + "__dir__() -> List\n\n" + "Returns a list of all attributes." + }, + + {NULL} +}; + +static PyGetSetDef ldmud_registered_efuns_getset [] = { + {"__dict__", (getter)ldmud_registered_efuns_dict, NULL, NULL}, + {NULL} +}; + +static PyTypeObject ldmud_registered_efuns_type = +{ + PyVarObject_HEAD_INIT(NULL, 0) + "ldmud.registered_efuns", /* tp_name */ + 0, /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + ldmud_registered_efuns_getattro, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Registered Python efuns", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + ldmud_registered_efuns_methods, /* tp_methods */ + 0, /* tp_members */ + ldmud_registered_efuns_getset, /* tp_getset */ +}; + +/*-------------------------------------------------------------------------*/ +static PyObject* +ldmud_registered_types_getattro (PyObject *val, PyObject *name) + +/* Return the class for a registered type. + */ + +{ + PyObject *result; + bool error; + string_t* typename; + ident_t *ident; + + /* First check real attributes... */ + result = PyObject_GenericGetAttr(val, name); + if (result || !PyErr_ExceptionMatches(PyExc_AttributeError)) + return result; + + PyErr_Clear(); + + /* And now look up registered types. */ + typename = find_tabled_python_string(name, "type name", &error); + if (error) + return NULL; + + if (typename) + { + ident = find_shared_identifier_mstr(typename, I_TYPE_PYTHON_TYPE, 0); + while (ident && ident->type != I_TYPE_PYTHON_TYPE) + ident = ident->inferior; + + if (ident && ident->type == I_TYPE_PYTHON_TYPE) + { + result = python_type_table[ident->u.python_type_id]->pytype; + if (result) + { + Py_INCREF(result); + return result; + } + } + } + + PyErr_Format(PyExc_AttributeError, "No such registered type: '%U'", name); + return NULL; +} /* ldmud_registered_types_getattro() */ + +/*-------------------------------------------------------------------------*/ +static PyObject * +ldmud_registered_types_dir (PyObject *self) + +/* Returns a list of all attributes, this includes all registered type names. + */ + +{ + PyObject *result; + PyObject *attrs = get_class_dir(self); + + if (attrs == NULL) + return NULL; + + /* Now add all registered types. */ + for (ident_t *ident = all_python_types; ident; ident = ident->next_all) + { + if (python_type_table[ident->u.python_type_id]->pytype != NULL) + { + PyObject *typename = PyUnicode_FromStringAndSize(get_txt(ident->name), mstrsize(ident->name)); + + if (typename == NULL) + { + PyErr_Clear(); + continue; + } + + if (PySet_Add(attrs, typename) < 0) + PyErr_Clear(); + Py_DECREF(typename); + } + } + + /* And return the keys of our dict. */ + result = PySequence_List(attrs); + Py_DECREF(attrs); + return result; +} /* ldmud_registered_types_dir() */ + +/*-------------------------------------------------------------------------*/ +static PyObject * +ldmud_registered_types_dict (ldmud_program_t *self, void *closure) + +/* Returns a list of all registered types. + */ + +{ + PyObject *result, *dict = PyDict_New(); + if (!dict) + return NULL; + + for (ident_t *ident = all_python_types; ident; ident = ident->next_all) + { + PyObject *type = python_type_table[ident->u.python_type_id]->pytype; + if (type != NULL) + { + PyObject *typename = PyUnicode_FromStringAndSize(get_txt(ident->name), mstrsize(ident->name)); + + if (typename == NULL) + { + PyErr_Clear(); + continue; + } + + if (PyDict_SetItem(dict, typename, type) < 0) + PyErr_Clear(); + Py_DECREF(typename); + } + } + + result = PyDictProxy_New(dict); + Py_DECREF(dict); + return result; +} /* ldmud_registered_types_dict() */ + +/*-------------------------------------------------------------------------*/ +static PyMethodDef ldmud_registered_types_methods[] = +{ + { + "__dir__", + (PyCFunction)ldmud_registered_types_dir, METH_NOARGS, + "__dir__() -> List\n\n" + "Returns a list of all attributes." + }, + + {NULL} +}; + +static PyGetSetDef ldmud_registered_types_getset [] = { + {"__dict__", (getter)ldmud_registered_types_dict, NULL, NULL}, + {NULL} +}; + +static PyTypeObject ldmud_registered_types_type = +{ + PyVarObject_HEAD_INIT(NULL, 0) + "ldmud.registered_types", /* tp_name */ + 0, /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + ldmud_registered_types_getattro, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Registered Python types", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + ldmud_registered_types_methods, /* tp_methods */ + 0, /* tp_members */ + ldmud_registered_types_getset, /* tp_getset */ +}; + /*-------------------------------------------------------------------------*/ /* Module definition for the ldmud builtin module */ @@ -12165,6 +12511,10 @@ init_ldmud_module () return NULL; if (PyType_Ready(&ldmud_lvalue_struct_members_type) < 0) return NULL; + if (PyType_Ready(&ldmud_registered_efuns_type) < 0) + return NULL; + if (PyType_Ready(&ldmud_registered_types_type) < 0) + return NULL; ldmud_interrupt_exception_type.tp_base = (PyTypeObject *) PyExc_RuntimeError; if (PyType_Ready(&ldmud_interrupt_exception_type) < 0) @@ -12201,6 +12551,8 @@ init_ldmud_module () if (!efuns) return NULL; PyModule_AddObject(module, "efuns", efuns); + PyModule_AddObject(module, "registered_efuns", ldmud_registered_efuns_type.tp_alloc(&ldmud_registered_efuns_type, 0)); + PyModule_AddObject(module, "registered_types", ldmud_registered_types_type.tp_alloc(&ldmud_registered_types_type, 0)); return module; } /* init_ldmud_module() */ diff --git a/test/t-python/startup.py b/test/t-python/startup.py index 096d1f5d9..be15e9e87 100644 --- a/test/t-python/startup.py +++ b/test/t-python/startup.py @@ -1002,7 +1002,6 @@ def testUnions(self): self.assertIn(ldmud.String, ldmud.Integer | ldmud.String) self.assertEqual(ldmud.LPCType((ldmud.Integer, ldmud.String)), ldmud.Integer | ldmud.String) - class TestEfuns(unittest.TestCase): def testDir(self): self.assertGreater(len(dir(ldmud.efuns)), 200) @@ -1012,6 +1011,41 @@ def testCalls(self): self.assertEqual(ldmud.efuns.call_other(master, "master_fun"), 54321) self.assertEqual(ldmud.efuns.object_name(master), "/master") +class TestRegisteredEfuns(unittest.TestCase): + def testDir(self): + self.assertIn("python_test", dir(ldmud.registered_efuns)) + self.assertGreater(len(dir(ldmud.registered_efuns)), 16) + + def testDict(self): + self.assertIn("python_test", ldmud.registered_efuns.__dict__) + self.assertEqual(ldmud.registered_efuns.__dict__["python_test"], python_test) + self.assertEqual(len(ldmud.registered_efuns.__dict__), 16) + + def testAttribute(self): + self.assertTrue(hasattr(ldmud.registered_efuns, 'python_test')) + self.assertEqual(ldmud.registered_efuns.python_test, python_test) + with self.assertRaises(AttributeError): + ldmud.registered_efuns.doesnt_exist + with self.assertRaises(AttributeError): + # Unregistered Efun + ldmud.registered_efuns.abc + +class TestRegisteredTypes(unittest.TestCase): + def testDir(self): + self.assertIn("bigint", dir(ldmud.registered_types)) + self.assertGreater(len(dir(ldmud.registered_types)), 3) + + def testDict(self): + self.assertIn("bigint", ldmud.registered_types.__dict__) + self.assertEqual(ldmud.registered_types.__dict__["bigint"], bigint) + self.assertEqual(len(ldmud.registered_types.__dict__), 3) + + def testAttribute(self): + self.assertTrue(hasattr(ldmud.registered_types, 'bigint')) + self.assertEqual(ldmud.registered_types.bigint, bigint) + with self.assertRaises(AttributeError): + ldmud.registered_types.doesnt_exist + def python_test(): """Run the python test cases.""" From ccce474d32541f7cf38c81f8016f0488a20a2100 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Sun, 15 Oct 2023 17:58:05 +0200 Subject: [PATCH 05/21] Check for uncallable closures on callback setup Efuns like call_out, add_action, map or filter will throw immediately when they're given an uncallable closure for callback. Also improved structure of call_lambda() and fixed giving an error message without a control stack entry. (Fixes #908) --- src/actions.c | 1 + src/call_out.c | 1 + src/closure.c | 49 ++++++++++++++++++ src/closure.h | 1 + src/comm.c | 1 + src/interpret.c | 117 ++++++++++++++++++++++--------------------- src/simulate.c | 8 +-- src/simulate.h | 2 +- src/svalue.h | 4 -- test/inc/deep_eq.inc | 2 +- test/t-efuns.c | 65 ++++++++++++++++++++++-- 11 files changed, 180 insertions(+), 71 deletions(-) diff --git a/src/actions.c b/src/actions.c index a859d01a0..ad16c974c 100644 --- a/src/actions.c +++ b/src/actions.c @@ -1249,6 +1249,7 @@ e_add_action (svalue_t *func, svalue_t *cmd, p_int flag) { error_index = setup_closure_callback(&(p->cb), func , 0, NULL + , true ); func->type = T_INVALID; /* So that an error won't free it again. */ } diff --git a/src/call_out.c b/src/call_out.c index 76aff22b3..9c3b31177 100644 --- a/src/call_out.c +++ b/src/call_out.c @@ -186,6 +186,7 @@ v_call_out (svalue_t *sp, int num_arg) else error_index = setup_closure_callback(&(cop->fun), arg , num_arg-2, arg+2 + , true ); if (error_index >= 0) diff --git a/src/closure.c b/src/closure.c index d1b5539c4..3b618b870 100644 --- a/src/closure.c +++ b/src/closure.c @@ -5779,6 +5779,55 @@ is_undef_closure (svalue_t *sp) || sp->x.closure_type == F_UNDEF+CLOSURE_EFUN+CLOSURE_LWO); } /* is_undef_closure() */ +/*-------------------------------------------------------------------------*/ +bool +is_closure_callable (svalue_t *cl, bool expect_code) + +/* Returns true if is a callable closure. If is true, + * also identifier closures will not be accepted. + */ + +{ + assert(cl->type == T_CLOSURE); + + int i = cl->x.closure_type; + switch (i) + { + case CLOSURE_LFUN: + case CLOSURE_BOUND_LAMBDA: + case CLOSURE_LAMBDA: + return true; + + case CLOSURE_UNBOUND_LAMBDA: + return false; + + case CLOSURE_IDENTIFIER: + return !expect_code; + + default: + if (i >= 0) + fatal("Invalid closure type: %d.\n", i); + if (i < CLOSURE_LWO) + i -= CLOSURE_LWO; + switch (i & -0x0800) + { + case CLOSURE_EFUN: + case CLOSURE_SIMUL_EFUN: +#ifdef USE_PYTHON + case CLOSURE_PYTHON_EFUN: +#endif + return true; + + case CLOSURE_OPERATOR: + return false; + + default: + fatal("Invalid closure type: %d.\n", cl->x.closure_type); + } + } + return false; /* NOTREACHED */ +} /* is_closure_callable() */ + /*-------------------------------------------------------------------------*/ void closure_lookup_lfun_prog ( lfun_closure_t * l diff --git a/src/closure.h b/src/closure.h index 09d07d861..7b6e38c86 100644 --- a/src/closure.h +++ b/src/closure.h @@ -152,6 +152,7 @@ extern void closure_literal(svalue_t *dest, int ix, unsigned short inhIndex extern void closure_identifier (svalue_t *dest, svalue_t obj, int ix, Bool raise_error); extern void free_closure(svalue_t *svp); extern Bool is_undef_closure (svalue_t *sp); +extern bool is_closure_callable (svalue_t *cl, bool expect_code); extern void closure_lookup_lfun_prog (lfun_closure_t *l , program_t ** pProg , string_t ** pName , Bool * pIsInherited); extern const char * closure_operator_to_string (int type); extern const char * closure_efun_to_string (int type); diff --git a/src/comm.c b/src/comm.c index debe1e3d6..cde1e90a3 100644 --- a/src/comm.c +++ b/src/comm.c @@ -7346,6 +7346,7 @@ v_input_to (svalue_t *sp, int num_arg) else if (arg[0].type == T_CLOSURE) error_index = setup_closure_callback(&(it->fun), arg , extra, extra_arg + , true ); else error_index = 1; diff --git a/src/interpret.c b/src/interpret.c index 9b6c9e6b3..c2d73cea6 100644 --- a/src/interpret.c +++ b/src/interpret.c @@ -21626,27 +21626,10 @@ int_call_lambda (svalue_t *lsvp, int num_arg, bool external, svalue_t *bind_ob) set_current_object(ob); } - if (i < CLOSURE_SIMUL_EFUN) + switch (i & -0x0800) { - /* It's an operator or efun */ - - if (i == CLOSURE_EFUN + F_UNDEF) - { - /* The closure was discovered to be bound to a destructed - * object and thus disabled. - * This situation should no longer happen - in all situations - * the closure should be zeroed out. - */ - CLEAN_CSP - pop_n_elems(num_arg); - push_number(sp, 0); - inter_sp = sp; - return; - } - #ifdef USE_PYTHON - if (i >= CLOSURE_PYTHON_EFUN && i < CLOSURE_EFUN) - { + case CLOSURE_PYTHON_EFUN: inter_pc = csp->funstart = PYTHON_EFUN_FUNSTART; csp->instruction = i - CLOSURE_PYTHON_EFUN; csp->num_local_variables = 0; @@ -21654,24 +21637,33 @@ int_call_lambda (svalue_t *lsvp, int num_arg, bool external, svalue_t *bind_ob) call_python_efun(i - CLOSURE_PYTHON_EFUN, num_arg); CLEAN_CSP return; - } #endif - i -= CLOSURE_EFUN; - /* Efuns have now a positive value, operators a negative one. - */ - - if (i >= 0 - || instrs[i -= CLOSURE_OPERATOR-CLOSURE_EFUN].min_arg) + case CLOSURE_EFUN: { - /* To call an operator or efun, we have to construct - * a small piece of program with this instruction. - */ bytecode_t code[9]; /* the code fragment */ bytecode_p p; /* the code pointer */ int min, max, def; + i -= CLOSURE_EFUN; + if (i == F_UNDEF) + { + /* The closure was discovered to be bound to a destructed + * object and thus disabled. + * This situation should no longer happen - in all situations + * the closure should be zeroed out. + */ + CLEAN_CSP + pop_n_elems(num_arg); + push_number(sp, 0); + inter_sp = sp; + return; + } + + /* To call an efun, we have to construct a small piece + * of program with this instruction. + */ min = instrs[i].min_arg; max = instrs[i].max_arg; p = code; @@ -21741,45 +21733,56 @@ int_call_lambda (svalue_t *lsvp, int num_arg, bool external, svalue_t *bind_ob) /* The result is on the stack (inter_sp) */ return; } - else - { - /* It is an operator or syntactic marker: fall through - * to uncallable closure type. + + case CLOSURE_OPERATOR: + /* It is an operator or syntactic marker. */ + csp->extern_call = MY_TRUE; + inter_pc = csp->funstart = EFUN_FUNSTART; + csp->instruction = i - CLOSURE_OPERATOR; break; - } - } - else - { - /* simul_efun */ - object_t *ob; - /* Mark the call as sefun closure */ - inter_pc = csp->funstart = SIMUL_EFUN_FUNSTART; - - /* Get the simul_efun object */ - if ( !(ob = simul_efun_object) ) + case CLOSURE_SIMUL_EFUN: { - /* inter_sp == sp */ - if (!assert_simul_efun_object() - || !(ob = simul_efun_object) - ) + /* simul_efun */ + object_t *ob; + + /* Mark the call as sefun closure */ + inter_pc = csp->funstart = SIMUL_EFUN_FUNSTART; + + /* Get the simul_efun object */ + if ( !(ob = simul_efun_object) ) { - csp->extern_call = MY_TRUE; - errorf("Couldn't load simul_efun object\n"); - /* NOTREACHED */ - return; + /* inter_sp == sp */ + if (!assert_simul_efun_object() + || !(ob = simul_efun_object) + ) + { + csp->extern_call = MY_TRUE; + errorf("Couldn't load simul_efun object\n"); + /* NOTREACHED */ + return; + } } + call_simul_efun(i - CLOSURE_SIMUL_EFUN, ob, num_arg); + CLEAN_CSP + + /* The result is on the stack (inter_sp) */ + return; } - call_simul_efun(i - CLOSURE_SIMUL_EFUN, ob, num_arg); - CLEAN_CSP + + default: + fatal("Invalid closure type: %d.\n", lsvp->x.closure_type); } - /* The result is on the stack (inter_sp) */ - return; + break; } } - CLEAN_CSP + /* We need to have at least one stack entry for error handling. */ + if (csp > CONTROL_STACK) + { + CLEAN_CSP + } errorf("Uncallable closure\n"); /* NOTREACHED */ return; diff --git a/src/simulate.c b/src/simulate.c index 5ba39e86b..06d5b3177 100644 --- a/src/simulate.c +++ b/src/simulate.c @@ -4034,10 +4034,12 @@ setup_function_callback_base ( callback_t *cb, svalue_t ob, string_t * fun /*-------------------------------------------------------------------------*/ int setup_closure_callback ( callback_t *cb, svalue_t *cl - , int nargs, svalue_t * args) + , int nargs, svalue_t * args + , bool expect_code) /* Setup the empty/uninitialized callback to hold a closure * call to with the arguments starting from . + * If is true, identifier closures are not allowed. * * Both and the arguments are adopted (taken away from the caller). * @@ -4052,7 +4054,7 @@ setup_closure_callback ( callback_t *cb, svalue_t *cl cb->is_closure = MY_TRUE; transfer_svalue_no_free(&(cb->function.closure), cl); - if (cb->function.closure.x.closure_type == CLOSURE_UNBOUND_LAMBDA) + if (!is_closure_callable(&(cb->function.closure), expect_code)) { /* Uncalleable closure */ error_index = 0; @@ -4119,7 +4121,7 @@ setup_efun_callback_base ( callback_t **cb, svalue_t *args, int nargs if (args[0].type == T_CLOSURE) { memsafe(*cb = xalloc(sizeof(callback_t)) , sizeof(callback_t), "callback structure"); - error_index = setup_closure_callback(*cb, args, nargs-1, args+1); + error_index = setup_closure_callback(*cb, args, nargs-1, args+1, false); } else if (args[0].type == T_STRING) { diff --git a/src/simulate.h b/src/simulate.h index 89b1d87a6..013b6558f 100644 --- a/src/simulate.h +++ b/src/simulate.h @@ -262,7 +262,7 @@ extern void assert_shadow_sent (object_t *ob); extern void init_empty_callback (callback_t *cb); extern int setup_function_callback_base(callback_t *cb, svalue_t ob, string_t *fun, int nargs, svalue_t * args, bool no_warn); #define setup_function_callback(cb,ob,fun,nargs,args) setup_function_callback_base(cb,ob,fun,nargs,args,false) -extern int setup_closure_callback(callback_t *cb, svalue_t *cl, int nargs, svalue_t * args); +extern int setup_closure_callback(callback_t *cb, svalue_t *cl, int nargs, svalue_t * args, bool expect_code); extern int setup_efun_callback_base ( callback_t **cb, svalue_t *args, int nargs, Bool bNoObj); #define setup_efun_callback(cb,args,nargs) setup_efun_callback_base(cb,args,nargs,MY_FALSE) #define setup_efun_callback_noobj(cb,args,nargs) setup_efun_callback_base(cb,args,nargs,MY_TRUE) diff --git a/src/svalue.h b/src/svalue.h index f089389e4..1c535b4c3 100644 --- a/src/svalue.h +++ b/src/svalue.h @@ -362,10 +362,6 @@ struct svalue_s /* TRUE if the closure points to actual code. */ -#define CLOSURE_CALLABLE(c) ((c) >= CLOSURE_EFUN && (c) <= CLOSURE_LAMBDA) - /* TRUE if the closure is callable. - */ - /* T_LVALUE secondary information. */ diff --git a/test/inc/deep_eq.inc b/test/inc/deep_eq.inc index 87746e64b..97e8ad7a1 100644 --- a/test/inc/deep_eq.inc +++ b/test/inc/deep_eq.inc @@ -61,7 +61,7 @@ int deep_eq(mixed arg1, mixed arg2) if (CLOSURE_IS_BOUND_LAMBDA(t) || CLOSURE_IS_LAMBDA(t) || CLOSURE_IS_UNBOUND_LAMBDA(t)) - raise_error("Don't know how to compare lambda closures.\n"); + return arg1 == arg2; if (CLOSURE_IS_SIMUL_EFUN(t)) return 1; if (CLOSURE_IS_EFUN(t)) diff --git a/test/t-efuns.c b/test/t-efuns.c index 6e282b13c..500aa4a90 100644 --- a/test/t-efuns.c +++ b/test/t-efuns.c @@ -97,6 +97,10 @@ mixed *tests = ({ "acos 2", TF_ERROR, (: funcall(#'acos, "1.0") :) }), ({ "acos 3", TF_ERROR, (: acos(1.1) :) }), ({ "acos 4", TF_ERROR, (: acos(-1.1) :) }), + ({ "add_action with missing function", 0, (: last_rt_warning = 0; add_action("ThisFunctionDoesNotExist", "test"); return sizeof(last_rt_warning); :) }), + ({ "add_action with operator closure", TF_ERROR, (: add_action(#'switch, "test"); :) }), + ({ "add_action with identifier closure", TF_ERROR, (: add_action(#'global_var, "test"); :) }), + ({ "add_action with unbound lambda closure", TF_ERROR, (: add_action(unbound_lambda(0,0), "test"); :) }), ({ "all_environment 1", 0, (: object o = clone_object(this_object()); @@ -252,7 +256,10 @@ mixed *tests = ({ "call_direct_resolved array 1", 0, (: int* result; return deep_eq(call_direct_resolved(&result, ({clone,clone,object_name(clone),this_object(),0}), "f", 10, ({ 20 })), ({1, 1, 1, 1, 0})) && deep_eq(result, ({ 11, 11, 11, 11, 0})); :) }), ({ "call_direct_resolved array 2", 0, (: int* result; return deep_eq(call_direct_resolved(&result, ({clone,clone,object_name(clone),this_object(),0}), "g", 10, ({ 20 })), ({0, 0, 0, 0, 0})) && deep_eq(result, ({ 0, 0, 0, 0, 0})); :) }), ({ "call_direct_resolved array 3", 0, (: int* result; return deep_eq(call_direct_resolved(&result, ({clone,clone,object_name(clone),this_object(),0}), "h", 10, ({ 20 })), ({0, 0, 0, 0, 0})) && deep_eq(result, ({ 0, 0, 0, 0, 0})); :) }), - ({ "call_out", 0, (: last_rt_warning = 0; call_out("ThisFunctionDoesNotExist", 10); return sizeof(last_rt_warning); :) }), + ({ "call_out with missing function",0, (: last_rt_warning = 0; call_out("ThisFunctionDoesNotExist", 10); return sizeof(last_rt_warning); :) }), + ({ "call_out with operator closure", TF_ERROR, (: call_out(#'switch, 0); :) }), + ({ "call_out with identifier closure", TF_ERROR, (: call_out(#'global_var, 0); :) }), + ({ "call_out with unbound lambda closure", TF_ERROR, (: call_out(unbound_lambda(0,0), 0); :) }), ({ "catch 1", 0, (: catch(throw(''X)) == ''X :) }), ({ "catch 2", 0, (: catch(raise_error("X")) == "*X" :) }), ({ "catch 3", 0, (: catch(1+1) == 0 :) }), @@ -448,6 +455,27 @@ mixed *tests = ])))) == "ABCZ"; :) }), + ({ "compile_string (uncallable closure from mapping) 1", TF_ERROR, + (: + funcall(compile_string(0, "fun1() + fun2(\"z\")", ( + functions: + ([ + 'fun1: #'switch, + 'fun2: unbound_lambda(0,0), + ])))); + :) + }), + ({ "compile_string (uncallable closure from mapping) 2", 0, + (: + closure cl = unbound_lambda(0,0); + return deep_eq(funcall(compile_string(0, "({ #'fun1, #'fun2 })", ( + functions: + ([ + 'fun1: #'switch, + 'fun2: cl, + ])))), ({ #'switch, cl })); + :) + }), ({ "compile_string (function from function)", 0, (: return funcall(compile_string(0, "fun1() + fun2(\"z\")", ( @@ -957,6 +985,10 @@ mixed *tests = return 1; :) }), + ({ "input_to with missing function", 0, (: last_rt_warning = 0; input_to("ThisFunctionDoesNotExist"); return sizeof(last_rt_warning); :) }), + ({ "input_to with operator closure", TF_ERROR, (: input_to(#'switch); :) }), + ({ "input_to with identifier closure", TF_ERROR, (: input_to(#'global_var); :) }), + ({ "input_to with unbound lambda closure", TF_ERROR, (: input_to(unbound_lambda(0,0)); :) }), ({ "save_object 1", 0, (: stringp(save_object()) :) }), /* Bug #594 */ ({ "strstr 01", 0, (: strstr("","") == 0 :) }), /* Bug #536 */ ({ "strstr 02", 0, (: strstr("","", 1) == -1 :) }), @@ -982,7 +1014,7 @@ mixed *tests = ({ "text_width 11", 0, (: text_width("\U0001F1E9\U0001F1EA") == 2 :) }), ({ "text_width 12", 0, (: text_width("\u1100\u1161\u11ab") == 2 :) }), ({ "this_object", 0, (: this_object(({})...) == this_object() :) }), - ({ "this_player", 0, (: this_player(({})...) == 0 :) }), + ({ "this_player", 0, (: this_player(({})...) == this_object() :) }), ({ "hash string (MD5)", 0, (: hash(TLS_HASH_MD5, "line 13: Warning: Missing " "'return ' statement") == @@ -1082,27 +1114,44 @@ mixed *tests = return res; :) }), - ({ "filter array", TF_ERROR, (: filter(({1,2,3}), unbound_lambda(0,0), ({4,5,6})) :) }), - ({ "filter mapping", TF_ERROR, (: filter(([1,2,3]), unbound_lambda(0,0), ([4,5,6])) :) }), + ({ "filter string with operator closure", TF_ERROR, (: filter("abc", #'switch, "xyz") :) }), + ({ "filter string with unbound lambda", TF_ERROR, (: filter("abc", unbound_lambda(0,0), "xyz") :) }), + ({ "filter string with identifier closure", 0, (: global_var = 0; return filter("abc", #'global_var, "xyz")==""; :) }), + ({ "filter array with operator closure", TF_ERROR, (: filter(({1,2,3}), #'switch, ({4,5,6})) :) }), + ({ "filter array with unbound lambda", TF_ERROR, (: filter(({1,2,3}), unbound_lambda(0,0), ({4,5,6})) :) }), + ({ "filter array with identifier closure", 0, (: global_var = 0; return deep_eq(filter(({1,2,3}), #'global_var, ({4,5,6})), ({})); :) }), + ({ "filter mapping with operator closure", TF_ERROR, (: filter(([1,2,3]), #'switch, ([4,5,6])) :) }), + ({ "filter mapping with unbound lambda", TF_ERROR, (: filter(([1,2,3]), unbound_lambda(0,0), ([4,5,6])) :) }), + ({ "filter mapping with identifier closure", 0, (: global_var = 1; return deep_eq(filter(([1,2,3]), #'global_var, ([4,5,6])), ([1,2,3])); :) }), + ({ "map string 1", 0, (: map("abc", (['a':'x'])) == "xbc" :) }), ({ "map string 2", 0, (: map("abc", (['a':'x';'y']), 1) == "ybc" :) }), ({ "map string 3", TF_ERROR, (: map("abc", (['a']), 0) == "abc" :) }), ({ "map string 4", 0, (: map("abc", #'+, 1) == "bcd" :) }), ({ "map string 5", 0, (: map("abc", "f") == "bcd" :) }), ({ "map string 6", TF_ERROR, (: map("abc", unbound_lambda(0,0), ({1,2,3})) :) }), - ({ "map string x", TF_ERROR, (: map("abc", (['a':'x']), 2) :) }), + ({ "map string 7", TF_ERROR, (: map("abc", (['a':'x']), 2) :) }), + ({ "map string with operator closure", TF_ERROR, (: map("abc", #'switch) :) }), + ({ "map string with unbound lambda", TF_ERROR, (: map("abc", unbound_lambda(0,0)) :) }), + ({ "map string with identifier closure", 0, (: global_var = 'x'; return map("abc", #'global_var) == "xxx"; :) }), ({ "map array 1", 0, (: deep_eq(map(({1,2,3}), ([1:5])), ({5,2,3})) :) }), ({ "map array 2", 0, (: deep_eq(map(({1,2,3}), ([1:5;6]), 1),({6,2,3})) :) }), ({ "map array 3", TF_ERROR, (: map(({1,2,3}), ([1]), 0) :) }), ({ "map array 4", 0, (: deep_eq(map(({1,2,3}), #'+, 1), ({2,3,4})) :) }), ({ "map array 5", 0, (: deep_eq(map(({1,2,3}), "f"), ({2,3,4})) :) }), ({ "map array 6", TF_ERROR, (: map(({0}), unbound_lambda(0,0), ({1,2,3})) :) }), + ({ "map array with operator closure", TF_ERROR, (: map(({1,2,3}), #'switch) :) }), + ({ "map array with unbound lambda", TF_ERROR, (: map(({1,2,3}), unbound_lambda(0,0)) :) }), + ({ "map array with identifier closure", 0, (: global_var = 4; return deep_eq(map(({1,2,3}), #'global_var), ({4,4,4})); :) }), ({ "map mapping 1", TF_ERROR, (: deep_eq(map(([1,2,3]), ([1:5])), ({5,2,3})) :) }), ({ "map mapping 2", TF_ERROR, (: deep_eq(map(([1,2,3]), ([1:5;6]), 1),({6,2,3})) :) }), ({ "map mapping 3", TF_ERROR, (: map(([1,2,3]), ([1]), 0) :) }), ({ "map mapping 4", 0, (: deep_eq(map(([1,2,3]), (: $1 + $3 :), 1), ([1:2,2:3,3:4])) :) }), ({ "map mapping 5", 0, (: deep_eq(map(([1,2,3]), "f"), ([1:2,2:3,3:4])) :) }), ({ "map mapping 6", TF_ERROR, (: map(([]), unbound_lambda(0,0), ([1,2,3])) :) }), + ({ "map mapping with operator closure", TF_ERROR, (: map(([1,2,3]), #'switch) :) }), + ({ "map mapping with unbound lambda", TF_ERROR, (: map(([1,2,3]), unbound_lambda(0,0)) :) }), + ({ "map mapping with identifier closure", 0, (: global_var = "X"; return deep_eq(map(([1,2,3]), #'global_var), ([1:"X",2:"X",3:"X"])); :) }), ({ "lambda with many values", 0, (: @@ -1131,6 +1180,10 @@ mixed *tests = :) }), + ({ "limited with operator closure", TF_ERROR, (: limited(#'switch) :) }), + ({ "limited with unbound_lambda", TF_ERROR, (: limited(unbound_lambda(0,0)) :) }), + ({ "limited with identifier closure", 0, (: global_var = "X"; return limited(#'global_var) == "X"; :) }), + ({ "load_object 1", 0, (: load_object(__FILE__) == this_object() :) }), ({ "load_object 2", 0, (: load_object("/" __FILE__) == this_object() :) }), ({ "load_object 3", 0, (: load_object("./" __FILE__) == this_object() :) }), @@ -1681,6 +1734,8 @@ string *epilog(int eflag) return 0; }); + set_this_player(this_object()); + run_test(); return 0; } From e732416e05e9ae522484bd45a76145e3f8ea91c5 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Sun, 15 Oct 2023 18:02:30 +0200 Subject: [PATCH 06/21] Fix memory leak in attach_erq_demon() --- src/comm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/comm.c b/src/comm.c index cde1e90a3..106dd1ed6 100644 --- a/src/comm.c +++ b/src/comm.c @@ -6014,7 +6014,7 @@ f_attach_erq_demon (svalue_t *sp) if (privilege_violation4(STR_ATTACH_ERQ_DEMON, const0, suffix, sp[1].u.number, sp+1)) { - char *native = convert_path_str_to_native_or_throw(suffix); + char *native = convert_path_str_to_native_or_throw(ref_mstring(suffix)); if (erq_demon != FLAG_NO_ERQ) { if (sp[1].u.number & 1) { From 016e6facb1b4a5fdf77fb52f76635bed3f319d7c Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Sun, 15 Oct 2023 18:03:08 +0200 Subject: [PATCH 07/21] Fix a crash with or operator on lpctypes --- src/interpret.c | 7 +++++-- test/t-operators.c | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/interpret.c b/src/interpret.c index c2d73cea6..93c5ebaa6 100644 --- a/src/interpret.c +++ b/src/interpret.c @@ -13972,7 +13972,6 @@ eval_instruction (bytecode_p first_instruction } #endif - TYPE_TEST_EXP_LEFT((sp-1), TF_NUMBER|TF_POINTER|TF_LPCTYPE); if ((sp-1)->type == T_NUMBER) { TYPE_TEST_RIGHT(sp, T_NUMBER); @@ -13988,7 +13987,7 @@ eval_instruction (bytecode_p first_instruction sp--; sp->u.vec = join_array(sp->u.vec, (sp+1)->u.vec); } - else if (sp->type == T_LPCTYPE && (sp-1)->type == T_LPCTYPE) + else if ((sp-1)->type == T_LPCTYPE) { TYPE_TEST_RIGHT(sp, T_LPCTYPE); lpctype_t * result = get_union_type(sp[-1].u.lpctype, sp[0].u.lpctype); @@ -13998,6 +13997,10 @@ eval_instruction (bytecode_p first_instruction sp--; sp->u.lpctype = result; } + else + { + OP_ARG_ERROR(1, TF_NUMBER|TF_POINTER|TF_LPCTYPE, sp-1); + } break; } diff --git a/test/t-operators.c b/test/t-operators.c index e9eadf776..f4ebdda15 100644 --- a/test/t-operators.c +++ b/test/t-operators.c @@ -124,24 +124,28 @@ mixed *tests = ({ ({ "[int|string] | [int|float]", 0, (: ([int|string] | [int|float]) == [int|string|float] :) }), ({ "[int] | [mixed]", 0, (: ([int] | [mixed]) == [mixed] :) }), ({ "[int] | [void]", 0, (: ([int] | [void]) == [int] :) }), + ({ "[int] | int", TF_ERROR, (: mixed val = 42; return [int] | val; :) }), ({ "[int] & [float]", 0, (: ([int] & [float]) == [void] :) }), ({ "[int] & [int|float]", 0, (: ([int] & [int|float]) == [int] :) }), ({ "[int|string] & [int|float]", 0, (: ([int|string] & [int|float]) == [int] :) }), ({ "[int] & [mixed]", 0, (: ([int] & [mixed]) == [int] :) }), ({ "[int] & [void]", 0, (: ([int] & [void]) == [void] :) }), + ({ "[int] & int", TF_ERROR, (: mixed val = 42; return [int] & val; :) }), ({ "[int] |= [float]", 0, (: lpctype val = [int]; val |= [float]; return val == [int|float]; :) }), ({ "[int] |= [int|float]", 0, (: lpctype val = [int]; val |= [int|float]; return val == [int|float]; :) }), ({ "[int|string] |= [int|float]", 0, (: lpctype val = [int|string]; val |= [int|float]; return val == [int|string|float]; :) }), ({ "[int] |= [mixed]", 0, (: lpctype val = [int]; val |= [mixed]; return val == [mixed]; :) }), ({ "[int] |= [void]", 0, (: lpctype val = [int]; val |= [void]; return val == [int]; :) }), + ({ "[int] |= int", TF_ERROR, (: mixed val = [int]; val |= 42; :) }), ({ "[int] &= [float]", 0, (: lpctype val = [int]; val &= [float]; return val == [void]; :) }), ({ "[int] &= [int|float]", 0, (: lpctype val = [int]; val &= [int|float]; return val == [int]; :) }), ({ "[int|string] &= [int|float]", 0, (: lpctype val = [int|string]; val &= [int|float]; return val == [int]; :) }), ({ "[int] &= [mixed]", 0, (: lpctype val = [int]; val &= [mixed]; return val == [int]; :) }), ({ "[int] &= [void]", 0, (: lpctype val = [int]; val &= [void]; return val == [void]; :) }), + ({ "[int] &= int", TF_ERROR, (: mixed val = [int]; val &= 42; :) }), ({ "[int] in [float]", 0, (: !([int] in [float]) :) }), ({ "[int] in [int|float]", 0, (: [int] in [int|float] :) }), @@ -150,6 +154,7 @@ mixed *tests = ({ ({ "[int] in [void]", 0, (: !([int] in [void]) :) }), ({ "[mixed] in [int]", 0, (: !([mixed] in [int]) :) }), ({ "[void] in [int]", 0, (: [void] in [int] :) }), + ({ "42 in [int]", TF_ERROR, (: mixed val = 42; return val in [int]; :) }), ({ "decltype(42)", 0, (: decltype(42) == [int] :) }), ({ "decltype(int var)", 0, (: int var; return decltype(var) == [int]; :) }), From 158ddb80afcaef65f0adf4dc9f3b5ef54be67190 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Sun, 15 Oct 2023 18:04:47 +0200 Subject: [PATCH 08/21] Fix memory leak in to_text() --- src/strfuns.c | 4 ++++ test/t-efuns.c | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/strfuns.c b/src/strfuns.c index b2866aff3..e8df9471e 100644 --- a/src/strfuns.c +++ b/src/strfuns.c @@ -1959,6 +1959,8 @@ v_to_text (svalue_t *sp, int num_arg) if (!iconv_valid(cd)) { + if (text->type == T_POINTER) + xfree(in_buf_start); if (errno == EINVAL) errorf("Bad arg 2 to to_text(): Unsupported encoding '%s'.\n", get_txt(sp->u.str)); else @@ -1972,6 +1974,8 @@ v_to_text (svalue_t *sp, int num_arg) if (in_buf_size == 0) { iconv_close(cd); + if (text->type == T_POINTER) + xfree(in_buf_start); sp = pop_n_elems(2, sp); push_ref_string(sp, STR_EMPTY); diff --git a/test/t-efuns.c b/test/t-efuns.c index 500aa4a90..655a8f772 100644 --- a/test/t-efuns.c +++ b/test/t-efuns.c @@ -1375,6 +1375,8 @@ mixed *tests = :) }), ({ "to_text 10", 0, (: deep_eq(to_array(to_text(({65, 192, 9786, 127154}))), ({65, 192, 9786, 127154})) :) }), + ({ "to_text 11", 0, (: to_text(({"Ignored", 0}), "ASCII") :) }), + ({ "to_text 12", TF_ERROR, (: to_text(({0}), "Illegal Encoding") :) }), ({ "to_bytes 1", 0, (: deep_eq(to_array(to_bytes( ({}) )), ({}) ) :) }), ({ "to_bytes 2", 0, (: deep_eq(to_array(to_bytes( ({0, 130, 150, 200, 255}) )), ({0, 130, 150, 200, 255}) ) :) }), From fd01b178db54000335e7610815a2854c6a192fec Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Sun, 15 Oct 2023 18:10:53 +0200 Subject: [PATCH 09/21] Fix memory leak in sl_open() --- src/pkg-sqlite.c | 15 ++++++++------- test/t-efuns.c | 4 ++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/pkg-sqlite.c b/src/pkg-sqlite.c index 927e651e7..7a639fe5e 100644 --- a/src/pkg-sqlite.c +++ b/src/pkg-sqlite.c @@ -783,25 +783,26 @@ sl_vfs_full_pathname (sqlite3_vfs* vfs, const char* name, int len, char* buf) */ { - string_t *orig_file = new_unicode_mstring(name); - string_t *new_file = check_valid_path(orig_file, current_object, STR_SQLITE_OPEN , MY_TRUE); + string_t *file_name = new_unicode_mstring(name); char *native; int rc; - free_mstring(orig_file); + push_string(inter_sp, file_name); + file_name = check_valid_path(file_name, current_object, STR_SQLITE_OPEN , MY_TRUE); + pop_stack(); - if (!new_file) + if (!file_name) return SQLITE_AUTH; - native = convert_path_to_native(get_txt(new_file), mstrsize(new_file)); + native = convert_path_to_native(get_txt(file_name), mstrsize(file_name)); if (!native || strlen(native) >= len) { - free_mstring(new_file); + free_mstring(file_name); return SQLITE_CANTOPEN; } rc = ((sqlite3_vfs*)vfs->pAppData)->xFullPathname((sqlite3_vfs*)vfs->pAppData, native, len, buf); - free_mstring(new_file); + free_mstring(file_name); return rc; } /* sl_vfs_full_pathname() */ diff --git a/test/t-efuns.c b/test/t-efuns.c index 655a8f772..68f89494c 100644 --- a/test/t-efuns.c +++ b/test/t-efuns.c @@ -1212,6 +1212,10 @@ mixed *tests = ({ "regreplace 3", 0, (: regreplace("A\x00BC", "B", "X", RE_TRADITIONAL) == "A\x00XC" :) }), ({ "regreplace 4", 0, (: regreplace("A\x00BC", "B", "X", RE_PCRE) == "A\x00XC" :) }), +#ifdef __SQLITE__ + ({ "sl_open with illegal path", TF_ERROR, (: sl_open("whatever/../../somethingelse.db"); :) }), +#endif + ({ "sscanf 1", 0, (: sscanf("A10", "A%~d") == 1 :) }), ({ "sscanf 2", 0, (: sscanf("B10", "A%~d") == 0 :) }), ({ "sscanf 3", 0, (: sscanf("A10", "A%!d") == 0 :) }), From 97a978e45f9fba1993d72d378fc9d7e5682b0188 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Sun, 15 Oct 2023 18:49:28 +0200 Subject: [PATCH 10/21] Fix crash on __INT_MIN__ % -1 (At least) on Intel processors modulo division is done together with normal division resulting in an exception when the division results exceeds the value range. Therefore fix the special case of __INT_MIN__ % -1, where division would not be allowed, but modulo is well defined. --- src/interpret.c | 6 +++--- test/t-operators.c | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/interpret.c b/src/interpret.c index 93c5ebaa6..b30674b81 100644 --- a/src/interpret.c +++ b/src/interpret.c @@ -13214,12 +13214,12 @@ eval_instruction (bytecode_p first_instruction TYPE_TEST_LEFT((sp-1), T_NUMBER); TYPE_TEST_RIGHT(sp, T_NUMBER); if (sp->u.number == 0) - { ERROR("Modulus by zero.\n"); - break; - } + else if (sp->u.number == -1) // Shortcut, needed for sp[-1] == PINT_MIN + i = 0; else i = (sp-1)->u.number % sp->u.number; + sp--; sp->u.number = i; break; diff --git a/test/t-operators.c b/test/t-operators.c index f4ebdda15..6c75a193c 100644 --- a/test/t-operators.c +++ b/test/t-operators.c @@ -4,6 +4,10 @@ #include "/inc/deep_eq.inc" mixed *tests = ({ + ({ "__INT_MIN__ % -1", 0, (: __INT_MIN__ % -1 == 0 :) }), + ({ "__INT_MIN__ / -1", TF_ERROR, (: __INT_MIN__ / -1 == 0 :) }), + ({ "__INT_MIN__ * -1", TF_ERROR, (: __INT_MIN__ * -1 == 0 :) }), + ({ "int + float", 0, (: float val = 1 + 2.5; return val > 3.4999 && val < 3.5001; :) }), ({ "int - float", 0, (: float val = 1 - 2.5; return val > -1.5001 && val < -1.4999; :) }), ({ "int * float", 0, (: float val = 1 * 2.5; return val > 2.4999 && val < 2.5001; :) }), From 7fd33bf1921eb0f910e8f56a6106547a417e1a2a Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Thu, 19 Oct 2023 21:59:31 +0200 Subject: [PATCH 11/21] Streamlined IPv4 and IPv6 implementation Use mostly the same code path for IPv4 and IPv6. Thereby removing calls to deprecated functions like gethostbyname() in favor of getaddrinfo() and getnameinfo() Also removed the redefinition of IPv4 structures with the IPv6 ones, but instead create new definitions. --- src/access_check.c | 12 +- src/access_check.h | 2 +- src/comm.c | 415 +++++++++++++++------------------------------ src/comm.h | 49 ++++-- 4 files changed, 182 insertions(+), 296 deletions(-) diff --git a/src/access_check.c b/src/access_check.c index 543324275..9eceff6aa 100644 --- a/src/access_check.c +++ b/src/access_check.c @@ -142,7 +142,7 @@ static time_t last_read_time = 0; /*-------------------------------------------------------------------------*/ static struct access_class * -find_access_class (struct sockaddr_in *full_addr, int port) +find_access_class (sockaddr_in4or6 *full_addr, int port) /* Find and return the class structure for the given IP at * the current time. Return NULL if no rule covers the IP at this time. @@ -155,8 +155,10 @@ find_access_class (struct sockaddr_in *full_addr, int port) struct tm *tm_p; #ifdef DEBUG_ACCESS_CHECK + char buf[INET4OR6_ADDRSTRLEN]; + fprintf(stderr, "find_class for '%s':%d\n" - , inet_ntoa(*(struct in_addr*)&full_addr->sin_addr) + , inet_ntop(AF_INET4OR6, &(full_addr->sin4or6_addr), buf, sizeof(buf)) , port); #endif #ifndef USE_IPV6 @@ -165,7 +167,7 @@ find_access_class (struct sockaddr_in *full_addr, int port) #ifndef s6_addr32 # define s6_addr32 __u6_addr.__u6_addr32 #endif - addr = full_addr->sin_addr.s6_addr32[3]; + addr = full_addr->sin6_addr.s6_addr32[3]; /* TODO: DANGER: The above assignment will not work for any real IPv6 addresses. * TODO:: The general format of the access file needs work for that. */ @@ -208,7 +210,7 @@ find_access_class (struct sockaddr_in *full_addr, int port) /*-------------------------------------------------------------------------*/ static void -add_access_entry (struct sockaddr_in *full_addr, int login_port, long *idp) +add_access_entry (sockaddr_in4or6 *full_addr, int login_port, long *idp) /* Find the class structure for and increments its count * of users. The id of the class is put into *idp. @@ -434,7 +436,7 @@ read_access_file (void) /*-------------------------------------------------------------------------*/ char * -allow_host_access (struct sockaddr_in *full_addr, int login_port, long *idp) +allow_host_access (sockaddr_in4or6 *full_addr, int login_port, long *idp) /* Check if the IP address is allowed access at the current * time. Return NULL if access is granted, else an error message. diff --git a/src/access_check.h b/src/access_check.h index dc3ceaf6f..6678818ea 100644 --- a/src/access_check.h +++ b/src/access_check.h @@ -9,7 +9,7 @@ extern char *access_file; extern char *access_log; -extern char * allow_host_access(struct sockaddr_in *full_addr, int, long *idp); +extern char * allow_host_access(sockaddr_in4or6 *full_addr, int, long *idp); extern void release_host_access(long num); extern void initialize_host_access(); diff --git a/src/comm.c b/src/comm.c index 106dd1ed6..58c53324e 100644 --- a/src/comm.c +++ b/src/comm.c @@ -354,7 +354,7 @@ static erq_callback_t *free_erq; #endif static struct ipentry { - struct in_addr addr; /* The address (only .s_addr is significant) */ + in4or6_addr addr; /* The address (only .s_addr is significant) */ string_t *name; /* tabled string with the hostname for */ } iptable[IPSIZE]; /* Cache of known names for given IP addresses. @@ -390,12 +390,12 @@ static char host_name[MAXHOSTNAMELEN+1]; /* This computer's hostname, used for query_host_name() efun. */ -static struct in_addr host_ip_number; +static in4or6_addr host_ip_number; /* This computer's numeric IP address only, used for * the query_host_ip_number() efun. */ -static struct sockaddr_in host_ip_addr_template; +static sockaddr_in4or6 host_ip_addr_template; /* The template address of this computer. It is copied locally * and augmented with varying port numbers to open the driver's ports. */ @@ -496,7 +496,7 @@ typedef enum { } OutConnStatus; struct OutConn { - struct sockaddr_in target; /* Address connected to (allocated) */ + sockaddr_in4or6 target; /* Address connected to (allocated) */ object_t * curr_obj; /* Associated object */ int socket; /* Socket on our side */ OutConnStatus status; /* Status of this entry */ @@ -519,7 +519,7 @@ static void send_dont(int); static void add_flush_entry(interactive_t *ip); static void remove_flush_entry(interactive_t *ip); static void clear_message_buf(interactive_t *ip); -static void new_player(object_t *receiver, SOCKET_T new_socket, struct sockaddr_in *addr, size_t len, int login_port); +static void new_player(object_t *receiver, SOCKET_T new_socket, sockaddr_in4or6 *addr, size_t len, int login_port); #ifdef ERQ_DEMON @@ -527,8 +527,8 @@ static long read_32(char *); static Bool send_erq(int handle, int request, const char *arg, size_t arglen); static void shutdown_erq_demon(void); static void stop_erq_demon(Bool); -static string_t * lookup_ip_entry (struct in_addr addr, Bool useErq); -static void add_ip_entry(struct in_addr addr, const char *name); +static string_t * lookup_ip_entry (in4or6_addr addr, Bool useErq); +static void add_ip_entry(in4or6_addr addr, const char *name); #ifdef USE_IPV6 static void update_ip_entry(const char *oldname, const char *newname); #endif @@ -559,79 +559,13 @@ static INLINE ssize_t comm_send_buf(char *msg, size_t size, interactive_t *ip); # define s6_addr32 __u6_addr.__u6_addr32 #endif -static inline void CREATE_IPV6_MAPPED(struct in_addr *v6, uint32 v4) { +static inline void CREATE_IPV6_MAPPED(struct in6_addr *v6, uint32 v4) { v6->s6_addr32[0] = 0; v6->s6_addr32[1] = 0; v6->s6_addr32[2] = htonl(0x0000ffff); v6->s6_addr32[3] = v4; } -/* These are the typical IPv6 structures - we use them transparently. - * - * --- arpa/inet.h --- - * - * struct in6_addr { - * union { - * u_int32_t u6_addr32[4]; - * #ifdef notyet - * u_int64_t u6_addr64[2]; - * #endif - * u_int16_t u6_addr16[8]; - * u_int8_t u6_addr8[16]; - * } u6_addr; - * }; - * #define s6_addr32 u6_addr.u6_addr32 - * #ifdef notyet - * #define s6_addr64 u6_addr.u6_addr64 - * #endif - * #define s6_addr16 u6_addr.u6_addr16 - * #define s6_addr8 u6_addr.u6_addr8 - * #define s6_addr u6_addr.u6_addr8 - * - * --- netinet/in.h --- - * - * struct sockaddr_in6 { - * u_char sin6_len; - * u_char sin6_family; - * u_int16_t sin6_port; - * u_int32_t sin6_flowinfo; - * struct in6_addr sin6_addr; - * }; - * - */ - -/*-------------------------------------------------------------------------*/ -static char * -inet6_ntoa (struct in6_addr in) - -/* Convert the ipv6 address into a string and return it. - * Note: the string is stored in a local buffer. - */ - -{ - static char str[INET6_ADDRSTRLEN+1]; - - if (NULL == inet_ntop(AF_INET6, &in, str, INET6_ADDRSTRLEN)) - { - perror("inet_ntop"); - } - return str; -} /* inet6_ntoa() */ - -/*-------------------------------------------------------------------------*/ -static struct in6_addr -inet6_addr (const char *to_host) - -/* Convert the name into a ipv6 address and return it. - */ - -{ - struct in6_addr addr; - - inet_pton(AF_INET6, to_host, &addr); - return addr; -} /* inet6_addr() */ - #endif /* USE_IPV6 */ /*-------------------------------------------------------------------------*/ @@ -973,27 +907,26 @@ add_listen_port (const char *port) port_numbers[numports] = (struct listen_port_s){ LISTEN_PORT_ADDR, port, p }; -#ifndef USE_IPV6 - if (!inet_pton(AF_INET, addr, &(port_numbers[numports].addr))) - { - free(addr); - return false; - } - numports++; -#else if (length == 0) { - port_numbers[numports].addr = in6addr_any; + port_numbers[numports].addr = IN4OR6ADDR_ANY; } else if (addr[0] == '[' && addr[length-1] == ']') { addr[length-1] = 0; - if (!inet_pton(AF_INET6, addr+1, &(port_numbers[numports].addr))) + if (!inet_pton(AF_INET4OR6, addr+1, &(port_numbers[numports].addr))) { free(addr); return false; } } +#ifndef USE_IPV6 // Only IPv4 allowed without [] + else if (!inet_pton(AF_INET, addr, &(port_numbers[numports].addr))) + { + free(addr); + return false; + } +#endif else { free(addr); @@ -1003,7 +936,6 @@ add_listen_port (const char *port) numports++; free(addr); return true; -#endif } } @@ -1111,112 +1043,101 @@ initialize_host_ip_number (const char *hname, const char * haddr) memset(&host_ip_addr_template, 0, sizeof host_ip_addr_template); if (haddr != NULL) { -#ifndef USE_IPV6 - host_ip_number.s_addr = inet_addr(haddr); - host_ip_addr_template.sin_family = AF_INET; - host_ip_addr_template.sin_addr = host_ip_number; -#else - host_ip_number = inet6_addr(haddr); - host_ip_addr_template.sin_family = AF_INET6; - host_ip_addr_template.sin_addr = host_ip_number; -#endif - - /* Find the domain part of the hostname */ - domain = strchr(host_name, '.'); + inet_pton(AF_INET4OR6, haddr, &host_ip_number); + host_ip_addr_template.sin4or6_family = AF_INET4OR6; + host_ip_addr_template.sin4or6_addr = host_ip_number; } else { - struct hostent *hp; + struct addrinfo *addr, *current; + struct addrinfo hints = { + .ai_family = AF_INET4OR6, + .ai_socktype = SOCK_STREAM, + .ai_protocol = 0, + .ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_CANONNAME + }; + int res; -#ifndef USE_IPV6 - hp = gethostbyname(host_name); - if (!hp) { - fprintf(stderr, "%s gethostbyname: unknown host '%s'.\n" - , time_stamp(), host_name); + res = getaddrinfo(host_name, NULL, &hints, &addr); + if (res) + { + fprintf(stderr, "%s getaddrinfo: unknown host '%s': %s\n" + , time_stamp(), host_name, gai_strerror(res)); exit(1); } - memcpy(&host_ip_addr_template.sin_addr, hp->h_addr, (size_t)hp->h_length); - host_ip_addr_template.sin_family = (unsigned short)hp->h_addrtype; -#else - hp = gethostbyname2(host_name, AF_INET6); - if (!hp) - hp = gethostbyname2(host_name, AF_INET); - if (!hp) + + for (current = addr; current != NULL; current = current->ai_next) + if (current->ai_family == AF_INET4OR6) + break; + + if (!current) { - fprintf(stderr, "%s gethostbyname2: unknown host '%s'.\n" + freeaddrinfo(addr); + fprintf(stderr, "%s getaddrinfo: No suitable address for host '%s' found.\n" , time_stamp(), host_name); exit(1); } - memcpy(&host_ip_addr_template.sin_addr, hp->h_addr, (size_t)hp->h_length); - if (hp->h_addrtype == AF_INET) + if (current->ai_addrlen != sizeof(host_ip_addr_template)) { - CREATE_IPV6_MAPPED(&host_ip_addr_template.sin_addr, *(u_int32_t*)hp->h_addr_list[0]); + freeaddrinfo(addr); + fprintf(stderr, "%s getaddrinfo: Wrong format for address of host '%s'.\n" + , time_stamp(), host_name); + exit(1); } - host_ip_addr_template.sin_family = AF_INET6; -#endif - host_ip_number = host_ip_addr_template.sin_addr; + memcpy(&host_ip_addr_template, current->ai_addr, current->ai_addrlen); + host_ip_number = host_ip_addr_template.sin4or6_addr; /* Now set the template to the proper _ANY value */ - memset(&host_ip_addr_template.sin_addr, 0, sizeof(host_ip_addr_template.sin_addr)); -#ifndef USE_IPV6 - host_ip_addr_template.sin_addr.s_addr = INADDR_ANY; - host_ip_addr_template.sin_family = AF_INET; -#else - host_ip_addr_template.sin_addr = in6addr_any; - host_ip_addr_template.sin_family = AF_INET6; -#endif + memset(&host_ip_addr_template.sin4or6_addr, 0, sizeof(host_ip_addr_template.sin4or6_addr)); + host_ip_addr_template.sin4or6_addr = IN4OR6ADDR_ANY; + host_ip_addr_template.sin4or6_family = AF_INET4OR6; - /* Find the domain part of the hostname */ - if (hname == NULL) - domain = strchr(hp->h_name, '.'); - else - domain = strchr(host_name, '.'); + domain = strchr(addr->ai_canonname, '.'); + if (domain) + domain_name = strdup(domain+1); + + freeaddrinfo(addr); } -#ifndef USE_IPV6 - printf("%s Hostname '%s' address '%s'\n" - , time_stamp(), host_name, inet_ntoa(host_ip_number)); - debug_message("%s Hostname '%s' address '%s'\n" - , time_stamp(), host_name, inet_ntoa(host_ip_number)); -#else - printf("%s Hostname '%s' address '%s'\n" - , time_stamp(), host_name, inet6_ntoa(host_ip_number)); - debug_message("%s Hostname '%s' address '%s'\n" - , time_stamp(), host_name, inet6_ntoa(host_ip_number)); -#endif + { + char buf[INET4OR6_ADDRSTRLEN+1]; + + printf("%s Hostname '%s' address '%s'\n" + , time_stamp(), host_name, inet_ntop(AF_INET4OR6, &host_ip_number, buf, sizeof(buf))); + } /* Put the domain name part of the hostname into domain_name, then * strip it off the host_name[] (as only query_host_name() is going * to need it). - * Note that domain might not point into host_name[] here, so we - * can't just stomp '\0' in there. */ + domain = strchr(host_name, '.'); if (domain) { + // Given argument has precedence over name lookup. + if (domain_name) + free(domain_name); + domain_name = strdup(domain+1); + *domain = '\0'; } - else + else if (!domain_name) domain_name = strdup("unknown"); - domain = strchr(host_name, '.'); - if (domain) - *domain = '\0'; - /* Initialize udp at an early stage so that the master object can use * it in inaugurate_master() , and the port number is known. */ if (udp_port != -1) { - struct sockaddr_in host_ip_addr; + sockaddr_in4or6 host_ip_addr; memcpy(&host_ip_addr, &host_ip_addr_template, sizeof(host_ip_addr)); - host_ip_addr.sin_port = htons((u_short)udp_port); + host_ip_addr.sin4or6_port = htons((u_short)udp_port); debug_message("%s UDP recv-socket requested for port: %d\n" , time_stamp(), udp_port); - udp_s = socket(host_ip_addr.sin_family, SOCK_DGRAM, 0); + udp_s = socket(host_ip_addr.sin4or6_family, SOCK_DGRAM, 0); if (udp_s == -1) { perror("socket(udp_socket)"); @@ -1245,8 +1166,8 @@ initialize_host_ip_number (const char *hname, const char * haddr) , time_stamp(), udp_port); debug_message("%s UDP port %d already bound!\n" , time_stamp(), udp_port); - if (host_ip_addr.sin_port) { - host_ip_addr.sin_port = 0; + if (host_ip_addr.sin4or6_port) { + host_ip_addr.sin4or6_port = 0; continue; } close(udp_s); @@ -1264,14 +1185,14 @@ initialize_host_ip_number (const char *hname, const char * haddr) * initialise it. */ if (udp_s >= 0) { - struct sockaddr_in host_ip_addr; + sockaddr_in4or6 host_ip_addr; tmp = sizeof(host_ip_addr); if (!getsockname(udp_s, (struct sockaddr *)&host_ip_addr, &tmp)) { int oldport = udp_port; - udp_port = ntohs(host_ip_addr.sin_port); + udp_port = ntohs(host_ip_addr.sin4or6_port); if (oldport != udp_port) debug_message("%s UDP recv-socket on port: %d\n" , time_stamp(), udp_port); @@ -1332,7 +1253,7 @@ prepare_ipc(void) */ for (i = 0; i < numports; i++) { - struct sockaddr_in host_ip_addr; + sockaddr_in4or6 host_ip_addr; memcpy(&host_ip_addr, &host_ip_addr_template, sizeof(host_ip_addr)); @@ -1340,15 +1261,11 @@ prepare_ipc(void) { /* Real port number */ - host_ip_addr.sin_port = htons((u_short)port_numbers[i].port); + host_ip_addr.sin4or6_port = htons((u_short)port_numbers[i].port); if (port_numbers[i].type == LISTEN_PORT_ADDR) -#ifndef USE_IPV6 - host_ip_addr.sin_addr.s_addr = port_numbers[i].addr; -#else - host_ip_addr.sin_addr = port_numbers[i].addr; -#endif + host_ip_addr.sin4or6_addr = port_numbers[i].addr; - sos[i] = socket(host_ip_addr.sin_family, SOCK_STREAM, 0); + sos[i] = socket(host_ip_addr.sin4or6_family, SOCK_STREAM, 0); if ((int)sos[i] == -1) { perror("socket"); exit(1); @@ -1381,12 +1298,8 @@ prepare_ipc(void) if (!getsockname(sos[i], (struct sockaddr *)&host_ip_addr, &tmp)) { port_numbers[i].type = LISTEN_PORT_ADDR; - port_numbers[i].port = ntohs(host_ip_addr.sin_port); -#ifndef USE_IPV6 - port_numbers[i].addr = host_ip_addr.sin_addr.s_addr; -#else - port_numbers[i].addr = host_ip_addr.sin_addr; -#endif + port_numbers[i].port = ntohs(host_ip_addr.sin4or6_port); + port_numbers[i].addr = host_ip_addr.sin4or6_addr; } } @@ -2298,7 +2211,7 @@ get_message (char *buff, size_t *bufflength) while(MY_TRUE) { - struct sockaddr_in addr; + sockaddr_in4or6 addr; length_t length; /* length of */ struct timeval timeout; @@ -2578,7 +2491,7 @@ get_message (char *buff, size_t *bufflength) #endif } else { uint32 naddr; - struct in_addr net_addr; + in4or6_addr net_addr; memcpy((char*)&naddr, rp+8, sizeof(naddr)); #ifndef USE_IPV6 @@ -2782,7 +2695,6 @@ get_message (char *buff, size_t *bufflength) if (udp_s >= 0 && FD_ISSET(udp_s, &readfds)) #endif { - char *ipaddr_str; int cnt; length = sizeof addr; @@ -2800,18 +2712,16 @@ get_message (char *buff, size_t *bufflength) } else { + char buf[INET4OR6_ADDRSTRLEN+1]; + command_giver = NULL; current_interactive = NULL; clear_current_object(); trace_level = 0; -#ifndef USE_IPV6 - ipaddr_str = inet_ntoa(addr.sin_addr); -#else - ipaddr_str = inet6_ntoa(addr.sin_addr); -#endif - push_c_string(inter_sp, ipaddr_str); + + push_c_string(inter_sp, inet_ntop(AF_INET4OR6, &addr.sin4or6_addr, buf, sizeof(buf))); push_bytes(inter_sp, udp_data); /* adopts the ref */ - push_number(inter_sp, ntohs(addr.sin_port)); + push_number(inter_sp, ntohs(addr.sin4or6_port)); RESET_LIMITS; callback_master(STR_RECEIVE_UDP, 3); CLEAR_EVAL_COST; @@ -3403,7 +3313,7 @@ remove_interactive (object_t *ob, Bool force) /*-------------------------------------------------------------------------*/ void -refresh_access_data(void (*add_entry)(struct sockaddr_in *, int, long*) ) +refresh_access_data(void (*add_entry)(sockaddr_in4or6 *, int, long*) ) /* Called from access_check after the ACCESS_FILE has been (re)read, this * function has to call the passed callback function add_entry for every @@ -3420,13 +3330,13 @@ refresh_access_data(void (*add_entry)(struct sockaddr_in *, int, long*) ) this = *user; if (this) { - struct sockaddr_in addr; + sockaddr_in4or6 addr; int port; length_t length; length = sizeof(addr); getsockname(this->socket, (struct sockaddr *)&addr, &length); - port = ntohs(addr.sin_port); + port = ntohs(addr.sin4or6_port); (*add_entry)(&this->addr, port, &this->access_class); } } @@ -3522,7 +3432,7 @@ set_encoding (interactive_t *ip, const char* encoding) /*-------------------------------------------------------------------------*/ static void new_player ( object_t *ob, SOCKET_T new_socket - , struct sockaddr_in *addr, size_t addrlen + , sockaddr_in4or6 *addr, size_t addrlen , int login_port ) @@ -3570,15 +3480,14 @@ new_player ( object_t *ob, SOCKET_T new_socket { FILE *log_file = fopen (access_log, "a"); - if (log_file) { + if (log_file) + { + char buf[INET4OR6_ADDRSTRLEN+1]; + FCOUNT_WRITE(log_file); fprintf(log_file, "%s %s: %s\n" , time_stamp() -#ifndef USE_IPV6 - , inet_ntoa(addr->sin_addr) -#else - , inet6_ntoa(addr->sin_addr) -#endif + , inet_ntop(AF_INET4OR6, &addr->sin4or6_addr, buf, sizeof(buf)) , message ? "denied" : "granted"); fclose(log_file); } @@ -3788,7 +3697,7 @@ new_player ( object_t *ob, SOCKET_T new_socket new_interactive->snoop_on->snoop_by = ob; } #ifdef ERQ_DEMON - (void) lookup_ip_entry(new_interactive->addr.sin_addr, MY_TRUE); + (void) lookup_ip_entry(new_interactive->addr.sin4or6_addr, MY_TRUE); /* TODO: We could pass the retrieved hostname right to login */ #endif #ifdef USE_TLS @@ -6231,7 +6140,7 @@ read_32 (char *str) /*-------------------------------------------------------------------------*/ static void -add_ip_entry (struct in_addr addr, const char *name) +add_ip_entry (in4or6_addr addr, const char *name) /* Add a new IP address /hostname pair to the cache iptable[]. * If the already exists in the table, replace the old tabled name @@ -6246,7 +6155,7 @@ add_ip_entry (struct in_addr addr, const char *name) new_entry = MY_FALSE; for (i = 0; i < IPSIZE; i++) { - if (!memcmp(&(iptable[i].addr.s_addr), &addr.s_addr, sizeof(iptable[i].addr.s_addr))) + if (!memcmp(&(iptable[i].addr.s4or6_addr), &addr.s4or6_addr, sizeof(iptable[i].addr.s4or6_addr))) { ix = i; break; @@ -6297,7 +6206,7 @@ update_ip_entry (const char *oldname, const char *newname) /*-------------------------------------------------------------------------*/ static string_t * -lookup_ip_entry (struct in_addr addr, Bool useErq) +lookup_ip_entry (in4or6_addr addr, Bool useErq) /* Lookup the IP address and return an uncounted pointer to * a shared string with the hostname. The function looks first in the @@ -6308,7 +6217,8 @@ lookup_ip_entry (struct in_addr addr, Bool useErq) { int i; string_t *ipname; - struct in_addr tmp; + in4or6_addr tmp; + char buf[INET4OR6_ADDRSTRLEN+1]; /* Search for the address backwards from the last added entry, * hoping that its one of the more recently added ones. @@ -6319,7 +6229,7 @@ lookup_ip_entry (struct in_addr addr, Bool useErq) if (i < 0) i += IPSIZE; - if (!memcmp(&(iptable[i].addr.s_addr), &addr.s_addr, sizeof(iptable[i].addr.s_addr)) + if (!memcmp(&(iptable[i].addr.s4or6_addr), &addr.s4or6_addr, sizeof(iptable[i].addr.s4or6_addr)) && iptable[i].name) { return iptable[i].name; @@ -6337,14 +6247,8 @@ lookup_ip_entry (struct in_addr addr, Bool useErq) free_mstring(iptable[ipcur].name); memcpy(&tmp, &addr, sizeof(tmp)); -#ifndef USE_IPV6 - ipname = new_tabled(inet_ntoa(tmp), STRING_ASCII); -#else - ipname = new_tabled(inet6_ntoa(tmp), STRING_ASCII); -#endif - + ipname = new_tabled(inet_ntop(AF_INET4OR6, &tmp, buf, sizeof(buf)), STRING_ASCII); iptable[ipcur].name = ipname; - ipcur = (ipcur+1) % IPSIZE; /* If we have the erq and may use it, lookup the real hostname */ @@ -6631,15 +6535,12 @@ get_host_ip_number (void) */ { -#ifndef USE_IPV6 - char buf[INET_ADDRSTRLEN+3]; + char buf[INET4OR6_ADDRSTRLEN+3]; - sprintf(buf, "\"%s\"", inet_ntoa(host_ip_number)); -#else - char buf[INET6_ADDRSTRLEN+3]; + buf[0] = '"'; + inet_ntop(AF_INET4OR6, &host_ip_number, buf+1, sizeof(buf)-2); + strcat(buf, "\""); - sprintf(buf, "\"%s\"", inet6_ntoa(host_ip_number)); -#endif return string_copy(buf); } /* query_host_ip_number() */ @@ -6763,15 +6664,11 @@ f_send_udp (svalue_t *sp) */ { - char *to_host = NULL; + const char *to_host; int to_port; char *msg; size_t msglen; -#ifndef USE_IPV6 - int ip1, ip2, ip3, ip4; -#endif /* USE_IPV6 */ - struct sockaddr_in name; - struct hostent *hp; + sockaddr_in4or6 name; int ret = 0; svalue_t *firstarg; /* store the first argument */ @@ -6828,57 +6725,33 @@ f_send_udp (svalue_t *sp) break; /* Determine the destination address */ + to_host = get_txt(firstarg->u.str); + to_port = (sp-1)->u.number; { - size_t adrlen; + struct addrinfo *addr; + struct addrinfo hints = { + .ai_family = AF_INET4OR6, + .ai_socktype = SOCK_STREAM, + .ai_protocol = 0, + .ai_flags = AI_V4MAPPED | AI_ADDRCONFIG + }; + int res = getaddrinfo(to_host, NULL, &hints, &addr); - adrlen = mstrsize(firstarg->u.str); - /* as there are no runtime error raised below, we just xallocate - * and don't bother with an error handler. */ - to_host = xalloc(adrlen+1); - if (!to_host) + if (res) + break; + if (addr->ai_family != AF_INET4OR6 + || addr->ai_addrlen != sizeof(name)) { - errorf("Out of memory (%zu bytes) in send_udp() for host address\n" - , (adrlen+1)); - /* NOTREACHED */ + freeaddrinfo(addr); + break; } - memcpy(to_host, get_txt(firstarg->u.str), adrlen); - to_host[adrlen] = '\0'; - } - to_port = (sp-1)->u.number; -#ifndef USE_IPV6 - if (sscanf(to_host, "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4) == 4) - { - name.sin_addr.s_addr = inet_addr(to_host); - name.sin_family = AF_INET; - } - else - { - /* TODO: Uh-oh, blocking DNS in the execution thread */ - hp = gethostbyname(to_host); - if (hp == 0) - break; - memcpy(&name.sin_addr, hp->h_addr, (size_t)hp->h_length); - name.sin_family = AF_INET; - } - -#else /* USE_IPV6 */ + memcpy(&name, addr->ai_addr, sizeof(name)); + name.sin4or6_port = htons(to_port); - /* TODO: Uh-oh, blocking DNS in the execution thread */ - hp = gethostbyname2(to_host, AF_INET6); - if (hp == 0) hp = gethostbyname2(to_host, AF_INET); - if (hp == 0) break; - memcpy(&name.sin_addr, hp->h_addr, (size_t)hp->h_length); - - if (hp->h_addrtype == AF_INET) - { - CREATE_IPV6_MAPPED(&name.sin_addr, *(u_int32_t*)hp->h_addr_list[0]); + freeaddrinfo(addr); } - name.sin_family = AF_INET6; -#endif /* USE_IPV6 */ - - name.sin_port = htons(to_port); /* Send the message. */ #ifndef SENDTO_BROKEN @@ -6893,8 +6766,7 @@ f_send_udp (svalue_t *sp) * (including) to sp. */ sp = pop_n_elems((sp-firstarg)+1, sp); - xfree(to_host); - + /*Return the result */ sp++; put_number(sp, ret); @@ -8380,13 +8252,12 @@ f_net_connect (svalue_t *sp) /* Attempt the connection */ memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET4OR6; #ifndef USE_IPV6 - hints.ai_family = AF_INET; // Allow only IPv4 // only IPv4 if at least one interface with IPv4 and // only IPv6 if at least one interface with IPv6 hints.ai_flags = AI_ADDRCONFIG|AI_NUMERICSERV; #else - hints.ai_family = AF_INET6; // Allow only IPv6 // only IPv6 if at least one interface with IPv6. // And list IPv4 addresses as IPv4-mapped IPv6 addresses if no IPv6 addresses could be found. hints.ai_flags = AI_ADDRCONFIG|AI_V4MAPPED|AI_NUMERICSERV; @@ -8495,7 +8366,7 @@ f_net_connect (svalue_t *sp) * we can complete it immediately. For the reason see below. */ outconn[n].socket = sfd; - outconn[n].target = *((struct sockaddr_in *)rp->ai_addr); + outconn[n].target = *((sockaddr_in4or6 *)rp->ai_addr); outconn[n].curr_obj = ref_object(ob, "net_conect"); // no longer need the results from getaddrinfo() @@ -8516,7 +8387,7 @@ f_net_connect (svalue_t *sp) user = command_giver; inter_sp = sp; - new_player(ob, sfd, (struct sockaddr_in *)rp->ai_addr, sizeof(struct sockaddr_in), 0); + new_player(ob, sfd, (sockaddr_in4or6 *)rp->ai_addr, sizeof(sockaddr_in4or6), 0); command_giver = user; /* All done - clean up */ @@ -9037,7 +8908,7 @@ f_interactive_info (svalue_t *sp) { string_t * hname; - hname = lookup_ip_entry(ip->addr.sin_addr, MY_FALSE); + hname = lookup_ip_entry(ip->addr.sin4or6_addr, MY_FALSE); if (hname) { put_ref_string(&result, hname); @@ -9050,13 +8921,9 @@ f_interactive_info (svalue_t *sp) case II_IP_NUMBER: { string_t *haddr; + char buf[INET4OR6_ADDRSTRLEN+1]; -#ifndef USE_IPV6 - haddr = new_mstring(inet_ntoa(ip->addr.sin_addr), STRING_ASCII); -#else - haddr = new_mstring(inet6_ntoa(ip->addr.sin_addr), STRING_ASCII); -#endif - + haddr = new_mstring(inet_ntop(AF_INET4OR6, &(ip->addr.sin4or6_addr), buf, sizeof(buf)), STRING_ASCII); if (!haddr) errorf("Out of memory for IP address\n"); @@ -9065,7 +8932,7 @@ f_interactive_info (svalue_t *sp) } case II_IP_PORT: - put_number(&result, ntohs(ip->addr.sin_port)); + put_number(&result, ntohs(ip->addr.sin4or6_port)); break; case II_IP_ADDRESS: @@ -9094,11 +8961,11 @@ f_interactive_info (svalue_t *sp) case II_MUD_PORT: { - struct sockaddr_in addr; + sockaddr_in4or6 addr; length_t length = sizeof(addr); getsockname(ip->socket, (struct sockaddr *)&addr, &length); - put_number(&result, ntohs(addr.sin_port)); + put_number(&result, ntohs(addr.sin4or6_port)); break; } diff --git a/src/comm.h b/src/comm.h index 878e1186e..c5f30638a 100644 --- a/src/comm.h +++ b/src/comm.h @@ -36,17 +36,21 @@ #ifdef USE_IPV6 -/* For IPv6 we defined macros for the 'old' sockaddr member names - * which expand into the ipv6 names. - */ +#define AF_INET4OR6 AF_INET6 +#define INET4OR6_ADDRSTRLEN INET6_ADDRSTRLEN +#define IN4OR6ADDR_ANY in6addr_any + +typedef struct sockaddr_in6 sockaddr_in4or6; +typedef struct in6_addr in4or6_addr; + +/* sockaddr_in6 members */ +#define sin4or6_port sin6_port +#define sin4or6_addr sin6_addr +#define sin4or6_family sin6_family -#define sockaddr_in sockaddr_in6 +/* in6_addr member */ +#define s4or6_addr s6_addr -#define sin_port sin6_port -#define sin_addr sin6_addr -#define sin_family sin6_family -#define s_addr s6_addr -#define in_addr in6_addr #if defined(__APPLE__) && defined(__MACH__) && !defined(s6_addr32) @@ -59,6 +63,23 @@ # define s6_addr32 __u6_addr.__u6_addr32 #endif +#else + +#define AF_INET4OR6 AF_INET +#define INET4OR6_ADDRSTRLEN INET_ADDRSTRLEN +#define IN4OR6ADDR_ANY (struct in_addr){.s_addr = INADDR_ANY} + +typedef struct sockaddr_in sockaddr_in4or6; +typedef struct in_addr in4or6_addr; + +/* sockaddr_in6 members */ +#define sin4or6_port sin_port +#define sin4or6_addr sin_addr +#define sin4or6_family sin_family + +/* in6_addr member */ +#define s4or6_addr s_addr + #endif /* USE_IPV6 */ @@ -139,11 +160,7 @@ struct listen_port_s enum { LISTEN_PORT_ANY, LISTEN_PORT_ADDR, LISTEN_PORT_INHERITED } type; const char* str; int port; -#ifndef USE_IPV6 - in_addr_t addr; -#else - struct in6_addr addr; -#endif + in4or6_addr addr; }; @@ -183,7 +200,7 @@ struct interactive_s { called with next input line */ object_t *modify_command; /* modify_command() handler() */ svalue_t prompt; /* The prompt to print. */ - struct sockaddr_in addr; /* Address of connected user */ + sockaddr_in4or6 addr; /* Address of connected user */ CBool closing; /* True when closing this socket. */ CBool tn_enabled; /* True: telnet machine enabled */ @@ -481,6 +498,6 @@ extern svalue_t *f_users(svalue_t *sp); extern svalue_t *f_net_connect (svalue_t *sp); extern svalue_t *f_configure_interactive(svalue_t *sp); -extern void refresh_access_data(void (*add_entry)(struct sockaddr_in *, int, long*) ); +extern void refresh_access_data(void (*add_entry)(sockaddr_in4or6 *, int, long*) ); #endif /* COMM_H__ */ From 27086e72fecc1188294c5da05bd70645cbd32ab9 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Thu, 19 Oct 2023 22:03:06 +0200 Subject: [PATCH 12/21] Implemented asynchronous name lookup using C-Ares Added build option --enable-anl to enable asynchronous lookup for net_connect() and logged on players. For the later replacing the ERQ calls, thereby entirely removing the necessity of ERQ for the driver. --- src/autoconf/configure.ac | 34 ++- src/comm.c | 564 ++++++++++++++++++++++++++++---------- src/comm.h | 1 + src/config.h.in | 5 + src/main.c | 1 + src/settings/unitopia | 1 + test/t-ed.c | 1 + 7 files changed, 460 insertions(+), 147 deletions(-) diff --git a/src/autoconf/configure.ac b/src/autoconf/configure.ac index 156e97f41..b5de1fad8 100644 --- a/src/autoconf/configure.ac +++ b/src/autoconf/configure.ac @@ -215,6 +215,7 @@ AC_MY_ARG_ENABLE(filename-spaces,no,,[Allow space characters in filenames]) AC_MY_ARG_ENABLE(share-variables,no,,[Enable clone initialization from blueprint variable values]) AC_MY_ARG_ENABLE(keyword-in,no,,[Enable 'in' as a keyword]) AC_MY_ARG_ENABLE(use-ipv6,no,,[Enables support for IPv6]) +AC_MY_ARG_ENABLE(use-anl,no,,[Enable asynchronous name lookup using C-Ares]) AC_MY_ARG_ENABLE(use-mccp,no,,[Enables MCCP support]) AC_MY_ARG_ENABLE(use-mysql,no,,[Enables mySQL support]) AC_MY_ARG_ENABLE(use-pgsql,no,,[Enables PostgreSQL support]) @@ -516,6 +517,7 @@ AC_CDEF_FROM_ENABLE(share_variables) AC_CDEF_FROM_ENABLE(keyword_in) AC_CDEF_FROM_ENABLE(use_mccp) AC_CDEF_FROM_ENABLE(use_ipv6) +AC_CDEF_FROM_ENABLE(use_anl) AC_CDEF_FROM_ENABLE(use_deprecated) AC_CDEF_FROM_ENABLE(use_parse_command) AC_CDEF_FROM_ENABLE(use_process_string) @@ -1013,6 +1015,33 @@ else enable_use_ipv6=no fi +# --- Asynchronous Name Lookup --- + +ifdef([PKG_PROG_PKG_CONFIG],[PKG_PROG_PKG_CONFIG()],[]) + +AC_MY_SEARCH_LIB(CARES,libcares,lp_cv_has_c_ares,,cares_path, +[ + #include + + void callback(void *arg, int status, int timeouts, struct ares_addrinfo *result): + void foo(ares_channel channel) + { + ares_getaddrinfo(channel, "ldmud.eu", NULL, NULL, callback, NULL); + } +],cares,ares_init,enable_use_anl) + +if test "$lp_cv_has_c_ares" = "yes"; then + AC_DEFINE(HAS_C_ARES, 1, [Does the machine offer C-Ares?]) +else + if test "$enable_use_anl" = "yes"; then + echo "C-Ares library not found - disabling asynchronous name lookup" + AC_NOT_AVAILABLE(use-anl) + fi + + cdef_use_anl="#undef" + enable_use_anl="no" +fi + # --- TLS --- has_tls=no @@ -1668,9 +1697,7 @@ fi # --- SQLite3 --- -ifdef([PKG_PROG_PKG_CONFIG],[PKG_PROG_PKG_CONFIG()],[]) - -AC_MY_SEARCH_LIB(SQLITE3,sqlite3,lp_cv_has_sqlite3,sqlite3,xml_path, +AC_MY_SEARCH_LIB(SQLITE3,sqlite3,lp_cv_has_sqlite3,sqlite3,sqlite_path, [ #include @@ -2541,6 +2568,7 @@ AC_SUBST(cdef_filename_spaces) AC_SUBST(cdef_share_variables) AC_SUBST(cdef_keyword_in) AC_SUBST(cdef_use_ipv6) +AC_SUBST(cdef_use_anl) AC_SUBST(cdef_use_mysql) AC_SUBST(cdef_use_pgsql) AC_SUBST(cdef_use_sqlite) diff --git a/src/comm.c b/src/comm.c index 58c53324e..9d35c4e17 100644 --- a/src/comm.c +++ b/src/comm.c @@ -116,6 +116,14 @@ # include SOCKET_INC #endif +#ifdef USE_ANL +# ifdef HAS_C_ARES +# include +# else +# undef USE_ANL +# endif +#endif + #include "comm.h" #include "access_check.h" #include "actions.h" @@ -204,6 +212,10 @@ extern int socketpair(int, int, int, int[2]); # define INET_ADDRSTRLEN 16 #endif +#if defined(ERQ_DEMON) || defined(USE_ANL) +# define LOOKUP_IP_ADRESS +#endif + /* Amazing how complicated networking can be, hm? */ /*-------------------------------------------------------------------------*/ @@ -284,6 +296,10 @@ statcounter_t inet_volume_in = 0; #endif +#ifdef USE_ANL +ares_channel anl_channel; +#endif + /*-------------------------------------------------------------------------*/ #ifdef ERQ_DEMON @@ -340,6 +356,9 @@ static erq_callback_t pending_erq[MAX_PENDING_ERQ+1]; static erq_callback_t *free_erq; /* The first free entry in the freelist in pending_erq[] */ +#endif /* ERQ_DEMON */ + +#ifdef LOOKUP_IP_ADRESS /* The size of the IPTABLE depends on the number of users, * and is at least 200. */ @@ -371,7 +390,7 @@ static int ipcur = 0; /* Index of the next entry to use in the iptable[]. */ -#endif /* ERQ_DEMON */ +#endif /* LOOKUP_IP_ADRESS */ /*-------------------------------------------------------------------------*/ @@ -487,18 +506,32 @@ static interactive_t *first_player_for_flush = NULL; typedef enum { ocNotUsed /* Entry not used */ + , ocNameResolutionInThread + /* Name resolution is performed for the connection, + * the efun is still running an will return the status. + */ + , ocNameResolutionOutsideThread + /* Name resolution is performed for the connection, + * status needs to be passed to the logon() function. + */ , ocUsed /* Entry holds pending connection */ , ocLoggingOn /* Entry is doing the LPC logon protocol. * This value should not appear outside of * check_for_out_connections(), if it does, it * means that LPC logon threw an error. */ + , ocFailure /* When ocNameResolutionInThread used to + * report a failure. + */ } OutConnStatus; struct OutConn { sockaddr_in4or6 target; /* Address connected to (allocated) */ object_t * curr_obj; /* Associated object */ - int socket; /* Socket on our side */ + union { + int socket; /* Socket on our side */ + int error; /* Error code for .ocFailure */ + }; OutConnStatus status; /* Status of this entry */ } outconn[MAX_OUTCONN]; @@ -527,7 +560,6 @@ static long read_32(char *); static Bool send_erq(int handle, int request, const char *arg, size_t arglen); static void shutdown_erq_demon(void); static void stop_erq_demon(Bool); -static string_t * lookup_ip_entry (in4or6_addr addr, Bool useErq); static void add_ip_entry(in4or6_addr addr, const char *name); #ifdef USE_IPV6 static void update_ip_entry(const char *oldname, const char *newname); @@ -535,6 +567,12 @@ static void update_ip_entry(const char *oldname, const char *newname); #endif /* ERQ_DEMON */ +#ifdef LOOKUP_IP_ADRESS + +static string_t * lookup_ip_entry (in4or6_addr addr, Bool useErq); + +#endif /* LOOKUP_IP_ADRESS */ + static INLINE ssize_t comm_send_buf(char *msg, size_t size, interactive_t *ip); #ifdef USE_IPV6 @@ -957,6 +995,45 @@ add_inherited_port (const char *port) return false; } /* add_inherited_port() */ +/*-------------------------------------------------------------------------*/ +#if defined(USE_ANL) && defined(ALLOCATOR_WRAPPERS) +static void* comm_malloc (size_t size) { return pxalloc(size); } +static void comm_free (void* ptr) { pfree(ptr); } +static void* comm_realloc (void* ptr, size_t size) { return prexalloc(ptr, size); } +#endif +/*-------------------------------------------------------------------------*/ +void +comm_init () + +/* General initialization of communications functions. + */ + +{ +#ifdef LOOKUP_IP_ADRESS + /* Initialize the IP name lookup table */ + memset(iptable, 0, sizeof(iptable)); +#endif + +#ifdef USE_ANL + int res; + +# ifdef ALLOCATOR_WRAPPERS + res = ares_library_init_mem(ARES_LIB_INIT_ALL, comm_malloc, comm_free, comm_realloc); +# else + res = ares_library_init(ARES_LIB_INIT_ALL); +# endif + if (res) + fatal("Initialization of C-Ares failed: %s\n", ares_strerror(res)); + + res = ares_init(&anl_channel); + if (res) + fatal("Initialization of C-Ares channel failed: %s\n", ares_strerror(res)); +#endif + + for (int i = 0; i < MAX_OUTCONN; i++) + outconn[i].status = ocNotUsed; +} /* comm_init() */ + /*-------------------------------------------------------------------------*/ void initialize_host_name (const char *hname) @@ -1233,14 +1310,6 @@ prepare_ipc(void) int i; struct sigaction sa; // for installing the signal handlers -#ifdef ERQ_DEMON - /* Initialize the IP name lookup table */ - memset(iptable, 0, sizeof(iptable)); -#endif - - for (i = 0; i < MAX_OUTCONN; i++) - outconn[i].status = ocNotUsed; - /* Initialize the telnet machine unless mudlib_telopts() already * did that. */ @@ -1355,6 +1424,10 @@ ipc_remove (void) shutdown_erq_demon(); #endif +#ifdef USE_ANL + ares_destroy(anl_channel); + ares_library_cleanup(); +#endif } /* ipc_remove() */ /*-------------------------------------------------------------------------*/ @@ -2281,6 +2354,11 @@ get_message (char *buff, size_t *bufflength) } } /* for (all players) */ + for (i = 0; i < MAX_OUTCONN; i++) + { + if (outconn[i].status == ocUsed) + FD_SET(outconn[i].socket, &writefds); + } #ifdef ERQ_DEMON if (erq_demon >= 0) { @@ -2296,7 +2374,12 @@ get_message (char *buff, size_t *bufflength) pg_setfds(&readfds, &writefds, &nfds); #endif #ifdef USE_PYTHON - python_set_fds(&readfds, &writefds, &pexceptfds, &nfds); + python_set_fds(&readfds, &writefds, &pexceptfds, &nfds); +#endif +#ifdef USE_ANL + i = ares_fds(anl_channel, &readfds, &writefds); + if (i > nfds) + nfds = i; #endif /* select() until time is up or there is data */ @@ -2382,6 +2465,9 @@ get_message (char *buff, size_t *bufflength) #ifdef USE_PYTHON python_handle_fds(&readfds, &writefds, &exceptfds, nfds); #endif +#ifdef USE_ANL + ares_process(anl_channel, &readfds, &writefds); +#endif /* Initialise the user scan */ CmdsGiven = 0; @@ -2631,6 +2717,8 @@ get_message (char *buff, size_t *bufflength) #endif /* ERQ_DEMON */ + check_for_out_connections(); + /* --- Try to get a new player --- */ for (i = 0; i < numports; i++) { @@ -3696,7 +3784,7 @@ new_player ( object_t *ob, SOCKET_T new_socket { new_interactive->snoop_on->snoop_by = ob; } -#ifdef ERQ_DEMON +#ifdef LOOKUP_IP_ADRESS (void) lookup_ip_entry(new_interactive->addr.sin4or6_addr, MY_TRUE); /* TODO: We could pass the retrieved hostname right to login */ #endif @@ -6201,16 +6289,41 @@ update_ip_entry (const char *oldname, const char *newname) } /* update_ip_entry() */ /*-------------------------------------------------------------------------*/ - #endif /* USE_IPV6 */ - +#endif /* ERQ_DEMON */ + +#ifdef LOOKUP_IP_ADRESS +/*-------------------------------------------------------------------------*/ + +#ifdef USE_ANL +static void +lookup_ip_callback (void *arg, int status, int timeouts, char *node, char *service) + +/* Callback from C-Ares with a reverse lookup result. + * If successful, we just enter it into the IP table. + */ + +{ + struct ipentry* entry = (struct ipentry*) arg; + if (node != NULL) + { + free_mstring(entry->name); + entry->name = new_tabled(node, STRING_ASCII); + } + else if (d_flag > 1) + { + debug_message("%s Host lookup failed for %s.\n", time_stamp(), get_txt(entry->name)); + } +} /* lookup_ip_callback() */ +#endif /*-------------------------------------------------------------------------*/ static string_t * -lookup_ip_entry (in4or6_addr addr, Bool useErq) +lookup_ip_entry (in4or6_addr addr, Bool use_async_lookup) /* Lookup the IP address and return an uncounted pointer to * a shared string with the hostname. The function looks first in the - * iptable[], then, if not found there and is true, asks the ERQ. + * iptable[], then, if not found there and is true, + * an asynchronous name lookup (either C-Ares or ERQ) is initiated. * If the hostname can not be found, NULL is returned. */ @@ -6252,20 +6365,48 @@ lookup_ip_entry (in4or6_addr addr, Bool useErq) ipcur = (ipcur+1) % IPSIZE; /* If we have the erq and may use it, lookup the real hostname */ - if (erq_demon >= 0 && useErq) + if (use_async_lookup) { -#ifndef USE_IPV6 - send_erq(ERQ_HANDLE_RLOOKUP, ERQ_RLOOKUP, (char *)&addr.s_addr, sizeof(addr.s_addr)); -#else - send_erq(ERQ_HANDLE_RLOOKUPV6, ERQ_RLOOKUPV6, get_txt(ipname) - , mstrsize(ipname)); +#ifdef USE_ANL + sockaddr_in4or6 saddr; + struct sockaddr* paddr = (struct sockaddr*) &saddr; + size_t paddrlen = sizeof(saddr); + +# ifdef USE_IPV6 + struct sockaddr_in mapped_addr; + if (IN6_IS_ADDR_V4MAPPED(&addr)) + { + mapped_addr.sin_family = AF_INET; + mapped_addr.sin_addr.s_addr = ((uint32_t*)&addr)[3]; + + paddr = (struct sockaddr*) &mapped_addr; + paddrlen = sizeof(mapped_addr); + } + else +# endif + { + memset(&saddr, 0, sizeof(saddr)); + saddr.sin4or6_family = AF_INET4OR6; + saddr.sin4or6_addr = addr; + } + + ares_getnameinfo(anl_channel, paddr, paddrlen, ARES_NI_LOOKUPHOST|ARES_NI_NAMEREQD, lookup_ip_callback, iptable + i); +#else /* ERQ_DEMON */ + if (erq_demon >= 0) + { +# ifndef USE_IPV6 + send_erq(ERQ_HANDLE_RLOOKUP, ERQ_RLOOKUP, (char *)&addr.s_addr, sizeof(addr.s_addr)); +# else + send_erq(ERQ_HANDLE_RLOOKUPV6, ERQ_RLOOKUPV6, get_txt(ipname), mstrsize(ipname)); +# endif + } #endif } - return iptable[ipcur].name; + return iptable[i].name; } -#endif /* ERQ_DEMON */ +#endif /* LOOKUP_IP_ADRESS */ /* End of ERQ Support */ /*=========================================================================*/ @@ -6444,12 +6585,14 @@ count_comm_refs (void) } } -#ifdef ERQ_DEMON +#ifdef LOOKUP_IP_ADRESS for(i = 0; i < IPSIZE; i++) { if (iptable[i].name) count_ref_from_string(iptable[i].name); } +#endif /* LOOKUP_IP_ADRESS*/ +#ifdef ERQ_DEMON for (i = sizeof (pending_erq) / sizeof (*pending_erq); --i >= 0;) { count_ref_in_vector(&pending_erq[i].fun, 1); @@ -8078,8 +8221,16 @@ check_for_out_connections (void) for (i = 0; i < MAX_OUTCONN; i++) { - if (outconn[i].status == ocNotUsed) + if (outconn[i].status == ocNotUsed + || outconn[i].status == ocNameResolutionOutsideThread) + continue; + + if (outconn[i].status == ocFailure /* shouldn't happen */ + || outconn[i].status == ocNameResolutionInThread) + { + outconn[i].status = ocNotUsed; continue; + } if (!outconn[i].curr_obj) /* shouldn't happen */ { @@ -8174,6 +8325,201 @@ check_for_out_connections (void) } } /* check_for_out_connections() */ +/*-------------------------------------------------------------------------*/ +static void +net_connect_error (struct OutConn* conn, int error) + +/* Report an error either to the caller of net_connect() or via logon(). + */ + +{ + if (conn->status == ocNameResolutionInThread) + { + conn->status = ocFailure; + conn->error = error; + } + else + { + conn->status = ocLoggingOn; + logon_object(conn->curr_obj, -1); + + conn->status = ocNotUsed; + free_object(conn->curr_obj, "net_connect"); + } +} /* net_connect_error() */ + +/*-------------------------------------------------------------------------*/ +#ifdef USE_ANL +#define FREEADDRINFO ares_freeaddrinfo +static void +net_connect_callback (void *arg, int status, int timeouts, struct ares_addrinfo *result) + +/* This function is called when a name lookup completed. + */ +{ + struct OutConn* conn = (struct OutConn*)arg; + struct ares_addrinfo_node *entry; + + if (status) + { + ares_freeaddrinfo(result); + net_connect_error(conn, NC_EUNKNOWNHOST); + return; + } + + if (!conn->curr_obj || (conn->curr_obj->flags & O_DESTRUCTED)) + { + free_object(conn->curr_obj, "net_connect"); + conn->status = ocNotUsed; + return; + } + + entry = result->nodes; + +#else +#define FREEADDRINFO freeaddrinfo +static void +net_connect_continue (struct OutConn* conn, struct addrinfo *result) + +/* Called from f_net_connect() after name resolution to continue connection. + */ +{ + // try each address until we successfully connect or run out of + // addresses. In case of errors, close the socket. + struct addrinfo *entry = result; +#endif + + int last_error = NC_EUNKNOWNHOST; + for (; entry != NULL; entry = entry->ai_next) + { + object_t *user; + int ret, sfd; + +#if defined(USE_ANL) && defined(USE_IPV6) +# define target_addrlen sizeof(target_mapped_addr) +# define target_family AF_INET6 +# define target_addr &target_mapped_addr + struct sockaddr_in6 target_mapped_addr; + if (entry->ai_family == AF_INET) + { + struct sockaddr_in *addr4 = (struct sockaddr_in*)entry->ai_addr; + + memset(&target_mapped_addr, 0, sizeof(target_mapped_addr)); + target_mapped_addr.sin6_family = AF_INET6; + target_mapped_addr.sin6_port = addr4->sin_port; + CREATE_IPV6_MAPPED(&target_mapped_addr.sin6_addr, addr4->sin_addr.s_addr); + } + else if (entry->ai_family == AF_INET6) + target_mapped_addr = *(struct sockaddr_in6*)entry->ai_addr; + else + continue; +#else +# define target_addr entry->ai_addr +# define target_addrlen entry->ai_addrlen +# define target_family entry->ai_family +#endif + + sfd = socket(target_family, entry->ai_socktype, entry->ai_protocol); + if (sfd==-1) + { + if (errno == EMFILE || errno == ENFILE + || errno == ENOBUFS || errno == ENOMEM) + { + // insufficient system ressources, probably transient error, + // but it is unlikely to be different for the next address. + // We exit here, the caller should try again. + FREEADDRINFO(result); + net_connect_error(conn, NC_ENORESSOURCES); + return; + } + + last_error = NC_ENOSOCKET; + continue; // try next address + } + + set_socket_nonblocking(sfd); + set_socket_nosigpipe(sfd); + + /* On multihomed machines it is important to bind the socket to + * the proper IP address. + */ + ret = bind(sfd, (struct sockaddr *) &host_ip_addr_template, sizeof(host_ip_addr_template)); + if (ret==-1) + { + if (errno==ENOBUFS) + { + // insufficient system ressources, probably transient error, + // but unlikely to be different for the next address. Caller + // should try again later. + FREEADDRINFO(result); + socket_close(sfd); + net_connect_error(conn, NC_ENORESSOURCES); + return; + } + + socket_close(sfd); + last_error = NC_ENOBIND; + continue; + } + + ret = connect(sfd, target_addr, target_addrlen); + if (ret == -1 && errno != EINPROGRESS) + { + // no succes connecting. :-( Store last error in rc. + if (errno==ECONNREFUSED) + { + // no one listening at remote end... happens, no real error... + last_error = NC_ECONNREFUSED; + } + else + { + last_error = NC_ENOCONNECT; + } + socket_close(sfd); + continue; + } + + last_error = NC_SUCCESS; + + /* Store the connection in the outconn[] table even if + * we can complete it immediately. For the reason see below. + */ + conn->socket = sfd; + conn->target = *((sockaddr_in4or6 *)target_addr); + + // no longer need the results from getaddrinfo() + FREEADDRINFO(result); + + if (errno == EINPROGRESS) + { + /* Can't complete right now */ + conn->status = ocUsed; + return; + } + + /* Attempt the logon. By setting the outconn[].status to + * ocLoggingOn, any subsequent call to check_for_out_connections() + * will clean up for us. + */ + conn->status = ocLoggingOn; + + user = command_giver; + new_player(conn->curr_obj, sfd, &(conn->target), sizeof(conn->target), 0); + command_giver = user; + + /* All done - clean up */ + conn->status = ocNotUsed; + free_object(conn->curr_obj, "net_connect"); + return; + } + + FREEADDRINFO(result); + net_connect_error(conn, last_error); +#undef target_addr +#undef target_addrlen +#undef target_family +} /* net_connect_callback/net_connect_continue() */ + /*-------------------------------------------------------------------------*/ svalue_t * f_net_connect (svalue_t *sp) @@ -8221,10 +8567,14 @@ f_net_connect (svalue_t *sp) /* Try the connection */ rc = 0; do { - int sfd, n, ret; - object_t *user; + int n; +#ifdef USE_ANL + struct ares_addrinfo_hints hints; +#else struct addrinfo hints; - struct addrinfo *result, *rp; + struct addrinfo *result; + int ret; +#endif char port_string[6]; // port is needed as a string @@ -8249,8 +8599,33 @@ f_net_connect (svalue_t *sp) break; } - /* Attempt the connection */ - + /* Reserve the connection entry. */ + outconn[n].curr_obj = ref_object(ob, "net_conect"); + outconn[n].status = ocNameResolutionInThread; + + /* Lookup the address */ +#ifdef USE_ANL + memset(&hints, 0, sizeof(hints)); +#ifndef USE_IPV6 + hints.ai_family = AF_INET; +#else + // C-Ares does not (yet?) support V4 mapping, so we're doing it ourselves. + hints.ai_family = AF_UNSPEC; +#endif + hints.ai_flags = ARES_AI_ADDRCONFIG|ARES_AI_NUMERICSERV; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + +#ifdef INSIDE_TRAVIS + // The Travis-CI environment only has loopback devices activated, + // the AI_ADDRCONFIG would therefore never find a name. + hints.ai_flags &= ~ARES_AI_ADDRCONFIG; +#endif + + ares_getaddrinfo(anl_channel, host, port_string, &hints, net_connect_callback, &(outconn[n])); + if (outconn[n].status == ocNameResolutionInThread) + outconn[n].status = ocNameResolutionOutsideThread; +#else memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET4OR6; #ifndef USE_IPV6 @@ -8271,128 +8646,29 @@ f_net_connect (svalue_t *sp) hints.ai_flags &= ~AI_ADDRCONFIG; #endif - /* TODO: Uh-oh, blocking DNS in the execution thread. - * TODO:: Better would be to start an ERQ lookup and fill in the - * TODO:: data in the background. + /* Uh-oh, blocking DNS in the execution thread. */ ret = getaddrinfo(host, port_string, &hints, &result); if (ret != 0) { rc = NC_EUNKNOWNHOST; + outconn[n].status = ocNotUsed; + free_object(outconn[n].curr_obj, "net_connect"); break; } - // try each address until we successfully connect or run out of - // addresses. In case of errors, close the socket. - for (rp = result; rp != NULL; rp = rp->ai_next) - { - sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (sfd==-1) - { - if (errno == EMFILE || errno == ENFILE - || errno == ENOBUFS || errno == ENOMEM) - { - // insufficient system ressources, probably transient error, - // but it is unlikely to be different for the next address. - // We exit here, the caller should try again. - rc = NC_ENORESSOURCES; - rp = NULL; - break; - } - else - { - // store only last error - rc = NC_ENOSOCKET; - } - continue; // try next address - } - - set_socket_nonblocking(sfd); - set_socket_nosigpipe(sfd); - - /* On multihomed machines it is important to bind the socket to - * the proper IP address. - */ - ret = bind(sfd, (struct sockaddr *) &host_ip_addr_template, sizeof(host_ip_addr_template)); - if (ret==-1) - { - if (errno==ENOBUFS) - { - // insufficient system ressources, probably transient error, - // but unlikely to be different for the next address. Caller - // should try again later. - rc = NC_ENORESSOURCES; - rp = NULL; - socket_close(sfd); - break; - } - else - { - // store only last error. - rc = NC_ENOBIND; - } - socket_close(sfd); - continue; - } - - ret = connect(sfd, rp->ai_addr, rp->ai_addrlen); - if (ret != -1 || errno == EINPROGRESS) - break; // success! no need to try further - - // no succes connecting. :-( Store last error in rc. - if (errno==ECONNREFUSED) - { - // no one listening at remote end... happens, no real error... - rc = NC_ECONNREFUSED; - } - else - { - rc = NC_ENOCONNECT; - } - socket_close(sfd); - } - - if (rp == NULL) - { - // No address succeeded, rc contains the last 'error', we just - // exit here. - freeaddrinfo(result); - break; - } - // at this point we a connected socket - rc = NC_SUCCESS; - - /* Store the connection in the outconn[] table even if - * we can complete it immediately. For the reason see below. - */ - outconn[n].socket = sfd; - outconn[n].target = *((sockaddr_in4or6 *)rp->ai_addr); - outconn[n].curr_obj = ref_object(ob, "net_conect"); - - // no longer need the results from getaddrinfo() - freeaddrinfo(result); + /* Attempt the connection */ + net_connect_continue(&(outconn[n]), result); +#endif - if (errno == EINPROGRESS) + if (outconn[n].status == ocFailure) { - /* Can't complete right now */ - outconn[n].status = ocUsed; - break; + rc = outconn[n].error; + outconn[n].status = ocNotUsed; + free_object(outconn[n].curr_obj, "net_connect"); } - - /* Attempt the logon. By setting the outconn[].status to - * ocLoggingOn, any subsequent call to check_for_out_connections() - * will clean up for us. - */ - outconn[n].status = ocLoggingOn; - - user = command_giver; - inter_sp = sp; - new_player(ob, sfd, (sockaddr_in4or6 *)rp->ai_addr, sizeof(sockaddr_in4or6), 0); - command_giver = user; - - /* All done - clean up */ - outconn[n].status = ocNotUsed; - free_object(outconn[n].curr_obj, "net_connect"); + else + rc = 0; }while(0); /* Return the result */ @@ -8904,7 +9180,7 @@ f_interactive_info (svalue_t *sp) /* Connection information */ case II_IP_NAME: -#ifdef ERQ_DEMON +#ifdef LOOKUP_IP_ADRESS { string_t * hname; diff --git a/src/comm.h b/src/comm.h index c5f30638a..82bc13899 100644 --- a/src/comm.h +++ b/src/comm.h @@ -430,6 +430,7 @@ extern statcounter_t inet_volume_in; /* --- Prototypes --- */ +extern void comm_init(); extern void initialize_host_name (const char *hname); extern void initialize_host_ip_number(const char *, const char *); extern bool add_listen_port(const char *port); diff --git a/src/config.h.in b/src/config.h.in index b66e3cd22..1961de29c 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -208,6 +208,11 @@ */ @cdef_use_ipv6@ USE_IPV6 +/* Define this if you want asynchronous name lookup using libanl + * instead of using the ERQ. + */ +@cdef_use_anl@ USE_ANL + /* maximum number of concurrent outgoing connection attempts by net_connect() * (that is connections that are in progress but not fully established yet). */ diff --git a/src/main.c b/src/main.c index 19da8cef9..e7f120297 100644 --- a/src/main.c +++ b/src/main.c @@ -302,6 +302,7 @@ main (int argc, char **argv) mb_init(); init_interpret(); rx_init(); + comm_init(); put_number(&const0, 0); put_number(&const1, 1); diff --git a/src/settings/unitopia b/src/settings/unitopia index 5d3b0bd60..a8e5bff9d 100755 --- a/src/settings/unitopia +++ b/src/settings/unitopia @@ -44,6 +44,7 @@ with_max_cost=500000 with_portno=3333 with_udp_port=3335 enable_use_ipv6=yes +enable_use_anl=yes enable_use_mccp=yes enable_use_tls=gnu with_tls_keyfile=no diff --git a/test/t-ed.c b/test/t-ed.c index b5c4055e4..720eabecd 100644 --- a/test/t-ed.c +++ b/test/t-ed.c @@ -115,6 +115,7 @@ void start_ed(string content, coroutine cb) rm("/dummy-ed"); ed_cb = cb; + set_this_player(this_object()); ed("/dummy-ed", "ed_ends"); } From ef12e038bc5e6389d0cd315a0ea4e443b096131a Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Fri, 10 Nov 2023 22:43:19 +0100 Subject: [PATCH 13/21] Added a few sefun implementations of LDMud 3.2 efuns Simul-efun implementations of deprecated efuns from the mud Seifenblase by Myonara@UNItopia and Gnomi@UNItopia. --- mudlib/deprecated/allocate_mapping.c | 11 +++++++++++ mudlib/deprecated/copy_mapping.c | 12 ++++++++++++ mudlib/deprecated/extract.c | 12 ++++++++++++ mudlib/deprecated/file_name.c | 12 ++++++++++++ mudlib/deprecated/filter_array.c | 15 +++++++++++++++ mudlib/deprecated/filter_mapping.c | 15 +++++++++++++++ mudlib/deprecated/map_array.c | 15 +++++++++++++++ mudlib/deprecated/map_mapping.c | 15 +++++++++++++++ mudlib/deprecated/mapping_contains.c | 21 +++++++++++++++++++++ mudlib/deprecated/member_array.c | 12 ++++++++++++ mudlib/deprecated/query_imp_port.c | 14 ++++++++++++++ mudlib/deprecated/send_imp.c | 18 ++++++++++++++++++ mudlib/deprecated/strlen.c | 12 ++++++++++++ 13 files changed, 184 insertions(+) create mode 100644 mudlib/deprecated/allocate_mapping.c create mode 100644 mudlib/deprecated/copy_mapping.c create mode 100644 mudlib/deprecated/extract.c create mode 100644 mudlib/deprecated/file_name.c create mode 100644 mudlib/deprecated/filter_array.c create mode 100644 mudlib/deprecated/filter_mapping.c create mode 100644 mudlib/deprecated/map_array.c create mode 100644 mudlib/deprecated/map_mapping.c create mode 100644 mudlib/deprecated/mapping_contains.c create mode 100644 mudlib/deprecated/member_array.c create mode 100644 mudlib/deprecated/query_imp_port.c create mode 100644 mudlib/deprecated/send_imp.c create mode 100644 mudlib/deprecated/strlen.c diff --git a/mudlib/deprecated/allocate_mapping.c b/mudlib/deprecated/allocate_mapping.c new file mode 100644 index 000000000..42841f14d --- /dev/null +++ b/mudlib/deprecated/allocate_mapping.c @@ -0,0 +1,11 @@ +/* This sefun is to provide a replacement for the efun allocate_mapping(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ +#if ! __EFUN_DEFINED__(allocate_mapping) + +mapping allocate_mapping(int size, int width = 1) +{ + return m_allocate(size, width); +} + +#endif diff --git a/mudlib/deprecated/copy_mapping.c b/mudlib/deprecated/copy_mapping.c new file mode 100644 index 000000000..8b04a3458 --- /dev/null +++ b/mudlib/deprecated/copy_mapping.c @@ -0,0 +1,12 @@ +/* This sefun is to provide a replacement for the efun copy_mapping(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ + +#if ! __EFUN_DEFINED__(copy_mapping) + +mapping copy_mapping(mapping m) +{ + return copy(m); +} + +#endif diff --git a/mudlib/deprecated/extract.c b/mudlib/deprecated/extract.c new file mode 100644 index 000000000..b2d52bd7a --- /dev/null +++ b/mudlib/deprecated/extract.c @@ -0,0 +1,12 @@ +/* This sefun is to provide a replacement for the efun extract(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ + +#if ! __EFUN_DEFINED__(extract) + +string extract(string str, int from, int to = -1) +{ + return str[>from..>to]; +} + +#endif diff --git a/mudlib/deprecated/file_name.c b/mudlib/deprecated/file_name.c new file mode 100644 index 000000000..33d6ffbb9 --- /dev/null +++ b/mudlib/deprecated/file_name.c @@ -0,0 +1,12 @@ +/* This sefun is to provide a replacement for the efun file_name(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ + +#if ! __EFUN_DEFINED__(file_name) + +string file_name(object ob = previous_object()) +{ + return object_name(ob); +} + +#endif diff --git a/mudlib/deprecated/filter_array.c b/mudlib/deprecated/filter_array.c new file mode 100644 index 000000000..3d72eb9b6 --- /dev/null +++ b/mudlib/deprecated/filter_array.c @@ -0,0 +1,15 @@ +/* This sefun is to provide a replacement for the efun filter_array(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ + +#if ! __EFUN_DEFINED__(filter_array) + +mixed* filter_array(mixed *arr, string|closure|mapping f, varargs mixed* args) +{ + if (efun::extern_call()) + efun::set_this_object(efun::previous_object()); + + return filter(arr, f, args...); +} + +#endif diff --git a/mudlib/deprecated/filter_mapping.c b/mudlib/deprecated/filter_mapping.c new file mode 100644 index 000000000..c908f77a0 --- /dev/null +++ b/mudlib/deprecated/filter_mapping.c @@ -0,0 +1,15 @@ +/* This sefun is to provide a replacement for the efun filter_mapping(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ + +#if ! __EFUN_DEFINED__(filter_mapping) + +mapping filter_mapping(mapping m, string|closure|mapping f, varargs mixed* args) +{ + if (efun::extern_call()) + efun::set_this_object(efun::previous_object()); + + return filter_indices(m, f, args...); +} + +#endif diff --git a/mudlib/deprecated/map_array.c b/mudlib/deprecated/map_array.c new file mode 100644 index 000000000..ea0495bc3 --- /dev/null +++ b/mudlib/deprecated/map_array.c @@ -0,0 +1,15 @@ +/* This sefun is to provide a replacement for the efun map_array(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ + +#if ! __EFUN_DEFINED__(map_array) + +mixed* map_array(mixed *arr, string|closure|mapping f, varargs mixed* args) +{ + if (efun::extern_call()) + efun::set_this_object(efun::previous_object()); + + return map(arr, f, args...); +} + +#endif diff --git a/mudlib/deprecated/map_mapping.c b/mudlib/deprecated/map_mapping.c new file mode 100644 index 000000000..0269e81ab --- /dev/null +++ b/mudlib/deprecated/map_mapping.c @@ -0,0 +1,15 @@ +/* This sefun is to provide a replacement for the efun map_mapping(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ + +#if ! __EFUN_DEFINED__(map_mapping) + +mapping map_mapping(mapping m, string|closure|mapping f, varargs mixed* args) +{ + if (efun::extern_call()) + efun::set_this_object(efun::previous_object()); + + return map_indices(m, f, args...); +} + +#endif diff --git a/mudlib/deprecated/mapping_contains.c b/mudlib/deprecated/mapping_contains.c new file mode 100644 index 000000000..6432a3d8d --- /dev/null +++ b/mudlib/deprecated/mapping_contains.c @@ -0,0 +1,21 @@ +/* This sefun is to provide a replacement for the efun mapping_contains(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ + +#if ! __EFUN_DEFINED__(mapping_contains) + +int mapping_contains(varargs mixed* args) +{ + mapping m; + string key; + + if (sizeof(args) < 2) + raise_error(sprintf("Not enough args for mapping_contains: got %d, expected 2.\n", sizeof(args))); + + m = args[<2]; + key = args[<1]; + + return m_contains(&(args[..<3])..., m, key); +} + +#endif diff --git a/mudlib/deprecated/member_array.c b/mudlib/deprecated/member_array.c new file mode 100644 index 000000000..1a8ddbb38 --- /dev/null +++ b/mudlib/deprecated/member_array.c @@ -0,0 +1,12 @@ +/* This sefun is to provide a replacement for the efun member_array(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ + +#if ! __EFUN_DEFINED__(member_array) + +int member_array(mixed item, string|mixed* arr) +{ + return member(arr, item); +} + +#endif diff --git a/mudlib/deprecated/query_imp_port.c b/mudlib/deprecated/query_imp_port.c new file mode 100644 index 000000000..981be6707 --- /dev/null +++ b/mudlib/deprecated/query_imp_port.c @@ -0,0 +1,14 @@ +/* This sefun is to provide a replacement for the efun query_imp_port(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ + +#if ! __EFUN_DEFINED__(query_imp_port) + +#include + +int query_imp_port() +{ + return efun::driver_info(DI_UDP_PORT); +} + +#endif diff --git a/mudlib/deprecated/send_imp.c b/mudlib/deprecated/send_imp.c new file mode 100644 index 000000000..182d86f77 --- /dev/null +++ b/mudlib/deprecated/send_imp.c @@ -0,0 +1,18 @@ +/* This sefun is to provide a replacement for the efun send_imp(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ + +#if ! __EFUN_DEFINED__(send_imp) + +int send_imp(string host, int port, bytes|int*|string message) +{ + if (efun::extern_call()) + efun::set_this_object(efun::previous_object()); + + if (stringp(message)) + message = to_bytes(message, "ISO8859-1"); + + return efun::send_udp(host, port, message); +} + +#endif diff --git a/mudlib/deprecated/strlen.c b/mudlib/deprecated/strlen.c new file mode 100644 index 000000000..945c02512 --- /dev/null +++ b/mudlib/deprecated/strlen.c @@ -0,0 +1,12 @@ +/* This sefun is to provide a replacement for the efun strlen(). + * Feel free to add it to your mudlibs, if you have much code relying on that. + */ + +#if ! __EFUN_DEFINED__(strlen) + +int strlen(string str) +{ + return sizeof(str); +} + +#endif From 8e62b65f8b415f4cc5405ed0bd984eb668396c44 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Sun, 12 Nov 2023 18:48:41 +0100 Subject: [PATCH 14/21] Added 'detect_end' member to compile_string_options documentation Forgot to add the already existing member to the documentation. --- doc/structs/compile_string_options | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/structs/compile_string_options b/doc/structs/compile_string_options index 309b7ced6..2920d4f9a 100644 --- a/doc/structs/compile_string_options +++ b/doc/structs/compile_string_options @@ -15,6 +15,8 @@ DEFINITION int compile_expression; int compile_block; int as_async; + + int detect_end; }; DESCRIPTION From a04e3a7a0aa5fe4a0a431298f9122acb7dff5456 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Sun, 19 Nov 2023 19:07:44 +0100 Subject: [PATCH 15/21] Fix function lookup in compile_string() with use_object_functions When use_object_functions is true, a large function offset was interpreted as being undefined. --- src/prolang.y | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/prolang.y b/src/prolang.y index 2a242971c..b6d95abf1 100644 --- a/src/prolang.y +++ b/src/prolang.y @@ -17549,6 +17549,10 @@ function_call: program_t *prog = get_current_object_program(); inherited_function.flags = prog->functions[f]; + if (inherited_function.flags & NAME_INHERITED) + inherited_function.flags &= ~INHERIT_MASK; + else + inherited_function.flags &= ~FUNSTART_MASK; get_function_information(&inherited_function, prog, f); arg_types = prog->argument_types; types = prog->types; From 3f19bf44a380d7905bb7676d308699c4174851ad Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Sun, 12 Nov 2023 18:50:42 +0100 Subject: [PATCH 16/21] Implement to_type() efun to_type() converts a value to a given target type. If required, does so recursively. --- doc/concepts/python | 9 + doc/efun/to_type | 58 ++ doc/structs/to_type_options | 17 + src/efuns.c | 1517 +++++++++++++++++++++++++++++++---- src/efuns.h | 1 + src/func_spec | 1 + src/interpret.c | 2 +- src/lex.c | 42 +- src/lex.h | 2 +- src/mapping.c | 4 +- src/pkg-python.c | 180 ++++- src/pkg-python.h | 1 + src/strfuns.c | 719 +++++++++-------- src/strfuns.h | 4 + src/struct_spec | 10 + test/t-efuns.c | 107 ++- test/t-python/master.c | 10 + test/t-python/startup.py | 3 + 18 files changed, 2164 insertions(+), 523 deletions(-) create mode 100644 doc/efun/to_type create mode 100644 doc/structs/to_type_options diff --git a/doc/concepts/python b/doc/concepts/python index db2f1df2b..e06fa10ba 100644 --- a/doc/concepts/python +++ b/doc/concepts/python @@ -426,6 +426,15 @@ PYTHON TYPES from __save__ as the only argument and will return the restored object. + __convert__ + Used by to_type() to convert the Python object into another + type. Gets the target type object as its first argument and + the to_type_options struct (which may be None) as a second + argument. If the conversion is not supported, can either + return None or an exception. Only if the function does not + exist, __int__, __float__, __str__ or __bytes__ are called + if appropriate. + EXAMPLE import ldmud diff --git a/doc/efun/to_type b/doc/efun/to_type new file mode 100644 index 000000000..dc93eb11a --- /dev/null +++ b/doc/efun/to_type @@ -0,0 +1,58 @@ +SYNOPSIS + mixed to_type(mixed value, lpctype type) + mixed to_type(mixed value, lpctype type, struct to_type_options options) + +DESCRIPTION + Converts to . This efun will apply any type conversions + recursively to make conform to . The following + conversions are available: + + source type target types + ------------- ---------------------------------------- + array (int) string, bytes + array (mixed) quoted array, struct, mapping + bytes string, int* + closure object, lwobject, int, string + coroutine string + int float, string + float int, string + lpctype lpctype*, mapping, string + lwobject string + mapping struct, mixed* + object string + quoted array mixed* + string symbol, int, float, lpctype, object, bytes, int* + struct struct, mapping, mixed*, string + symbol string, int* + + If multiple conversions are possible (e.g. to_type("10", ) + the conversion will be selected in order as given in the table above. + If multiple array or struct types are given, the order is undefined. + Conversions, where elements (e.g. from an array, mapping or struct) + need to be discarded, are not considered. + + Optionally the function accepts a struct with additional options. + All entries in this struct are optional. These are the members: + + source_encoding: + The encoding (given as string) for conversion from bytes to + string. If not given, such a conversion will not be performed. + + target_encoding: + The encoding (given as string) for conversion from string to + bytes. If not given, such a conversion will not be performed. + + keep_zero: + If set (integer != 0) a zero will not be converted. If unset, + a zero will be converted to string or float if requested, + for all other types it will stay zero. + +EXAMPLES + to_type(({ "10", "20" }), [int*]) -> ({ 10, 20 }) + +HISTORY + Introduced in LDMud 3.6.8. + +SEE ALSO + to_array(E), to_bytes(E), to_float(E), to_int(E), to_lpctype(E), + to_object(E), to_string(E), to_struct(E), to_text(E) diff --git a/doc/structs/to_type_options b/doc/structs/to_type_options new file mode 100644 index 000000000..98a77ba97 --- /dev/null +++ b/doc/structs/to_type_options @@ -0,0 +1,17 @@ +NAME + to_type_options + +DEFINITION + struct to_type_options + { + string source_encoding; + string target_encoding; + + int keep_zero; + }; + +DESCRIPTION + This struct is used for passing options to the to_type() efun. + +SEE ALSO + to_type(E) diff --git a/src/efuns.c b/src/efuns.c index 87446e0c5..d3019c620 100644 --- a/src/efuns.c +++ b/src/efuns.c @@ -127,6 +127,7 @@ #include "random.h" #include "sha1.h" #include "stdstrings.h" +#include "stdstructs.h" #include "simulate.h" #include "simul_efun.h" #include "strfuns.h" @@ -165,6 +166,7 @@ static char* sscanf_format_str_end; /* Forward declarations */ static void copy_svalue (svalue_t *dest, svalue_t *, struct pointer_table *, int); +static void convert_to_type (svalue_t *dest, svalue_t *src, lpctype_t *type, struct_t *opts, bool keep_zero); /* Macros */ @@ -6162,7 +6164,7 @@ f_to_int (svalue_t *sp) case T_STRING: { unsigned long num = 0; - char * end; + const char * end; char * cp = get_txt(sp->u.str); Bool hasMinus = MY_FALSE; Bool overflow; @@ -6175,7 +6177,7 @@ f_to_int (svalue_t *sp) cp++; } - end = lex_parse_number(cp, &num, &overflow); + end = lex_parse_number(cp, NULL, &num, &overflow); if (end != cp) { if (overflow || ((p_int)num)<0) @@ -6399,6 +6401,102 @@ f_to_string (svalue_t *sp) return sp; } /* f_to_string() */ +/*-------------------------------------------------------------------------*/ +vector_t * +convert_string_to_array (const char* s, size_t len, enum unicode_type unicode, const char* efun_name) + +/* Create an array of the chars from (byte lengths ). + */ + +{ + vector_t *v; + svalue_t *svp; + unsigned char ch; + + if (unicode == STRING_UTF8) + { + bool error; + size_t left = len; + const char* cur = s; + size_t chars = byte_to_char_index(s, len, &error); + + if (error) + errorf("%s(): Invalid character in string at index %zd.\n", efun_name, chars); + + v = allocate_uninit_array((mp_int)chars); + svp = v->item; + + /* This is a UTF8 string, let's decode it. */ + while (left) + { + p_int code; + size_t codelen = utf8_to_unicode(cur, left, &code); + + if (!codelen) + errorf("%s(): Invalid character in string at index %zd.\n", efun_name, + byte_to_char_index(s, len - left, NULL)); + + left -= codelen; + cur += codelen; + + put_number(svp, code); + svp++; + } + + /* We should be at the end, otherwise utf8_to_unicode() + * or byte_to_char_index() did something wrong. + */ + assert(svp == v->item + chars); + } + else + { + v = allocate_uninit_array((mp_int)len); + svp = v->item; + + while (len-- > 0) + { + ch = (unsigned char)*s++; + put_number(svp, ch); + svp++; + } + } + + return v; +} /* convert_string_to_array() */ + +/*-------------------------------------------------------------------------*/ +vector_t * +convert_lpctype_to_array (lpctype_t *t) + +/* Create an array of element types from a (possibly) union type . + */ + +{ + vector_t *vec; + size_t pos = 0, len = 1; + + for (lpctype_t *cur = t; cur->t_class == TCLASS_UNION; cur = cur->t_union.head) + len++; + + vec = allocate_array(len); + while (true) + { + if (t->t_class == TCLASS_UNION) + { + put_ref_lpctype(vec->item + pos, t->t_union.member); + t = t->t_union.head; + } + else + { + put_ref_lpctype(vec->item + pos, t); + break; + } + pos++; + } + + return vec; +} /* convert_lpctype_to_array() */ + /*-------------------------------------------------------------------------*/ svalue_t * f_to_array (svalue_t *sp) @@ -6421,12 +6519,6 @@ f_to_array (svalue_t *sp) */ { - vector_t *v; - char *s; - unsigned char ch; - svalue_t *svp; - p_int len; - switch (sp->type) { default: @@ -6435,59 +6527,14 @@ f_to_array (svalue_t *sp) case T_STRING: case T_BYTES: case T_SYMBOL: + { /* Split the string into an array of ints */ - - len = (p_int)mstrsize(sp->u.str); - s = get_txt(sp->u.str); - - if (sp->type == T_STRING && sp->u.str->info.unicode == STRING_UTF8) - { - bool error; - size_t chars = byte_to_char_index(s, len, &error); - if (error) - errorf("to_array(): Invalid character in string at index %zd.\n", chars); - - v = allocate_uninit_array((mp_int)chars); - svp = v->item; - - /* This is a UTF8 string, let's decode it. */ - while (len) - { - p_int code; - size_t codelen = utf8_to_unicode(s, len, &code); - - if (!codelen) - errorf("to_array(): Invalid character in string at index %zd.\n", - byte_to_char_index(get_txt(sp->u.str), mstrsize(sp->u.str) - len, NULL)); - - len -= codelen; - s += codelen; - - put_number(svp, code); - svp++; - } - - /* We should be at the end, otherwise utf8_to_unicode() - * or byte_to_char_index() did something wrong. - */ - assert(svp == v->item + chars); - } - else - { - v = allocate_uninit_array((mp_int)len); - svp = v->item; - - while (len-- > 0) - { - ch = (unsigned char)*s++; - put_number(svp, ch); - svp++; - } - } + vector_t *v = convert_string_to_array(get_txt(sp->u.str), mstrsize(sp->u.str), sp->u.str->info.unicode, "to_array"); free_svalue(sp); put_array(sp, v); break; + } case T_STRUCT: { vector_t *vec; @@ -6510,29 +6557,8 @@ f_to_array (svalue_t *sp) break; case T_LPCTYPE: { - lpctype_t *t = sp->u.lpctype; - vector_t *vec; - size_t pos = 0; - - len = 1; - for (lpctype_t *cur = t; cur->t_class == TCLASS_UNION; cur = cur->t_union.head) - len++; + vector_t *vec = convert_lpctype_to_array(sp->u.lpctype); - vec = allocate_array(len); - while (true) - { - if (t->t_class == TCLASS_UNION) - { - put_ref_lpctype(vec->item + pos, t->t_union.member); - t = t->t_union.head; - } - else - { - put_ref_lpctype(vec->item + pos, t); - break; - } - pos++; - } free_lpctype(sp->u.lpctype); put_array(sp, vec); break; @@ -6597,6 +6623,7 @@ struct mtos_data_s struct mtos_member_s * first; /* List of found members */ struct mtos_member_s * last; int num; /* Number of members */ + bool has_non_string_key; }; static void @@ -6629,8 +6656,102 @@ map_to_struct_filter (svalue_t *key, svalue_t *data, void *extra) pData->num++; } } + else + pData->has_non_string_key = true; } /* map_to_struct_filter() */ +struct_t * +convert_mapping_to_anonymous_struct (mapping_t *map, bool ignore_non_string_keys) + +/* Creates an anonymous struct with members according to 's entries with + * string keys. If is false, returns NULL if the + * mapping has keys of different type than string. + */ + +{ + struct mtos_data_s data; + struct mtos_member_s * member; + struct_t * st; + int i; + + /* Gather the data from the mapping */ + data.pool = new_mempool(size_mempool(sizeof(struct mtos_member_s))); + if (data.pool == NULL) + { + outofmemory("memory pool"); + /* NOTREACHED */ + } + data.num = 0; + data.first = data.last = NULL; + data.has_non_string_key = false; + + walk_mapping(map, map_to_struct_filter, &data); + + if (data.has_non_string_key && !ignore_non_string_keys) + { + mempool_delete(data.pool); + return NULL; + } + + /* Get the result struct */ + st = struct_new_anonymous(data.num); + if (st == NULL) + { + mempool_delete(data.pool); + outofmemory("result"); + /* NOTREACHED */ + } + + /* Copy the data into the result struct, and also update + * the member names. + */ + for ( i = 0, member = data.first + ; member != NULL && i < data.num + ; i++, member = member->next + ) + { + /* Update the member name */ + free_mstring(st->type->member[i].name); + st->type->member[i].name = ref_mstring(member->name); + + /* Copy the data */ + if (map->num_values == 0) + put_number(&st->member[i], 1); + else if (map->num_values == 1) + { + assign_rvalue_no_free(&st->member[i], member->data); + } + else + { + vector_t * vec; + svalue_t * src, * dest; + int j; + + vec = allocate_uninit_array(map->num_values); + if (vec == NULL) + { + mempool_delete(data.pool); + struct_free(st); + outofmemory("result data"); + /* NOTREACHED */ + } + dest = vec->item; + src = member->data; + for (j = 0; j < map->num_values; j++) + { + assign_rvalue_no_free(dest++, src++); + } + + put_array(&st->member[i], vec); + } /* if (num_values) */ + } /* for (all data) */ + + /* Deallocate helper structures */ + mempool_delete(data.pool); + + return st; +} /* convert_mapping_to_anonymous_struct() */ + svalue_t * v_to_struct (svalue_t *sp, int num_arg) @@ -6812,77 +6933,7 @@ v_to_struct (svalue_t *sp, int num_arg) } else { - struct mtos_data_s data; - struct mtos_member_s * member; - int i; - - /* Gather the data from the mapping */ - data.pool = new_mempool(size_mempool(sizeof(struct mtos_member_s))); - if (data.pool == NULL) - { - outofmemory("memory pool"); - /* NOTREACHED */ - } - data.num = 0; - data.first = data.last = NULL; - - walk_mapping(argp->u.map, map_to_struct_filter, &data); - - /* Get the result struct */ - st = struct_new_anonymous(data.num); - if (st == NULL) - { - mempool_delete(data.pool); - outofmemory("result"); - /* NOTREACHED */ - } - - /* Copy the data into the result struct, and also update - * the member names. - */ - for ( i = 0, member = data.first - ; member != NULL && i < data.num - ; i++, member = member->next - ) - { - /* Update the member name */ - free_mstring(st->type->member[i].name); - st->type->member[i].name = ref_mstring(member->name); - - /* Copy the data */ - if (num_values == 0) - put_number(&st->member[i], 1); - else if (num_values == 1) - { - assign_rvalue_no_free(&st->member[i], member->data); - } - else - { - vector_t * vec; - svalue_t * src, * dest; - int j; - - vec = allocate_uninit_array(num_values); - if (vec == NULL) - { - mempool_delete(data.pool); - struct_free(st); - outofmemory("result data"); - /* NOTREACHED */ - } - dest = vec->item; - src = member->data; - for (j = 0; j < num_values; j++) - { - assign_rvalue_no_free(dest++, src++); - } - - put_array(&st->member[i], vec); - } /* if (num_values) */ - } /* for (all data) */ - - /* Deallocate helper structures */ - mempool_delete(data.pool); + st = convert_mapping_to_anonymous_struct(argp->u.map, true); } free_mapping(argp->u.map); @@ -7090,6 +7141,1174 @@ f_to_object (svalue_t *sp) return sp; } /* f_to_object() */ +/*-------------------------------------------------------------------------*/ +static bool +convert_string (svalue_t *dest, string_t *str, const char* text, size_t len, enum unicode_type unicode, lpctype_t *type, struct_t *opts) + +/* Convert to and put the result into . + * If is not NULL, it points to the same string. + * Return true on success. + */ + +{ + lpctype_t *object_types; + + if (lpctype_contains(lpctype_string, type)) + { + if (str) + put_ref_string(dest, str); + else + put_c_n_string(dest, text, len); + return true; + } + + if (lpctype_contains(lpctype_symbol, type)) + { + if (str) + put_ref_string(dest, str); + else + put_c_n_string(dest, text, len); + dest->type = T_SYMBOL; + dest->x.quotes = 1; + return true; + } + + if (lpctype_contains(lpctype_int, type)) + { + const char* p = text, *end; + bool negative = false; + unsigned long num; + bool overflow; + + if (len && (*p == '-' || *p == '+')) + { + negative = (*p == '-'); + p++; + } + + end = lex_parse_number(p, text + len, &num, &overflow); + if (end == text + len) + { + if (negative) + { + if (overflow || num > ((unsigned long)PINT_MAX)+1UL) + errorf("Number exceeds numeric limits.\n"); + + if (num == ((unsigned long)PINT_MAX)+1UL) + put_number(dest, PINT_MIN); + else + put_number(dest, -(p_int)num); + } + else + { + if (overflow || num > PINT_MAX) + errorf("Number exceeds numeric limits.\n"); + put_number(dest, (p_int)num); + } + return true; + } + } + if (lpctype_contains(lpctype_float, type)) + { + if (len < 32) + { + double d; + char *end; + char buf[32]; + + memcpy(buf, text, len); + buf[len] = 0; + + d = strtod(buf, &end); + if (end == buf+len) + { + put_float(dest, d); + return true; + } + } + else + { + double d; + char *buf; + char *end; + + memsafe(buf = xalloc(len), len, "string conversion"); + memcpy(buf, text, len); + buf[len] = 0; + + d = strtod(buf, &end); + xfree(buf); + + if (end == buf+len) + { + put_float(dest, d); + return true; + } + } + } + + if (lpctype_contains(lpctype_lpctype, type)) + { + const char* cur = text; + lpctype_t *result = parse_lpctype(&cur, text + len); + + if (result && cur == text + len) + { + put_lpctype(dest, result); + return true; + } + } + + object_types = get_common_type(lpctype_any_object, type); + if (object_types) + { + string_t *name = NULL; + object_t *obj; + + push_lpctype(inter_sp, object_types); + if (!str) + memsafe(name = new_n_unicode_mstring(text, len), len, "object name"); + + obj = find_object(str ? str : name); + if (name) + free_mstring(name); + + if (obj && is_compatible_object(obj, object_types)) + { + free_lpctype(object_types); + inter_sp--; + + put_ref_object(dest, obj, "to_type"); + return true; + } + free_lpctype(object_types); + inter_sp--; + } + + if (lpctype_contains(lpctype_bytes, type) && + opts != NULL && + opts->member[STRUCT_TO_TYPE_OPTIONS_TARGET_ENCODING].type == T_STRING) + { + string_t *result = utf8_string_to_bytes(text, len, + get_txt(opts->member[STRUCT_TO_TYPE_OPTIONS_TARGET_ENCODING].u.str), + "to_type", 3); + put_bytes(dest, result); + return true; + } + + if (lpctype_contains(lpctype_int_array, type)) + { + put_array(dest, convert_string_to_array(text, len, unicode, "to_type")); + return true; + } + + return false; +} + +/*-------------------------------------------------------------------------*/ +static bool +convert_bytes (svalue_t *dest, string_t *str, const char* text, size_t len, lpctype_t *type, struct_t *opts) + +/* Convert to and put the result into . + * If is not NULL, it points to the same string. + * Return true on success. + */ + +{ + if (lpctype_contains(lpctype_bytes, type)) + { + if (str) + put_ref_bytes(dest, str); + else + { + string_t *result; + memsafe(result = new_n_mstring(text, len, STRING_BYTES), len, "bytes"); + put_bytes(dest, result); + } + return true; + } + + if (lpctype_contains(lpctype_string, type) && + opts != NULL && + opts->member[STRUCT_TO_TYPE_OPTIONS_SOURCE_ENCODING].type == T_STRING) + { + string_t *result = bytes_to_utf8_string(text, len, + opts->member[STRUCT_TO_TYPE_OPTIONS_SOURCE_ENCODING].u.str, + "to_type", 3); + put_string(dest, result); + return true; + } + + if (lpctype_contains(lpctype_int_array, type)) + { + put_array(dest, convert_string_to_array(text, len, STRING_BYTES,"to_type")); + return true; + } + + return false; +} + +/*-------------------------------------------------------------------------*/ +static bool +convert_array (svalue_t *dest, vector_t *vec, svalue_t *items, size_t len, lpctype_t *type, struct_t *opts, bool keep_zero) + +/* Convert the array of to and put the result into . + * could be NULL, then the input is a sequence of zeroes. + * If is not NULL, it points to the same array. + * Return true on success. + */ + +{ + lpctype_t *struct_types, *array_types; + bool convert_to_string = false, convert_to_bytes = false; + + array_types = get_common_type(lpctype_any_array, type); + if (array_types != NULL) + { + /* We just select to first array and convert to it. */ + vector_t *result; + lpctype_t *single_array = array_types->t_class == TCLASS_UNION ? array_types->t_union.member : array_types; + assert(single_array->t_class == TCLASS_ARRAY); + + push_lpctype(inter_sp, array_types); + + memsafe(result = allocate_array(len), len, "converted array"); + put_array(dest, result); + + for (size_t i = 0; i < len; i++) + convert_to_type(result->item+i, items ? items+i : &const0, single_array->t_array.element, opts, keep_zero); + + free_lpctype(array_types); + inter_sp--; + return true; + } + + if ((convert_to_string=lpctype_contains(lpctype_string, type)) + || (convert_to_bytes=lpctype_contains(lpctype_bytes, type))) + { + bool only_integers = true; + bool is_ascii = true; + size_t size = 0; + + if (!items) + { + string_t *str; + + if (len == 0 && convert_to_string) + { + put_ref_string(dest, STR_EMPTY); + return true; + } + + memsafe(str = alloc_mstring(len), len, "converted string"); + memset(get_txt(str), 0, len); + + if (convert_to_string) + { + str->info.unicode = STRING_ASCII; + put_string(dest, str); + } + else + { + str->info.unicode = STRING_BYTES; + put_bytes(dest, str); + } + return true; + } + + for (size_t i = 0; i < len; i++) + if (items[i].type != T_NUMBER) + { + only_integers = false; + break; + } + else if (convert_to_string) + { + size_t chsize = utf8_size(items[i].u.number); + size += chsize; + if (!chsize) + convert_to_string = false; + else if (chsize > 1) + is_ascii = false; + } + + if (only_integers) + { + if (convert_to_string) + { + string_t *str; + char *pos; + + if (len == 0) + { + put_ref_string(dest, STR_EMPTY); + return true; + } + + memsafe(str = alloc_mstring(size), size, "converted string"); + pos = get_txt(str); + for (size_t i = 0; i < len; i++) + { + assert(items[i].type == T_NUMBER); + pos += unicode_to_utf8(items[i].u.number, pos); + } + + assert(pos == get_txt(str) + size); + str->info.unicode = is_ascii ? STRING_ASCII : STRING_UTF8; + + put_string(dest, str); + return true; + } + + if (convert_to_bytes) + { + string_t *str; + char *pos; + + memsafe(str = alloc_mstring(len), len, "converted byte sequence"); + pos = get_txt(str); + for (size_t i = 0; i < len; i++) + { + assert(items[i].type == T_NUMBER); + *pos = (char)items[i].u.number; + pos++; + } + + assert(pos == get_txt(str) + len); + str->info.unicode = STRING_BYTES; + + put_bytes(dest, str); + return true; + } + } + } + + if (lpctype_contains(lpctype_quoted_array, type)) + { + if (vec != NULL) + put_ref_array(dest, vec); + else + { + vector_t *result; + + memsafe(result = allocate_array(len), len, "quoted array"); + put_array(dest, result); + + for (size_t i = 0; i < len; i++) + assign_rvalue_no_free(result->item+i, items ? items+i : &const0); + } + + dest->type = T_QUOTED_ARRAY; + dest->x.quotes = 1; + return true; + } + + struct_types = get_common_type(lpctype_any_struct, type); + if (struct_types != NULL) + { + push_lpctype(inter_sp, struct_types); /* In case of errors. */ + + while (true) + { + lpctype_t *single_struct = struct_types->t_class == TCLASS_UNION ? struct_types->t_union.member : struct_types; + assert(single_struct->t_class == TCLASS_STRUCT); + + if (single_struct->t_struct.name == NULL) + { + struct_t *st; + + memsafe(st = struct_new_anonymous(len), len, "converted struct"); + for (size_t i = 0; i < len; i++) + assign_rvalue_no_free(st->member+i, items ? items+i : &const0); + put_struct(dest, st); + + free_lpctype(struct_types); + inter_sp--; + + return true; + } + else if (single_struct->t_struct.name->current == NULL) + { + errorf("Struct definition for '%s' (/%s) is not available.\n" + , get_txt(single_struct->t_struct.name->name) + , get_txt(single_struct->t_struct.name->prog_name)); + return false; /* NOTREACHED */ + } + else if(struct_t_size(single_struct->t_struct.name->current) >= len) + { + struct_type_t *stype = single_struct->t_struct.name->current; + struct_t *st; + + memsafe(st = struct_new(stype), struct_t_size(stype), "converted struct"); + + put_struct(dest, st); + + for (int i = 0; i < len; i++) + convert_to_type(st->member+i, items ? items+i : &const0, stype->member[i].type, opts, keep_zero); + + free_lpctype(struct_types); + inter_sp--; + + return true; + } + + if (single_struct->t_class == TCLASS_UNION) + single_struct = single_struct->t_union.head; + else + break; + } + free_lpctype(struct_types); + inter_sp--; + } + + if (lpctype_contains(lpctype_mapping, type)) + { + mapping_t *m; + + /* Convert the array to a set (mapping of width 0). */ + if (max_mapping_size && len > max_mapping_size) + errorf("Illegal mapping size: %zu elements\n", len); + if (max_mapping_keys && len > max_mapping_keys) + errorf("Illegal mapping size: %zu entries\n", len); + + memsafe(m = allocate_mapping(len, 0), len, "converted mapping"); + if (!items) + get_map_lvalue_unchecked(m, &const0); + else + for (size_t i = 0; i < len; i++) + get_map_lvalue_unchecked(m, items+i); + + put_mapping(dest, m); + return true; + } + + return false; +} + +/*-------------------------------------------------------------------------*/ +struct convert_mapping_entry_s +{ + lpctype_t *type; /* Target type (not refcounted) */ + struct_t *opts; /* Conversion options. */ + svalue_t *dest; /* Target item (to be increased after each call) */ + size_t width; /* Number of data elements in the mapping. */ + bool keep_zero; /* keep_zero conversion option. */ + bool success; /* To be set to false upon an error. */ +}; + +static void +convert_mapping_entry (svalue_t *key, svalue_t *data, void *extra) + +/* Converts a mapping entry according to . + * is free to be used as a cache. + */ + +{ + struct convert_mapping_entry_s* info = (struct convert_mapping_entry_s*)extra; + vector_t *vec; + if (!info->success) + return; + + if (info->width == 0) + { + info->success = convert_array(info->dest, NULL, key, 1, info->type, info->opts, info->keep_zero); + info->dest++; + return; + } + + /* We need to put key and data into a single array. */ + if (inter_sp->type == T_POINTER) + vec = inter_sp->u.vec; + else + { + assert(inter_sp->type == T_NUMBER); + + memsafe(vec = allocate_array(info->width + 1), info->width + 1, "converted array"); + put_array(inter_sp, vec); + } + + assign_svalue_no_free(vec->item, key); + for (size_t i = 0; i < info->width; i++) + assign_svalue_no_free(vec->item + 1 +i, data + i); + + info->success = convert_array(info->dest, vec, vec->item, info->width + 1, info->type, info->opts, info->keep_zero); + info->dest++; + + if (vec->ref > 1) + { + /* The array is used in the result. Cannot use it for subsequent calls. */ + free_array(vec); + put_number(inter_sp, 0); + } +} /* convert_mapping_entry() */ + +/*-------------------------------------------------------------------------*/ +static void +convert_to_type (svalue_t *dest, svalue_t *src, lpctype_t *type, struct_t *opts, bool keep_zero) + +/* Convert to and put the result into . + */ + +{ + svalue_t *rvalue = get_rvalue(src, NULL); + + if (!rvalue) + rvalue = src; + + switch (rvalue->type) + { + case T_LVALUE: + switch (rvalue->x.lvalue_type) + { + case LVALUE_PROTECTED_RANGE: + { + struct protected_range_lvalue *r = rvalue->u.protected_range_lvalue; + if (r->vec.type == T_POINTER) + { + if (convert_array(dest, NULL, r->vec.u.vec->item + r->index1, r->index2 - r->index1, type, opts, keep_zero)) + return; + } + else if (r->vec.type == T_BYTES) + { + if (convert_bytes(dest, NULL, get_txt(r->vec.u.str) + r->index1, r->index2 - r->index1, type, opts)) + return; + } + else + { + if (convert_string(dest, NULL, get_txt(r->vec.u.str) + r->index1, r->index2 - r->index1, r->vec.u.str->info.unicode, type, opts)) + return; + } + break; + } + + case LVALUE_PROTECTED_MAP_RANGE: + { + struct protected_map_range_lvalue *r = rvalue->u.protected_map_range_lvalue; + svalue_t *items = get_map_value(r->map, &(r->key)); + + if (items == &const0) + { + if (convert_array(dest, NULL, NULL, r->index2 - r->index1, type, opts, keep_zero)) + return; + } + else + { + if (convert_array(dest, NULL, items + r->index1, r->index2 - r->index1, type, opts, keep_zero)) + return; + } + + break; + } + + default: + fatal("Illegal lvalue type %d\n", rvalue->x.lvalue_type); + break; /* NOTREACHED */ + } + break; + + case T_NUMBER: + if (rvalue->u.number == 0 && keep_zero) + put_number(dest, 0); + else if (lpctype_contains(lpctype_int, type)) + put_number(dest, rvalue->u.number); + else if (lpctype_contains(lpctype_float, type)) + put_float(dest, (double)rvalue->u.number); + else if (lpctype_contains(lpctype_string, type)) + { + string_t *res; + char buf[32]; + + snprintf(buf, sizeof(buf), "%"PRIdPINT, rvalue->u.number); + memsafe(res = new_mstring(buf, STRING_ASCII), strlen(buf), "converted number"); + put_string(dest, res); + } + else if (rvalue->u.number == 0) + put_number(dest, 0); + else + break; + return; + + case T_STRING: + if (convert_string(dest, rvalue->u.str, get_txt(rvalue->u.str), mstrsize(rvalue->u.str), rvalue->u.str->info.unicode, type, opts)) + return; + break; + + case T_POINTER: + if (check_rtt_compatibility(type, rvalue)) + { + put_ref_array(dest, rvalue->u.vec); + return; + } + + if (convert_array(dest, rvalue->u.vec, rvalue->u.vec->item, VEC_SIZE(rvalue->u.vec), type, opts, keep_zero)) + return; + break; + + case T_OBJECT: + if (is_compatible_object(rvalue->u.ob, type)) + put_ref_object(dest, rvalue->u.ob, "to_type"); + else if (lpctype_contains(lpctype_string, type)) + put_string(dest, add_slash(rvalue->u.ob->name)); + else + break; + return; + + case T_MAPPING: + { + lpctype_t *struct_types = NULL, *array_types; + + if (lpctype_contains(lpctype_mapping, type)) + { + put_ref_mapping(dest, rvalue->u.map); + return; + } + + /* We only convert when we have values for the struct member. */ + if (rvalue->u.map->num_values > 0) + struct_types = get_common_type(lpctype_any_struct, type); + if (struct_types != NULL) + { + push_lpctype(inter_sp, struct_types); + + /* We'll go through all structs until we find one, + * that has all the members in the mapping. + */ + while (true) + { + lpctype_t *single_struct = struct_types->t_class == TCLASS_UNION ? struct_types->t_union.member : struct_types; + assert(single_struct->t_class == TCLASS_STRUCT); + + if (single_struct->t_struct.name == NULL) + { + struct_t *st = convert_mapping_to_anonymous_struct(rvalue->u.map, false); + if (!st) + break; + + free_lpctype(struct_types); + inter_sp--; + + put_struct(dest, st); + return; + } + else if (single_struct->t_struct.name->current == NULL) + { + errorf("Struct definition for '%s' (/%s) is not available.\n" + , get_txt(single_struct->t_struct.name->name) + , get_txt(single_struct->t_struct.name->prog_name)); + return; /* NOTREACHED */ + } + else if(struct_t_size(single_struct->t_struct.name->current) >= rvalue->u.map->num_entries) + { + svalue_t *values[1024]; + int num_found_keys = 0; + bool use_values = false; + struct_type_t *stype = single_struct->t_struct.name->current; + struct_t *st; + + /* For non-large structs we check beforehand whether it matches. */ + if (struct_t_size(stype) <= sizeof(values)/sizeof(values[0])) + { + + for (int i = 0; i < struct_t_size(stype); i++) + { + svalue_t key = svalue_string(stype->member[i].name); + svalue_t *value = get_map_value(rvalue->u.map, &key); + if (value == &const0) + values[i] = NULL; + else + { + values[i] = value; + num_found_keys++; + } + } + + if (rvalue->u.map->num_entries > num_found_keys) + { + if (single_struct->t_class == TCLASS_UNION) + { + single_struct = single_struct->t_union.head; + continue; + } + else + break; + } + + use_values = true; + } + + st = struct_new(stype); + put_struct(dest, st); + + for (int i = 0; i < struct_t_size(stype); i++) + { + svalue_t *src_value; + if (use_values) + { + src_value = values[i]; + if (src_value == NULL) + continue; + } + else + { + svalue_t key = svalue_string(stype->member[i].name); + src_value = get_map_value(rvalue->u.map, &key); + if (src_value == &const0) + continue; + num_found_keys++; + } + + if (rvalue->u.map->num_values > 1) + { + if (!convert_array(st->member+i, NULL, src_value, rvalue->u.map->num_values, stype->member[i].type, opts, keep_zero)) + { + put_number(dest, 0); + free_struct(st); + break; + } + } + else + convert_to_type(st->member+i, src_value, stype->member[i].type, opts, keep_zero); + } + + if (rvalue->u.map->num_entries <= num_found_keys) + { + free_lpctype(struct_types); + inter_sp--; + + return; + } + + put_number(dest, 0); + free_struct(st); + } + + if (single_struct->t_class == TCLASS_UNION) + single_struct = single_struct->t_union.head; + else + break; + } + free_lpctype(struct_types); + inter_sp--; + } + + array_types = get_common_type(lpctype_any_array, type); + if (array_types != NULL) + { + mapping_t *map = rvalue->u.map; + vector_t *vec; + lpctype_t *single_array = array_types->t_class == TCLASS_UNION ? array_types->t_union.member : array_types; + struct convert_mapping_entry_s info; + + assert(single_array->t_class == TCLASS_ARRAY); + push_lpctype(inter_sp, array_types); + + memsafe(vec = allocate_array(MAP_SIZE(map)), MAP_SIZE(map), "converted array"); + put_array(dest, vec); + + info.type = single_array->t_array.element; + info.opts = opts; + info.dest = vec->item; + info.width = map->num_values; + info.keep_zero = keep_zero; + info.success = true; + + push_number(inter_sp, 0); + walk_mapping(map, convert_mapping_entry, &info); + inter_sp = pop_n_elems(2, inter_sp); + + if (info.success) + { + assert(info.dest == vec->item + MAP_SIZE(map)); + return; + } + } + + break; + } + + case T_FLOAT: + if (lpctype_contains(lpctype_float, type)) + *dest = *rvalue; + else if (lpctype_contains(lpctype_int, type)) + { + double d = READ_DOUBLE(rvalue); + p_int result = (p_int)d; + /* The check against PINT_MAX is inaccurate, as double + * has a lower precision, therefore we check also that + * no overflow into the sign happens. + */ + if (d < (double)PINT_MIN || d > (double)PINT_MAX || (d < 0) != (result < 0)) + errorf("Number exceeds numeric limits.\n"); + + put_number(dest, result); + } + else if (lpctype_contains(lpctype_string, type)) + { + string_t *res; + char buf[32]; + + snprintf(buf, sizeof(buf), "%g", READ_DOUBLE(rvalue)); + memsafe(res = new_mstring(buf, STRING_ASCII), strlen(buf), "converted number"); + put_string(dest, res); + } + else + break; + return; + + case T_CLOSURE: + { + lpctype_t *object_types, *lwobject_types; + object_t *ob = NULL; + lwobject_t *lwob = NULL; + + if (is_undef_closure(rvalue)) + { + put_number(dest, 0); + return; + } + + if (lpctype_contains(lpctype_closure, type)) + { + assign_svalue_no_free(dest, rvalue); + return; + } + + object_types = get_common_type(lpctype_any_object, type); + lwobject_types = get_common_type(lpctype_any_lwobject, type); + if (object_types != NULL || lwobject_types != NULL) + { + if (CLOSURE_MALLOCED(rvalue->x.closure_type)) + { + if (rvalue->u.closure->ob.type == T_OBJECT) + ob = rvalue->u.closure->ob.u.ob; + else if (rvalue->u.closure->ob.type == T_LWOBJECT) + lwob = rvalue->u.closure->ob.u.lwob; + } + else + { + if (rvalue->x.closure_type < CLOSURE_LWO) + lwob = rvalue->u.lwob; + else + ob = rvalue->u.ob; + } + + if (ob && object_types && is_compatible_object(ob, object_types)) + { + free_lpctype(object_types); + free_lpctype(lwobject_types); + put_ref_object(dest, ob, "to_type"); + return; + } + else if (lwob && lwobject_types && is_compatible_lwobject(lwob, lwobject_types)) + { + free_lpctype(object_types); + free_lpctype(lwobject_types); + put_ref_lwobject(dest, lwob); + return; + } + + free_lpctype(object_types); + free_lpctype(lwobject_types); + } + + if (lpctype_contains(lpctype_int, type)) + { + if (rvalue->x.closure_type == CLOSURE_IDENTIFIER) + { + put_number(dest, rvalue->u.identifier_closure->var_index); + return; + } + else if (rvalue->x.closure_type == CLOSURE_LFUN) + { + put_number(dest, rvalue->u.lfun_closure->fun_index); + return; + } + } + + if (lpctype_contains(lpctype_string, type)) + { + put_string(dest, closure_to_string(rvalue, false)); + return; + } + break; + } + + case T_SYMBOL: + if (lpctype_contains(lpctype_symbol, type)) + put_ref_symbol(dest, rvalue->u.str, 1); + else if (lpctype_contains(lpctype_string, type)) + put_ref_string(dest, rvalue->u.str); + else if (lpctype_contains(lpctype_int_array, type)) + put_array(dest, convert_string_to_array(get_txt(rvalue->u.str), mstrsize(rvalue->u.str), rvalue->u.str->info.unicode, "to_type")); + else + break; + return; + + case T_QUOTED_ARRAY: + { + svalue_t sv; + + if (lpctype_contains(lpctype_quoted_array, type)) + { + assign_svalue_no_free(dest, rvalue); + return; + } + + sv = svalue_array(rvalue->u.vec); + if (check_rtt_compatibility(type, &sv)) + { + put_ref_array(dest, rvalue->u.vec); + return; + } + + if (convert_array(dest, rvalue->u.vec, rvalue->u.vec->item, VEC_SIZE(rvalue->u.vec), type, opts, keep_zero)) + return; + break; + } + + case T_STRUCT: + { + struct_t *st = rvalue->u.strct; + lpctype_t *value_type = get_struct_type(st->type); + lpctype_t *derived_struct_types; + lpctype_t *array_types; + + if (lpctype_contains(value_type, type)) + { + free_lpctype(value_type); + assign_svalue_no_free(dest, rvalue); + return; + } + + /* Search for any derived structs. */ + derived_struct_types = get_common_type(value_type, type); + if (derived_struct_types != NULL) + { + lpctype_t *single_derived_struct = derived_struct_types->t_class == TCLASS_UNION ? derived_struct_types->t_union.member : derived_struct_types; + struct_type_t *stype; + struct_t *result; + + assert(single_derived_struct->t_class == TCLASS_STRUCT); + assert(single_derived_struct->t_struct.name != NULL); + + stype = single_derived_struct->t_struct.name->current; + free_lpctype(value_type); + + if (stype == NULL) + { + push_lpctype(inter_sp, derived_struct_types); + errorf("Struct definition for '%s' (/%s) is not available.\n" + , get_txt(single_derived_struct->t_struct.name->name) + , get_txt(single_derived_struct->t_struct.name->prog_name)); + return; /* NOTREACHED */ + } + + assert(stype->num_members >= st->type->num_members); + result = struct_new(stype); + free_lpctype(derived_struct_types); + + memsafe(result, struct_t_size(stype), "converted struct"); + + for (int i = 0; i < st->type->num_members; i++) + assign_rvalue_no_free(result->member + i, st->member + i); + put_struct(dest, result); + + return; + } + free_lpctype(value_type); + + if (lpctype_contains(lpctype_mapping, type)) + { + mapping_t *m; + int len = struct_size(st); + + if (max_mapping_size && 2*len > max_mapping_size) + errorf("Illegal mapping size: %d elements\n", 2*len); + if (max_mapping_keys && len > max_mapping_keys) + errorf("Illegal mapping size: %d entries\n", len); + + memsafe(m = allocate_mapping(len, 1), len, "converted mapping"); + + for (int i = 0; i < len; i++) + { + svalue_t key = svalue_string(st->type->member[i].name); + svalue_t *entry = get_map_lvalue_unchecked(m, &key); + free_svalue(entry); + assign_rvalue_no_free(entry, &st->member[i]); + } + + put_mapping(dest, m); + return; + } + + array_types = get_common_type(lpctype_any_array, type); + if (array_types != NULL) + { + /* We just choose one of the array types. */ + int len = struct_size(st); + vector_t *vec; + lpctype_t *single_array = array_types->t_class == TCLASS_UNION ? array_types->t_union.member : array_types; + assert(single_array->t_class == TCLASS_ARRAY); + + push_lpctype(inter_sp, array_types); + + memsafe(vec = allocate_array(len), len, "converted array"); + put_array(dest, vec); + + for (int i = 0; i < len; i++) + convert_to_type(vec->item + i, st->member + i, single_array->t_array.element, opts, keep_zero); + + free_lpctype(array_types); + inter_sp--; + return; + } + + if (lpctype_contains(lpctype_string, type)) + { + string_t *str; + memsafe(str = extend_string(""), mstrsize(struct_name(st)), "converted lwobject name"); + put_string(dest, str); + return; + } + + break; + } + + case T_BYTES: + if (convert_bytes(dest, rvalue->u.str, get_txt(rvalue->u.str), mstrsize(rvalue->u.str), type, opts)) + return; + break; + + case T_LWOBJECT: + if (is_compatible_lwobject(rvalue->u.lwob, type)) + put_ref_lwobject(dest, rvalue->u.lwob); + else if (lpctype_contains(lpctype_string, type)) + { + string_t *str; + memsafe(str = extend_string("u.lwob->prog->name, ">"), mstrsize(rvalue->u.lwob->prog->name), "converted struct name"); + put_string(dest, str); + } + else + break; + return; + + case T_COROUTINE: + if (lpctype_contains(lpctype_coroutine, type)) + put_ref_coroutine(dest, rvalue->u.coroutine); + else if (lpctype_contains(lpctype_string, type)) + put_string(dest, coroutine_to_string(rvalue->u.coroutine)); + else + break; + return; + +#ifdef USE_PYTHON + case T_PYTHON: + if (convert_python_ob(dest, rvalue, type, opts)) + return; + break; +#endif + + case T_LPCTYPE: + { + lpctype_t *lpctype_array; + + if (lpctype_contains(lpctype_lpctype, type)) + { + put_ref_lpctype(dest, rvalue->u.lpctype); + return; + } + + lpctype_array = get_array_type(lpctype_lpctype); + if (lpctype_contains(lpctype_array, type)) + { + free_lpctype(lpctype_array); + + put_array(dest, convert_lpctype_to_array(rvalue->u.lpctype)); + return; + } + free_lpctype(lpctype_array); + + if (lpctype_contains(lpctype_mapping, type)) + { + mapping_t *m; + size_t len = 1; + + for (lpctype_t *cur = rvalue->u.lpctype; cur->t_class == TCLASS_UNION; cur = cur->t_union.head) + len++; + + memsafe(m = allocate_mapping(len, 0), len, "converted mapping"); + for (lpctype_t *cur = rvalue->u.lpctype;;) + { + if (cur->t_class == TCLASS_UNION) + { + svalue_t key = svalue_lpctype(cur->t_union.member); + get_map_lvalue_unchecked(m, &key); + cur = cur->t_union.head; + } + else + { + svalue_t key = svalue_lpctype(cur); + get_map_lvalue_unchecked(m, &key); + break; + } + } + + put_mapping(dest, m); + return; + } + + if (lpctype_contains(lpctype_string, type)) + { + put_string(dest, new_unicode_mstring(get_lpctype_name(rvalue->u.lpctype))); + return; + } + + break; + } + + default: + fatal("(to_type) Illegal source type %d\n", rvalue->type); + } + + errorf("Cannot convert %s to %s.\n", sv_typename(rvalue), get_lpctype_name(type)); +} /* convert_to_type() */ + +/*-------------------------------------------------------------------------*/ +svalue_t * +v_to_type (svalue_t *sp, int num_arg) + +/* EFUN to_type() + * + * mixed to_type(mixed value, lpctype type) + * mixed to_type(mixed value, lpctype type, struct to_type_options options) + * + * Converts into recursively. + */ + +{ + svalue_t *value = sp - num_arg + 1; + struct_t *opts = NULL; + bool keep_zero = false; + + if (num_arg > 2) + { + opts = value[2].u.strct; + test_efun_arg_struct_type("to_type", 3, opts, STRUCT_TO_TYPE_OPTIONS); + keep_zero = opts->member[STRUCT_TO_TYPE_OPTIONS_KEEP_ZERO].u.number; + } + + push_number(sp, 0); + inter_sp = sp; + + convert_to_type(sp, value, value[1].u.lpctype, opts, keep_zero); + + assert(inter_sp == sp); + + pop_n_elems(num_arg, sp - 1); + transfer_svalue_no_free(value, sp); + return value; +} /* v_to_type() */ + /*-------------------------------------------------------------------------*/ svalue_t * f_copy (svalue_t *sp) diff --git a/src/efuns.h b/src/efuns.h index de2b3360d..24d128a04 100644 --- a/src/efuns.h +++ b/src/efuns.h @@ -55,6 +55,7 @@ extern svalue_t *f_to_float (svalue_t *sp); extern svalue_t *f_to_string (svalue_t *sp); extern svalue_t *f_to_object (svalue_t *sp); extern svalue_t *f_to_lpctype(svalue_t *sp); +extern svalue_t *v_to_type (svalue_t *sp, int num_arg); extern svalue_t *f_copy (svalue_t *sp); extern svalue_t *f_deep_copy (svalue_t *sp); extern svalue_t *v_filter (svalue_t *sp, int num_arg); diff --git a/src/func_spec b/src/func_spec index 1b61d69b3..ba1bb28b0 100644 --- a/src/func_spec +++ b/src/func_spec @@ -377,6 +377,7 @@ mixed *to_array(string|bytes|mixed*|symbol|quoted_array|struct|lpctype); mixed to_struct(mapping|mixed*|struct, void|struct); object to_object(null|object|string|closure); lpctype to_lpctype(string); +mixed to_type(mixed,lpctype,void|struct to_type_options); bytes to_bytes(string|bytes|int*, void|string); string to_text(string|bytes|int*, void|string); diff --git a/src/interpret.c b/src/interpret.c index b30674b81..af9336875 100644 --- a/src/interpret.c +++ b/src/interpret.c @@ -8157,7 +8157,7 @@ check_rtt_compatibility_inl(lpctype_t *formaltype, svalue_t *svp, lpctype_t **sv { *svptype = get_array_type(svpelement ? svpelement : lpctype_mixed); free_lpctype(svpelement); - } + } return MY_FALSE; // No valid type found. } diff --git a/src/lex.c b/src/lex.c index 575ad821d..7724b02e7 100644 --- a/src/lex.c +++ b/src/lex.c @@ -4645,8 +4645,8 @@ parse_numeric_escape (char * cp, p_int * p_char) } /* parse_numeric_escape() */ /*-------------------------------------------------------------------------*/ -static INLINE char * -parse_number (char * cp, unsigned long * p_num, Bool * p_overflow) +static INLINE const char * +parse_number (const char* cp, const char* end, unsigned long* p_num, bool* p_overflow) /* Parse a positive integer number in one of the following formats: * @@ -4656,6 +4656,7 @@ parse_number (char * cp, unsigned long * p_num, Bool * p_overflow) * 0b * * with pointing to the first character. + * If is not NULL, it denotes the end of the string. * * The parsed number is stored in *, the function returns the pointer * to the first character after the number. If the parsed number exceeded @@ -4670,9 +4671,12 @@ parse_number (char * cp, unsigned long * p_num, Bool * p_overflow) unsigned long base = 10; unsigned long max_shiftable = ULONG_MAX / base; - *p_overflow = MY_FALSE; - c = *cp++; + *p_overflow = false; + *p_num = 0; + if (cp == end) + return cp; + c = *cp++; if ('0' == c) { /* '0' introduces decimal, octal, binary and sedecimal numbers, or it @@ -4682,6 +4686,8 @@ parse_number (char * cp, unsigned long * p_num, Bool * p_overflow) * two possible prefixes. */ + if (cp == end) + return cp; c = *cp++; switch (c) @@ -4698,7 +4704,7 @@ parse_number (char * cp, unsigned long * p_num, Bool * p_overflow) l = 0; max_shiftable = ULONG_MAX / 2; --cp; - while('0' == (c = *++cp) || '1' == c) + while(cp != end && ('0' == (c = *++cp) || '1' == c)) { *p_overflow = *p_overflow || (l > max_shiftable); l <<= 1; @@ -4736,7 +4742,7 @@ parse_number (char * cp, unsigned long * p_num, Bool * p_overflow) max_shiftable = ULONG_MAX / 16; l = 0; --cp; - while(leXdigit(c = *++cp)) + while(cp != end && leXdigit(c = *++cp)) { *p_overflow = *p_overflow || (l > max_shiftable); if (c > '9') @@ -4752,22 +4758,23 @@ parse_number (char * cp, unsigned long * p_num, Bool * p_overflow) max_shiftable = ULONG_MAX / base; l = c - '0'; - while (lexdigit(c = *cp++) && c < (char)('0'+base)) + while (cp != end && (lexdigit(c = *cp) && c < (char)('0'+base))) { *p_overflow = *p_overflow || (l > max_shiftable); c -= '0'; l = l * base + c; *p_overflow = *p_overflow || (l < (unsigned long)c); + cp++; } *p_num = *p_overflow ? LONG_MAX : l; - return cp-1; + return cp; } /* parse_number() */ /*-------------------------------------------------------------------------*/ -char * -lex_parse_number (char * cp, unsigned long * p_num, Bool * p_overflow) +const char * +lex_parse_number (const char* cp, const char* end, unsigned long* p_num, bool* p_overflow) /* Parse a positive integer number in one of the following formats: * @@ -4786,15 +4793,14 @@ lex_parse_number (char * cp, unsigned long * p_num, Bool * p_overflow) */ { - char c = *cp; - *p_overflow = MY_FALSE; + if (cp == end) + return cp; - if (isdigit(c)) - { - cp = parse_number(cp, p_num, p_overflow); - } - return cp; + if (!isdigit(*cp)) + return cp; + + return parse_number(cp, end, p_num, p_overflow); } /* lex_parse_number() */ /*-------------------------------------------------------------------------*/ @@ -6433,7 +6439,7 @@ yylex1 (void) } /* Nope, normal number */ - yyp = parse_number(numstart, &l, &overflow); + yyp = (char*)parse_number(numstart, NULL, &l, &overflow); if (overflow || (l > (unsigned long)LONG_MAX+1)) { /* Don't warn on __INT_MAX__+1 because there diff --git a/src/lex.h b/src/lex.h index 25bd8f307..bfb779503 100644 --- a/src/lex.h +++ b/src/lex.h @@ -328,7 +328,7 @@ extern void set_inc_list(vector_t *v); extern void remove_unknown_identifier(void); extern char *lex_error_context(void); extern svalue_t *f_expand_define(svalue_t *sp); -extern char * lex_parse_number (char * cp, unsigned long * p_num, Bool * p_overflow); +extern const char* lex_parse_number (const char* cp, const char* end, unsigned long* p_num, bool* p_overflow); extern void * get_include_handle (void); #ifdef GC_SUPPORT diff --git a/src/mapping.c b/src/mapping.c index dbc4904d4..5c0407e5e 100644 --- a/src/mapping.c +++ b/src/mapping.c @@ -4460,8 +4460,8 @@ v_mkmapping (svalue_t *sp, int num_arg) st = sp->u.strct; length = struct_size(st); - if (max_mapping_size && length > (p_int)max_mapping_size) - errorf("Illegal mapping size: %ld elements\n", length); + if (max_mapping_size && 2*length > (p_int)max_mapping_size) + errorf("Illegal mapping size: %ld elements (%ld x 2)\n", 2*length, length); if (max_mapping_keys && length > (p_int)max_mapping_keys) errorf("Illegal mapping size: %ld entries\n", length); diff --git a/src/pkg-python.c b/src/pkg-python.c index 3db1646c6..f442495fa 100644 --- a/src/pkg-python.c +++ b/src/pkg-python.c @@ -8405,6 +8405,22 @@ static ldmud_lpctype_t ldmud_struct_type = }, ldmud_struct_get_lpctype /* get_lpctype */ }; +/*-------------------------------------------------------------------------*/ +static PyObject* +ldmud_struct_create (struct_t* st) + +/* Creates a new Python struct from an LPC struct. + */ + +{ + PyObject* val = ldmud_struct_new(&ldmud_struct_type.type_base, NULL, NULL); + if (val != NULL) + ((ldmud_struct_t*)val)->lpc_struct = ref_struct(st); + + return val; +} /* ldmud_struct_create() */ + +/*-------------------------------------------------------------------------*/ static PyObject* ldmud_concrete_struct_new (PyTypeObject *type, PyObject *args, PyObject *kwds) @@ -12897,13 +12913,7 @@ svalue_to_python (svalue_t *svp) return ldmud_quoted_array_create(svp); case T_STRUCT: - { - PyObject* val = ldmud_struct_new(&ldmud_struct_type.type_base, NULL, NULL); - if (val != NULL) - ((ldmud_struct_t*)val)->lpc_struct = ref_struct(svp->u.strct); - - return val; - } + return ldmud_struct_create(svp->u.strct); case T_LVALUE: return ldmud_lvalue_create(svp); @@ -14934,6 +14944,162 @@ restore_python_ob (svalue_t *dest, string_t *name, svalue_t *value) return true; } /* restore_python_ob() */ +/*-------------------------------------------------------------------------*/ +bool +convert_python_ob (svalue_t *dest, svalue_t *ob, lpctype_t *type, struct_t *opts) + +/* Convert a Python object to of possible. Return true on success. + */ + +{ + PyObject *fun, *pyob; + bool started = python_start_thread(); + + assert(ob->type == T_PYTHON); + assert(python_type_table[ob->x.python_type] != NULL); + + pyob = (PyObject*)ob->u.generic; + fun = PyObject_GetAttrString(pyob, "__convert__"); + if (fun != NULL) + { + PyObject *args = PyTuple_New(2); + PyObject *result, *pytype, *pyopts; + bool was_external = python_is_external; + + if (args == NULL) + { + Py_DECREF(fun); + raise_python_error("to_type", started); + } + + pytype = lpctype_to_pythontype(type); + if (pytype == NULL) + { + Py_DECREF(fun); + Py_DECREF(args); + raise_python_error("to_type", started); + } + PyTuple_SET_ITEM(args, 0, pytype); + + if (opts) + pyopts = ldmud_struct_create(opts); + else + { + Py_INCREF(Py_None); + pyopts = Py_None; + } + if (pyopts == NULL) + { + Py_DECREF(fun); + Py_DECREF(args); + raise_python_error("to_type", started); + } + PyTuple_SET_ITEM(args, 1, pyopts); + + python_is_external = false; + python_save_context(); + + result = PyObject_CallObject(fun, args); + + python_clear_context(); + python_is_external = was_external; + Py_DECREF(fun); + Py_DECREF(args); + + if (result == NULL) + raise_python_error("to_type", started); + else if (result == Py_NotImplemented) + { + Py_DECREF(result); + python_finish_thread(started); + return false; + } + else + { + const char* err = python_to_svalue(dest, result); + Py_DECREF(result); + python_finish_thread(started); + + if (err != NULL) + errorf("Bad return value from __convert__: %s\n", err); + + return true; + } + } + PyErr_Clear(); + + /* Convert doesn't exist. Let's try native magic functions. */ + if (lpctype_contains(lpctype_int, type)) + { + PyObject* result = PyNumber_Long(pyob); + if (result != NULL) + { + const char* err = python_to_svalue(dest, result); + Py_DECREF(result); + python_finish_thread(started); + + if (err != NULL) + errorf("Bad return value from __int__: %s\n", err); + + return true; + } + PyErr_Clear(); + } + + if (lpctype_contains(lpctype_float, type)) + { + PyObject* result = PyNumber_Float(pyob); + if (result != NULL) + { + const char* err = python_to_svalue(dest, result); + Py_DECREF(result); + python_finish_thread(started); + + if (err != NULL) + errorf("Bad return value from __float__: %s\n", err); + + return true; + } + PyErr_Clear(); + } + + if (lpctype_contains(lpctype_string, type)) + { + PyObject* result = PyObject_Str(pyob); + if (result != NULL) + { + const char* err = python_to_svalue(dest, result); + Py_DECREF(result); + python_finish_thread(started); + + if (err != NULL) + errorf("Bad return value from __str__: %s\n", err); + + return true; + } + PyErr_Clear(); + } + + if (lpctype_contains(lpctype_bytes, type)) + { + PyObject* result = PyObject_Bytes(pyob); + if (result != NULL) + { + const char* err = python_to_svalue(dest, result); + Py_DECREF(result); + python_finish_thread(started); + + if (err != NULL) + errorf("Bad return value from __bytes__: %s\n", err); + + return true; + } + PyErr_Clear(); + } + + return false; +} /* convert_python_ob() */ + /*-------------------------------------------------------------------------*/ string_t* python_ob_to_string (svalue_t *pval) diff --git a/src/pkg-python.h b/src/pkg-python.h index c50179f8a..58738b916 100644 --- a/src/pkg-python.h +++ b/src/pkg-python.h @@ -144,6 +144,7 @@ extern void free_python_ob(svalue_t *pval); extern void copy_python_ob(svalue_t *dest, svalue_t *src); extern bool save_python_ob(svalue_t *dest, string_t **name, svalue_t *ob); extern bool restore_python_ob(svalue_t *dest, string_t *name, svalue_t *value); +extern bool convert_python_ob(svalue_t *dest, svalue_t *ob, lpctype_t *type, struct_t *opts); extern string_t* python_ob_to_string(svalue_t *pval); extern svalue_t* do_python_unary_operation(svalue_t *sp, enum python_operation op, const char* op_name); extern svalue_t* do_python_binary_operation(svalue_t *sp, enum python_operation op, enum python_operation rop, const char* op_name); diff --git a/src/strfuns.c b/src/strfuns.c index e8df9471e..d3675e03a 100644 --- a/src/strfuns.c +++ b/src/strfuns.c @@ -649,6 +649,36 @@ unescape_string (const char * text, size_t len, char * buf, size_t buflen) return dest - buf; } /* unescape_string() */ +/*--------------------------------------------------------------------*/ + +string_t * +extend_string (const char *prefix, string_t *txt, const char *suffix) + +/* Returns a string consisting of , , and . + * and are considered pure ASCII text. + * Returns NULL when out of memory. + */ + +{ + size_t prefix_len = strlen(prefix); + size_t suffix_len = strlen(suffix); + size_t len = prefix_len + mstrsize(txt) + suffix_len; + + string_t *result = alloc_mstring(len); + char *buf; + + if (!result) + return NULL; + + buf = get_txt(result); + memcpy(buf, prefix, prefix_len); + memcpy(buf + prefix_len, get_txt(txt), mstrsize(txt)); + memcpy(buf + prefix_len + mstrsize(txt), suffix, suffix_len); + + result->info.unicode = txt->info.unicode; + return result; +} /* extend_string() */ + /*====================================================================*/ /* ENCODING */ @@ -1522,6 +1552,338 @@ get_string_up_to_width (const char* str, size_t len, int width, bool* error) /*--------------------------------------------------------------------*/ +string_t * +utf8_string_to_bytes (const char* src, size_t len, const char* encoding, const char* efun_name, int efun_encoding_arg_pos) + +/* Convert a UTF-8 string into byte string with . + */ + +{ + iconv_t cd; + string_t *result; + + char* in_buf_ptr = (char*) src; + size_t in_buf_left = len; + + char* out_buf_ptr; + char* out_buf_start; + size_t out_buf_size; + size_t out_buf_left; + + cd = iconv_open(encoding, "UTF-8"); + if (!iconv_valid(cd)) + { + if (errno == EINVAL) + errorf("Bad arg %d to %s(): Unsupported encoding '%s'.\n", efun_encoding_arg_pos, efun_name, encoding); + else + errorf("%s(): %s\n", efun_name, strerror(errno)); + return NULL; /* NOTREACHED */ + } + + if (len == 0) + { + iconv_close(cd); + + /* We can't use STR_EMPTY, because that one is a unicode string. */ + memsafe(result = alloc_mstring(0), 0, "converted array"); + result->info.unicode = STRING_BYTES; + return result; + } + + /* For small texts, we reserve twice the space. */ + out_buf_left = out_buf_size = len > 32768 ? (len + 2048) : (2 * len); + xallocate(out_buf_start, out_buf_size, "conversion buffer"); + out_buf_ptr = out_buf_start; + + /* Convert the string, reallocating the output buffer where necessary */ + while (true) + { + size_t rc; + bool at_end = (in_buf_left == 0); + + /* At the end we need one final call. */ + if (at_end) + rc = iconv(cd, NULL, NULL, &out_buf_ptr, &out_buf_left); + else + rc = iconv(cd, &in_buf_ptr, &in_buf_left, &out_buf_ptr, &out_buf_left); + + if (rc == (size_t)-1) + { + size_t idx; + if (errno == E2BIG) + { + /* Reallocate output buffer */ + size_t new_size = out_buf_size + (len > 128 ? len : 128); + char* new_buf = rexalloc(out_buf_start, new_size); + + if (!new_buf) + { + iconv_close(cd); + + xfree(out_buf_start); + outofmem(new_size, "conversion buffer"); + return NULL; /* NOTREACHED */ + } + + out_buf_ptr = new_buf + (out_buf_ptr - out_buf_start); + out_buf_start = new_buf; + out_buf_left = out_buf_left + new_size - out_buf_size; + out_buf_size = new_size; + continue; + } + + /* Ignore EILSEQ at the end, they come from //IGNORE. */ + if (errno == EILSEQ && !in_buf_left) + continue; + + /* Other error: clean up */ + iconv_close(cd); + xfree(out_buf_start); + + idx = byte_to_char_index(src, len - in_buf_left, NULL); + if (errno == EILSEQ) + errorf("%s(): Invalid character sequence at index %zd.\n", efun_name, idx); + + if (errno == EINVAL) + errorf("%s(): Incomplete character sequence at index %zd.\n", efun_name, idx); + + errorf("%s(): %s\n", efun_name, strerror(errno)); + return NULL; /* NOTREACHED */ + } + + if (at_end) + break; + } + + iconv_close(cd); + + result = new_n_mstring(out_buf_start, out_buf_ptr - out_buf_start, STRING_BYTES); + result->info.unicode = STRING_BYTES; + xfree(out_buf_start); + if (!result) + { + outofmem(out_buf_ptr - out_buf_start, "converted string"); + return NULL; /* NOTREACHED */ + } + + return result; +} /* string_to_bytes() */ + +/*--------------------------------------------------------------------*/ + +string_t * +bytes_to_utf8_string (const char* src, size_t len, string_t* encoding, const char* efun_name, int efun_encoding_arg_pos) + +/* Convert the byte sequence to an UTF-8 string with . + */ + +{ + string_t* result; + + char* encoding_name; + size_t encoding_len; + + iconv_t cd; + bool ignore; + bool replace; + + char* in_buf_ptr = (char*)src; + size_t in_buf_left = len; + + char* out_buf_ptr; + char* out_buf_start; + size_t out_buf_size; + size_t out_buf_left; + + encoding_len = parse_input_encoding(encoding, &ignore, &replace); + encoding_name = get_txt(encoding); + + if (encoding_len < mstrsize(encoding)) + { + char save = encoding_name[encoding_len]; + + encoding_name[encoding_len] = 0; + cd = iconv_open("UTF-8", encoding_name); + encoding_name[encoding_len] = save; + } + else + cd = iconv_open("UTF-8", encoding_name); + + if (!iconv_valid(cd)) + { + if (errno == EINVAL) + errorf("Bad arg %d to %s(): Unsupported encoding '%s'.\n", efun_encoding_arg_pos, efun_name, get_txt(encoding)); + else + errorf("%s(): %s\n", efun_name, strerror(errno)); + return NULL; /* NOTREACHED */ + } + + if (len == 0) + { + iconv_close(cd); + return ref_mstring(STR_EMPTY); + } + + /* For small texts, we reserve twice the space. */ + out_buf_left = out_buf_size = len > 32768 ? (len + 2048) : (2 * len); + xallocate(out_buf_start, out_buf_size, "conversion buffer"); + out_buf_ptr = out_buf_start; + + /* Convert the string, reallocating the output buffer where necessary */ + while (true) + { + size_t rc; + bool at_end = (in_buf_left == 0); + + /* At the end we need one final call. */ + if (at_end) + rc = iconv(cd, NULL, NULL, &out_buf_ptr, &out_buf_left); + else + rc = iconv(cd, &in_buf_ptr, &in_buf_left, &out_buf_ptr, &out_buf_left); + + if (rc == (size_t)-1) + { + size_t idx; + if ((errno == EILSEQ || errno == EINVAL) && (ignore || replace)) + { + /* Handle encoding errors ourselves. */ + if (ignore) + { + if (at_end) + break; + + in_buf_ptr++; + in_buf_left--; + continue; + } + + if (replace) + { + /* Add the (3 bytes) replacement char. */ + if (out_buf_left < 3) + errno = E2BIG; + else + { + memcpy(out_buf_ptr, "\xef\xbf\xbd", 3); + out_buf_ptr += 3; + out_buf_left -= 3; + + if (at_end) + break; + + in_buf_ptr++; + in_buf_left--; + continue; + } + } + } + + if (errno == E2BIG) + { + /* Reallocate output buffer */ + size_t new_size = out_buf_size + (len > 128 ? len : 128); + char* new_buf = rexalloc(out_buf_start, new_size); + + if (!new_buf) + { + iconv_close(cd); + + xfree(out_buf_start); + outofmem(new_size, "conversion buffer"); + return NULL; /* NOTREACHED */ + } + + out_buf_ptr = new_buf + (out_buf_ptr - out_buf_start); + out_buf_start = new_buf; + out_buf_left = out_buf_left + new_size - out_buf_size; + out_buf_size = new_size; + continue; + } + + idx = len - in_buf_left; + if (errno == EILSEQ) + { + if (at_end) + { + iconv_close(cd); + xfree(out_buf_start); + errorf("to_text(): Invalid character sequence at byte %zd.\n", idx); + } + else + { + char* errseq = get_illegal_sequence(in_buf_ptr, in_buf_left, cd); + char context[128]; + size_t pos = sizeof(context); + + context[--pos] = 0; + context[--pos] = '"'; + + for (int contextlen = 0; contextlen < 10 && out_buf_ptr > out_buf_start; ) + { + char escbuf[16]; + char *prev = utf8_prev(out_buf_ptr, out_buf_ptr - out_buf_start); + p_int c; + size_t clen = utf8_to_unicode(prev, out_buf_ptr - prev, &c); + size_t esclen; + + if (!clen) + c = *(unsigned char*)prev; + + out_buf_ptr = prev; + contextlen++; + + esclen = get_escaped_character(c, escbuf, sizeof(escbuf)); + if (esclen && esclen < pos) + { + pos -= esclen; + memcpy(context + pos, escbuf, esclen); + } + } + + context[--pos] = '"'; + + iconv_close(cd); + xfree(out_buf_start); + + errorf("to_text(): Invalid character sequence at byte %zd after %s: %s.\n" + , idx + , context + pos + , errseq); + return NULL; /* NOTREACHED */ + } + } + + /* Other error: clean up */ + iconv_close(cd); + xfree(out_buf_start); + + if (errno == EINVAL) + errorf("to_text(): Incomplete character sequence at byte %zd.\n", idx); + + errorf("to_text(): %s\n", strerror(errno)); + return NULL; /* NOTREACHED */ + } + + if (at_end) + break; + } + + iconv_close(cd); + + result = new_n_unicode_mstring(out_buf_start, out_buf_ptr - out_buf_start); + xfree(out_buf_start); + + if (!result) + { + outofmem(out_buf_ptr - out_buf_start, "converted string"); + return NULL; /* NOTREACHED */ + } + + return result; +} /* bytes_to_utf8_string() */ + +/*--------------------------------------------------------------------*/ + svalue_t * v_to_bytes (svalue_t *sp, int num_arg) @@ -1691,125 +2053,15 @@ v_to_bytes (svalue_t *sp, int num_arg) outofmem(out_buf_ptr - out_buf_start, "converted array"); return sp; /* NOTREACHED */ } + result->info.unicode = STRING_BYTES; } else if (text->type == T_STRING) { - iconv_t cd; - - char* in_buf_ptr; - size_t in_buf_size; - size_t in_buf_left; - - char* out_buf_ptr; - char* out_buf_start; - size_t out_buf_size; - size_t out_buf_left; - - cd = iconv_open(get_txt(sp->u.str), "UTF-8"); - if (!iconv_valid(cd)) - { - if (errno == EINVAL) - errorf("Bad arg 2 to to_bytes(): Unsupported encoding '%s'.\n", get_txt(sp->u.str)); - else - errorf("to_bytes(): %s\n", strerror(errno)); - return sp; /* NOTREACHED */ - } - - in_buf_ptr = get_txt(text->u.str); - in_buf_left = in_buf_size = mstrsize(text->u.str); - - if (in_buf_size == 0) - { - iconv_close(cd); - - /* We can't use STR_EMPTY, because that one is a unicode string. */ - memsafe(result = alloc_mstring(0), 0, "converted array"); - result->info.unicode = STRING_BYTES; - - sp = pop_n_elems(2, sp); - push_bytes(sp, result); - return sp; - } - - /* For small texts, we reserve twice the space. */ - out_buf_left = out_buf_size = in_buf_size > 32768 ? (in_buf_size + 2048) : (2 * in_buf_size); - xallocate(out_buf_start, out_buf_size, "conversion buffer"); - out_buf_ptr = out_buf_start; - - /* Convert the string, reallocating the output buffer where necessary */ - while (true) - { - size_t rc; - bool at_end = (in_buf_left == 0); - - /* At the end we need one final call. */ - if (at_end) - rc = iconv(cd, NULL, NULL, &out_buf_ptr, &out_buf_left); - else - rc = iconv(cd, &in_buf_ptr, &in_buf_left, &out_buf_ptr, &out_buf_left); - - if (rc == (size_t)-1) - { - size_t idx; - if (errno == E2BIG) - { - /* Reallocate output buffer */ - size_t new_size = out_buf_size + (in_buf_size > 128 ? in_buf_size : 128); - char* new_buf = rexalloc(out_buf_start, new_size); - - if (!new_buf) - { - iconv_close(cd); - - xfree(out_buf_start); - outofmem(new_size, "conversion buffer"); - return sp; /* NOTREACHED */ - } - - out_buf_ptr = new_buf + (out_buf_ptr - out_buf_start); - out_buf_start = new_buf; - out_buf_left = out_buf_left + new_size - out_buf_size; - out_buf_size = new_size; - continue; - } - - /* Ignore EILSEQ at the end, they come from //IGNORE. */ - if (errno == EILSEQ && !in_buf_left) - continue; - - /* Other error: clean up */ - iconv_close(cd); - xfree(out_buf_start); - - idx = byte_to_char_index(get_txt(text->u.str), in_buf_size - in_buf_left, NULL); - if (errno == EILSEQ) - errorf("to_bytes(): Invalid character sequence at index %zd.\n", idx); - - if (errno == EINVAL) - errorf("to_bytes(): Incomplete character sequence at index %zd.\n", idx); - - errorf("to_bytes(): %s\n", strerror(errno)); - return sp; /* NOTREACHED */ - } - - if (at_end) - break; - } - - iconv_close(cd); - - result = new_n_mstring(out_buf_start, out_buf_ptr - out_buf_start, STRING_BYTES); - xfree(out_buf_start); - if (!result) - { - outofmem(out_buf_ptr - out_buf_start, "converted string"); - return sp; /* NOTREACHED */ - } + result = utf8_string_to_bytes(get_txt(text->u.str), mstrsize(text->u.str), get_txt(sp->u.str), "to_bytes", 2); } else errorf("Bad arg 1 to to_bytes(): byte string and encoding given.\n"); - result->info.unicode = STRING_BYTES; sp = pop_n_elems(2, sp); push_bytes(sp, result); return sp; @@ -1890,37 +2142,22 @@ v_to_text (svalue_t *sp, int num_arg) svalue_t* text = sp-1; string_t* result; - char* encoding_name; - size_t encoding_len; - - iconv_t cd; - bool ignore; - bool replace; - - char* in_buf_start; - char* in_buf_ptr; - size_t in_buf_size; - size_t in_buf_left; - - char* out_buf_ptr; - char* out_buf_start; - size_t out_buf_size; - size_t out_buf_left; - if (text->type == T_POINTER) { /* We need to put these bytes into a byte sequence for iconv. */ - in_buf_size = VEC_SIZE(text->u.vec); - if (in_buf_size == 0) - in_buf_start = NULL; + size_t len = VEC_SIZE(text->u.vec); + char* buf; + + if (len == 0) + buf = NULL; else { svalue_t *elem = text->u.vec->item; - svalue_t *end = elem + in_buf_size; + svalue_t *end = elem + len; char *ch; - xallocate(in_buf_start, in_buf_size, "conversion buffer"); - ch = in_buf_start; + memsafe(buf = xalloc_with_error_handler(len), len, "conversion buffer"); + ch = buf; for (; elem != end; elem++, ch++) { @@ -1928,224 +2165,22 @@ v_to_text (svalue_t *sp, int num_arg) if (item == NULL || item->type != T_NUMBER) { /* We stop here. */ - in_buf_size = ch - in_buf_start; + len = ch - buf; break; } *ch = (char)item->u.number; } } + + result = bytes_to_utf8_string(buf, len, sp->u.str, "to_text", 2); + pop_stack(); /* frees buf. */ } else if (text->type == T_BYTES) - { - in_buf_start = get_txt(text->u.str); - in_buf_size = mstrsize(text->u.str); - } + result = bytes_to_utf8_string(get_txt(text->u.str), mstrsize(text->u.str), sp->u.str, "to_text", 2); else errorf("Bad arg 1 to to_text(): unicode string and encoding given.\n"); - encoding_len = parse_input_encoding(sp->u.str, &ignore, &replace); - encoding_name = get_txt(sp->u.str); - if (encoding_len < mstrsize(sp->u.str)) - { - char save = encoding_name[encoding_len]; - - encoding_name[encoding_len] = 0; - cd = iconv_open("UTF-8", encoding_name); - encoding_name[encoding_len] = save; - } - else - cd = iconv_open("UTF-8", encoding_name); - - if (!iconv_valid(cd)) - { - if (text->type == T_POINTER) - xfree(in_buf_start); - if (errno == EINVAL) - errorf("Bad arg 2 to to_text(): Unsupported encoding '%s'.\n", get_txt(sp->u.str)); - else - errorf("to_text(): %s\n", strerror(errno)); - return sp; /* NOTREACHED */ - } - - in_buf_ptr = in_buf_start; - in_buf_left = in_buf_size; - - if (in_buf_size == 0) - { - iconv_close(cd); - if (text->type == T_POINTER) - xfree(in_buf_start); - - sp = pop_n_elems(2, sp); - push_ref_string(sp, STR_EMPTY); - return sp; - } - - /* For small texts, we reserve twice the space. */ - out_buf_left = out_buf_size = in_buf_size > 32768 ? (in_buf_size + 2048) : (2 * in_buf_size); - xallocate(out_buf_start, out_buf_size, "conversion buffer"); - out_buf_ptr = out_buf_start; - - /* Convert the string, reallocating the output buffer where necessary */ - while (true) - { - size_t rc; - bool at_end = (in_buf_left == 0); - - /* At the end we need one final call. */ - if (at_end) - rc = iconv(cd, NULL, NULL, &out_buf_ptr, &out_buf_left); - else - rc = iconv(cd, &in_buf_ptr, &in_buf_left, &out_buf_ptr, &out_buf_left); - - if (rc == (size_t)-1) - { - size_t idx; - if ((errno == EILSEQ || errno == EINVAL) && (ignore || replace)) - { - /* Handle encoding errors ourselves. */ - if (ignore) - { - if (at_end) - break; - - in_buf_ptr++; - in_buf_left--; - continue; - } - - if (replace) - { - /* Add the (3 bytes) replacement char. */ - if (out_buf_left < 3) - errno = E2BIG; - else - { - memcpy(out_buf_ptr, "\xef\xbf\xbd", 3); - out_buf_ptr += 3; - out_buf_left -= 3; - - if (at_end) - break; - - in_buf_ptr++; - in_buf_left--; - continue; - } - } - } - - if (errno == E2BIG) - { - /* Reallocate output buffer */ - size_t new_size = out_buf_size + (in_buf_size > 128 ? in_buf_size : 128); - char* new_buf = rexalloc(out_buf_start, new_size); - - if (!new_buf) - { - iconv_close(cd); - - xfree(out_buf_start); - if (text->type == T_POINTER) - xfree(in_buf_start); - outofmem(new_size, "conversion buffer"); - return sp; /* NOTREACHED */ - } - - out_buf_ptr = new_buf + (out_buf_ptr - out_buf_start); - out_buf_start = new_buf; - out_buf_left = out_buf_left + new_size - out_buf_size; - out_buf_size = new_size; - continue; - } - - idx = in_buf_size - in_buf_left; - if (errno == EILSEQ) - { - if (at_end) - { - iconv_close(cd); - xfree(out_buf_start); - if (text->type == T_POINTER) - xfree(in_buf_start); - errorf("to_text(): Invalid character sequence at byte %zd.\n", idx); - } - else - { - char* errseq = get_illegal_sequence(in_buf_ptr, in_buf_left, cd); - char context[128]; - size_t pos = sizeof(context); - - context[--pos] = 0; - context[--pos] = '"'; - - for (int contextlen = 0; contextlen < 10 && out_buf_ptr > out_buf_start; ) - { - char escbuf[16]; - char *prev = utf8_prev(out_buf_ptr, out_buf_ptr - out_buf_start); - p_int c; - size_t clen = utf8_to_unicode(prev, out_buf_ptr - prev, &c); - size_t esclen; - - if (!clen) - c = *(unsigned char*)prev; - - out_buf_ptr = prev; - contextlen++; - - esclen = get_escaped_character(c, escbuf, sizeof(escbuf)); - if (esclen && esclen < pos) - { - pos -= esclen; - memcpy(context + pos, escbuf, esclen); - } - } - - context[--pos] = '"'; - - iconv_close(cd); - xfree(out_buf_start); - if (text->type == T_POINTER) - xfree(in_buf_start); - - errorf("to_text(): Invalid character sequence at byte %zd after %s: %s.\n" - , idx - , context + pos - , errseq); - } - } - - /* Other error: clean up */ - iconv_close(cd); - xfree(out_buf_start); - if (text->type == T_POINTER) - xfree(in_buf_start); - - if (errno == EINVAL) - errorf("to_text(): Incomplete character sequence at byte %zd.\n", idx); - - errorf("to_text(): %s\n", strerror(errno)); - return sp; /* NOTREACHED */ - } - - if (at_end) - break; - } - - iconv_close(cd); - - result = new_n_unicode_mstring(out_buf_start, out_buf_ptr - out_buf_start); - xfree(out_buf_start); - if (text->type == T_POINTER) - xfree(in_buf_start); - - if (!result) - { - outofmem(out_buf_ptr - out_buf_start, "converted string"); - return sp; /* NOTREACHED */ - } - sp = pop_n_elems(2, sp); push_string(sp, result); return sp; diff --git a/src/strfuns.h b/src/strfuns.h index f3b6a1cdf..b79fb8445 100644 --- a/src/strfuns.h +++ b/src/strfuns.h @@ -38,6 +38,7 @@ extern size_t get_escaped_character(p_int c, char* buf, size_t buflen); extern bool string_needs_escape(const char * text, size_t len, bool allow_unicode); extern size_t escape_string(const char * text, size_t len, char * buf, size_t buflen, bool allow_unicode); extern size_t unescape_string(const char * text, size_t len, char * buf, size_t buflen); +extern string_t * extend_string (const char *prefix, string_t *txt, const char *suffix); extern size_t parse_input_encoding(string_t* encoding, bool* ignore, bool* replace); @@ -56,6 +57,9 @@ extern size_t next_grapheme_break(const char* str, size_t len, int* width) __att extern int get_string_width(const char* str, size_t len, bool* error) __attribute__((nonnull(1))); extern size_t get_string_up_to_width(const char* str, size_t len, int width, bool* error) __attribute__((nonnull(1))); +extern string_t * utf8_string_to_bytes(const char* src, size_t len, const char* encoding, const char* efun_name, int efun_encoding_arg_pos); +extern string_t * bytes_to_utf8_string(const char* src, size_t len, string_t* encoding, const char* efun_name, int efun_encoding_arg_pos); + extern svalue_t * v_to_bytes(svalue_t *sp, int num_arg); extern svalue_t * v_to_text(svalue_t *sp, int num_arg); extern string_t * intersect_strings (string_t * left, string_t * right, Bool bSubtract); diff --git a/src/struct_spec b/src/struct_spec index 9d3dc1827..df49f4b3e 100644 --- a/src/struct_spec +++ b/src/struct_spec @@ -24,3 +24,13 @@ struct compile_string_options int detect_end; }; + +/* Argument for to_type() efun. + */ +struct to_type_options +{ + string source_encoding; + string target_encoding; + + int keep_zero; +}; diff --git a/test/t-efuns.c b/test/t-efuns.c index 68f89494c..529f762f4 100644 --- a/test/t-efuns.c +++ b/test/t-efuns.c @@ -1,4 +1,4 @@ -#pragma save_types +#pragma save_types, lightweight, clone #define OWN_PRIVILEGE_VIOLATION @@ -31,6 +31,7 @@ struct derived_struct (test_struct) }; struct cs_opts (compile_string_options) {}; +struct tt_opts (to_type_options) {}; mapping json_testdata = ([ "test 1": 42, "test 2": 42.0, "test 3": "hello world\n", @@ -56,13 +57,13 @@ string dhe_testdata = mixed global_var; int f(int arg); -object clone = clonep() ? 0 : clone_object(this_object()); +object clone = (this_object() == blueprint()) && clone_object(this_object()); string last_privi_op; mixed last_privi_who; mixed* last_privi_args; -mixed *tests = +mixed *tests = (this_object() == blueprint()) && // String compiler test boundary ({ // TODO: Add cases for indexing at string end ("abc"[3]) @@ -1433,6 +1434,102 @@ mixed *tests = ({ "to_struct derived from base struct", 0, (: mixed b = ( ({10,20})); return deep_eq(to_struct(b, ()), ( ({10,20}), 0)); :) }), ({ "to_struct base from derived struct", 0, (: mixed d = ( ({10,20}), ({"A","B"})); return deep_eq(to_struct(d, ()), ( ({10,20}))); :) }), + ({ "to_type int* to mixed*", 0, (: deep_eq(to_type(({1,2,3}), [mixed*]), ({1,2,3})) :) }), + ({ "to_type int* to string", 0, (: deep_eq(to_type(({76,68,77,117,100}), [string]), "LDMud") :) }), + ({ "to_type int* to bytes", 0, (: deep_eq(to_type(({76,68,77,117,100}), [bytes]), b"LDMud") :) }), + ({ "to_type mixed* to int*", 0, (: deep_eq(to_type(({1,2,3}), [int*]), ({1,2,3})) :) }), + ({ "to_type string* to int*", 0, (: deep_eq(to_type(({"1", "-20", "0x30", "-0b100"}), [int*]), ({1, -20, 48, -4})) :) }), + ({ "to_type mixed* to string*", 0, (: deep_eq(to_type(({"abc", 0, 1, 2.3}), [string*]), ({"abc", "0", "1", "2.3"})) :) }), + ({ "to_type mixed* to string* with keep_zero", 0, (: deep_eq(to_type(({"abc", 0, 1, 2.3}), [string*], ( keep_zero: 1)), ({"abc", 0, "1", "2.3"})) :) }), + ({ "to_type mixed* to struct mixed", 0, (: deep_eq(to_type(({"abc", 3, #'abs}), [struct mixed]), to_struct(({"abc", 3, #'abs}))) :) }), + ({ "to_type mixed* to derived_struct", 0, (: deep_eq(to_type(({ ({1, "2", 3.3}), ({4, "5", 6.6}) }), [struct derived_struct]), ( arg: ({1, 2, 3}), values: ({"4", "5", "6.6"})) ) :) }), + ({ "to_type mixed* to string|mapping", 0, (: deep_eq(to_type(({"abc", 5, #'to_type}), [string|mapping]), (["abc", 5, #'to_type])) :) }), + ({ "to_type mixed* to quoted array", 0, (: deep_eq(to_type(({"abc", 5, #'to_type}), decltype('({}))), '({"abc", 5, #'to_type})) :) }), + ({ "to_type bytes to bytes|int*", 0, (: deep_eq(to_type(b"\x01\x02\x03", [bytes|int*]) , b"\x01\x02\x03") :) }), + ({ "to_type bytes to string without encoding", TF_ERROR, (: to_type(b"\xe2\x98\xba\xe2\x98\xb9", [string]) :) }), + ({ "to_type bytes to string with encoding", 0, (: deep_eq(to_type(b"\xe2\x98\xba\xe2\x98\xb9", [string], ( source_encoding: "utf-8")), "\u263a\u2639") :) }), + ({ "to_type bytes to int*", 0, (: deep_eq(to_type(b"\x01\x02\x03", [int*]) , ({1,2,3})) :) }), + ({ "to_type efun closure to closure|object", 0, (: to_type(#'abs, [closure|object]) == #'abs :) }), + ({ "to_type efun closure to object", 0, (: to_type(#'abs, [object]) == this_object() :) }), + ({ "to_type lfun closure to object", 0, (: to_type(#'f, [object]) == this_object() :) }), + ({ "to_type variable closure to object", 0, (: to_type(#'b, [object]) == this_object() :) }), + ({ "to_type lambda closure to object", 0, (: to_type(lambda(0,0), [object]) == this_object() :) }), + ({ "to_type efun closure to lwobject", 0, (: lwobject lwo = new_lwobject(object_name()); return to_type(lwo.get_efun_closure(), [lwobject]) == lwo; :) }), + ({ "to_type lfun closure to lwobject", 0, (: lwobject lwo = new_lwobject(object_name()); return to_type(lwo.get_lfun_closure(), [lwobject]) == lwo; :) }), + ({ "to_type var closure to lwobject", 0, (: lwobject lwo = new_lwobject(object_name()); return to_type(lwo.get_var_closure(), [lwobject]) == lwo; :) }), + ({ "to_type lambda closure to lwobject", 0, (: lwobject lwo = new_lwobject(object_name()); return to_type(bind_lambda(unbound_lambda(0,0), lwo), [lwobject]) == lwo; :) }), + ({ "to_type variable closure to int", 0, (: to_type(#'b, [int]) == 3 :) }), + ({ "to_type efun closure to string", 0, (: "abs" in to_type(#'abs, [string]) :) }), + ({ "to_type lfun closure to string", 0, (: "msg" in to_type(#'msg, [string]) :) }), + ({ "to_type variable closure to string", 0, (: "global_var" in to_type(#'global_var, [string]) :) }), + ({ "to_type lambda closure to string", 0, (: "lambda" in to_type(lambda(0,0), [string]) :) }), + ({ "to_type coroutine to coroutine|string", 0, (: coroutine cr = async function void() {}; return to_type(cr, [coroutine|string]) == cr; :) }), + ({ "to_type coroutine to string", 0, (: object_name() in to_type(async function void() {}, [string]) :) }), + ({ "to_type int to int|string", 0, (: to_type(11, [int|string]) == 11 :) }), + ({ "to_type int to float|string", 0, (: floatp(to_type(11, [float|string])) && to_type(11, [float|string]) == 11.0 :) }), + ({ "to_type int to string|object", 0, (: to_type(11, [string|object]) == "11" :) }), + ({ "to_type float to float|int", 0, (: to_type(11.235, [int|float]) == 11.235 :) }), + ({ "to_type float to int|string", 0, (: to_type(11.235, [int|string]) == 11 :) }), + ({ "to_type float to string|object", 0, (: to_type(11.235, [string|object]) == "11.235" :) }), + ({ "to_type lpctype to lpctype*", 0, (: deep_eq(mkmapping(to_type([int|float|string], [lpctype*])), ([ [int], [float], [string] ])) :) }), + ({ "to_type lpctype to mixed*", 0, (: deep_eq(mkmapping(to_type([mixed*|object], [mixed*|object])), ([ [mixed*], [object] ])) :) }), + ({ "to_type lpctype to mapping", 0, (: deep_eq(to_type([int|float|string], [mapping]), ([ [int], [float], [string] ])) :) }), + ({ "to_type lwobject to lwobject|string", 0, (: lwobject lwo = new_lwobject(object_name()); return to_type(lwo, [lwobject|string]) == lwo; :) }), + ({ "to_type lwobject to string", 0, (: lwobject lwo = new_lwobject(object_name()); return object_name() in to_type(lwo, [string]); :) }), + ({ "to_type mapping to mapping|mixed*", 0, (: mapping m = (["a", "b", "c"]); return to_type(m, [mapping|mixed*]) == m; :) }), + ({ "to_type mapping to derived_struct", 0, (: deep_eq(to_type((["values":({"1","2",3}), "arg":({"4","5",6})]), [struct derived_struct]), ( arg: ({4, 5, 6}), values: ({"1", "2", "3"})) ) :) }), + ({ "to_type wide mapping to derived_struct", 0, (: deep_eq(to_type((["values":"1";"2";3, "arg":"4";"5";6]), [struct derived_struct]), ( arg: ({4, 5, 6}), values: ({"1", "2", "3"})) ) :) }), + ({ "to_type empty mapping to derived_struct", 0, (: deep_eq(to_type(([]), [struct derived_struct]), () ) :) }), + ({ "to_type mapping to struct mixed", 0, (: deep_eq(to_type((["field1": "abc", "field2": 3, "field3": #'abs]), [struct mixed]), to_struct((["field1": "abc", "field2": 3, "field3": #'abs]))) :) }), + ({ "to_type mapping to ", 0, (: deep_eq(sort_array(to_type((["a":"x";"y";"z", "b":"1";"2";3]), [string**]), (: $1[0] > $2[0] :)), ({ ({ "a", "x", "y", "z" }), ({"b", "1", "2", "3"}) })) :) }), + ({ "to_type mapping to ", 0, (: deep_eq(sort_array(to_type(([65, 66, 67]), [string*]), #'>), ({ "A", "B", "C"})) :) }), + ({ "to_type mapping to ", 0, (: deep_eq(to_type((["a":"b";"c";"d"]), [mapping*]), ({ ([ "a", "b", "c", "d" ]) })) :) }), + ({ "to_type object to object|string", 0, (: to_type(this_object(), [object|string]) == this_object() :) }), + ({ "to_type object to string", 0, (: object_name() in to_type(this_object(), [string]) :) }), + ({ "to_type quoted_array to quoted_array|array", 0, (: mixed arr = '({'L', 'D'}); return to_type(arr, decltype('({}))|[mixed*]) == arr; :) }), + ({ "to_type quoted_array to array", 0, (: deep_eq(to_type('({'L', 'D'}), [mixed*]), ({'L', 'D'})) :) }), + ({ "to_type quoted_array to string", 0, (: to_type('({'L', 'D'}), [string]) == "LD" :) }), + ({ "to_type string to string|int", 0, (: to_type("123", [string|int]) == "123" :) }), + ({ "to_type string to symbol", 0, (: to_type("abc", [symbol]) == 'abc :) }), + ({ "to_type string to int", 0, (: to_type("0x123", [int]) == 0x123 :) }), + ({ "to_type string(__INT_MAX__) to int", 0, (: to_type(to_string(__INT_MAX__), [int]) == __INT_MAX__ :) }), + ({ "to_type string(__INT_MIN__) to int", 0, (: to_type(to_string(__INT_MIN__), [int]) == __INT_MIN__ :) }), + ({ "to_type string to int", 0, (: to_type("0x123", [int]) == 0x123 :) }), + ({ "to_type string to float", 0, (: to_type("3.25", [float]) == 3.25 :) }), + ({ "to_type long string to float", 0, (: to_type("0.00000000000000000000000000000000000000001", [float]) == 0.00000000000000000000000000000000000000001 :) }), + ({ "to_type string to lpctype", 0, (: to_type("string|lpctype", [lpctype]) == [string|lpctype] :) }), + ({ "to_type string to object", 0, (: to_type(object_name(), [object]) == this_object() :) }), + ({ "to_type string to bytes without encoding", TF_ERROR, (: to_type("\u263a\u2639", [bytes]) :) }), + ({ "to_type string to bytes with encoding", 0, (: to_type("\u263a\u2639", [bytes], ( target_encoding: "utf-8")) == b"\xe2\x98\xba\xe2\x98\xb9" :) }), + ({ "to_type string to int*", 0, (: deep_eq(to_type("\u263a\u2639", [int*]) , ({0x263a, 0x2639})) :) }), + ({ "to_type derived_struct to test_struct", 0, (: struct derived_struct s = ( values: ({"A", "B"}), arg: ({1, 2})); return to_type(s, [struct test_struct]) == s; :) }), + ({ "to_type test_struct to derived_struct", 0, (: deep_eq(to_type(( arg: ({1, 2})), [struct derived_struct]), ( arg: ({1,2}))) :) }), + ({ "to_type derived_struct to mapping", 0, (: deep_eq(to_type(( values: ({"A", "B"}), arg: ({1, 2})), [mapping]), (["values": ({"A","B"}), "arg": ({1,2})])) :) }), + ({ "to_type derived_struct to string", 0, (: "derived_struct" in to_type(( values: ({"A", "B"}), arg: ({1, 2})), [string]) :) }), + ({ "to_type symbol to symbol|string", 0, (: to_type('LDMud, [symbol|string]) == 'LDMud :) }), + ({ "to_type symbol to string", 0, (: to_type('LDMud, [string]) == "LDMud" :) }), + ({ "to_type symbol to int*", 0, (: deep_eq(to_type('abc, [int*]), ({'a', 'b', 'c'})) :) }), + ({ "to_type array range to quoted array", 0, (: mixed* arr = ({ 1, 2, 3, 4, 5 }), *range = ({ &(arr[2..3]) }); return deep_eq(to_type(range, decltype(({'({}) }))), ({ '({3,4}) })); :) }), + ({ "to_type string range to int", 0, (: string s = "12345", *range = ({ &(s[2..3]) }); return deep_eq(to_type(range, [int*]), ({ 34 })); :) }), + ({ "to_type string range to string", 0, (: string s = "12345", *range = ({ &(s[2..3]) }); return deep_eq(to_type(range, [string*]), ({ "34" })); :) }), + ({ "to_type string range to symbol", 0, (: string s = "LDMud", *range = ({ &(s[2..3]) }); return deep_eq(to_type(range, [symbol*]), ({ 'Mu })); :) }), + ({ "to_type string range to object", 0, (: string s = "__" + object_name() + "__", *range = ({ &(s[2..<3]) }); return deep_eq(to_type(range, [object*]), ({ this_object()})); :) }), + ({ "to_type string range to lpctype", 0, (: string s = "int|string|object", *range = ({ &(s[4..9]) }); return deep_eq(to_type(range, [lpctype*]), ({ [string] })); :) }), + ({ "to_type bytes range to bytes", 0, (: bytes b = b"LDMud", *range = ({ &(b[2..3]) }); return deep_eq(to_type(range, [bytes*]), ({ b"Mu" })); :) }), + ({ "to_type bytes range to string", 0, (: bytes b = b"LDMud", *range = ({ &(b[2..3]) }); return deep_eq(to_type(range, [string*], ( source_encoding: "utf-8")), ({ "Mu" })); :) }), + ({ "to_type existing map range to string", 0, (: mapping m = ([ "A": 1;2;3;4;5 ]); int** range = ({ &(m["A",2..3]) }); return deep_eq(to_type(range, [string*]), ({ "\x03\x04" })); :) }), + ({ "to_type existing map range to byte", 0, (: mapping m = ([ "A": 1;2;3;4;5 ]); int** range = ({ &(m["A",2..3]) }); return deep_eq(to_type(range, [bytes*]), ({ b"\x03\x04" })); :) }), + ({ "to_type existing map range to int*" , 0, (: mapping m = ([ "A": 1;2;3;4;5 ]); int** range = ({ &(m["A",2..3]) }); return deep_eq(to_type(range, [int**]), ({ ({3,4}) })); :) }), + ({ "to_type existing map range to mapping", 0, (: mapping m = ([ "A": 1;2;3;4;5 ]); int** range = ({ &(m["A",2..3]) }); return deep_eq(to_type(range, [mapping*]), ({ ([3,4]) })); :) }), + ({ "to_type existing map range to struct mixed", 0, (: mapping m = ([ "A": 1;2;3;4;5 ]); int** range = ({ &(m["A",2..3]) }); return deep_eq(to_type(range, [struct mixed*]), ({ to_struct(({3,4})) })); :) }), + ({ "to_type existing map range to derived_struct", 0, (: mapping m = ([ "A": 1;2;({3});({4});5 ]); int** range = ({ &(m["A",2..3]) }); return deep_eq(to_type(range, [struct derived_struct*]), ({ ( arg: ({3}), values: ({"4"})) })); :) }), + ({ "to_type non-existing map range to string", 0, (: mapping m = ([ "A": 1;2;3;4;5 ]); int** range = ({ &(m["B",2..3]) }); return deep_eq(to_type(range, [string*]), ({ "\x00\x00" })); :) }), + ({ "to_type non-existing map range to byte", 0, (: mapping m = ([ "A": 1;2;3;4;5 ]); int** range = ({ &(m["B",2..3]) }); return deep_eq(to_type(range, [bytes*]), ({ b"\x00\x00" })); :) }), + ({ "to_type non-existing map range to int*", 0, (: mapping m = ([ "A": 1;2;3;4;5 ]); int** range = ({ &(m["B",2..3]) }); return deep_eq(to_type(range, [int**]), ({ ({0,0}) })); :) }), + ({ "to_type non-existing map range to mapping", 0, (: mapping m = ([ "A": 1;2;3;4;5 ]); int** range = ({ &(m["B",2..3]) }); return deep_eq(to_type(range, [mapping*]), ({ ([0]) })); :) }), + ({ "to_type non-existing map range to struct mixed", 0, (: mapping m = ([ "A": 1;2;3;4;5 ]); int** range = ({ &(m["B",2..3]) }); return deep_eq(to_type(range, [struct mixed*]), ({ to_struct(({0,0})) })); :) }), + ({ "to_type non-existing map range to derived_struct",0, (: mapping m = ([ "A": 1;2;3;4;5 ]); int** range = ({ &(m["B",2..3]) }); return deep_eq(to_type(range, [struct derived_struct*]), ({ () })); :) }), + ({ "get_type_info with temporary anonymous struct 1", 0, (: deep_eq(get_type_info(to_struct((["A": 10]))), ({ T_STRUCT, "anonymous" })) :) }), ({ "get_type_info with temporary anonymous struct 2", 0, (: !strstr(get_type_info(to_struct((["A": 10])), 2), "anonymous ") :) }), ({ "struct_info of regular struct 1", 0, (: mixed info = struct_info(( ({10,20})), SINFO_FLAT); @@ -1772,6 +1869,10 @@ int get_args() return args; } +closure get_lfun_closure() { return #'f; } +closure get_var_closure() { return #'b; } +closure get_efun_closure() { return #'abs; } + struct derived_struct get_struct() { return ( ({11,12}), ({"A","B"})); diff --git a/test/t-python/master.c b/test/t-python/master.c index 888c248b8..e79580108 100644 --- a/test/t-python/master.c +++ b/test/t-python/master.c @@ -277,6 +277,12 @@ void run_test() || s != "10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376000") return 0; + // Check __int__, __float__ und __str__ working. + if (to_type(val, [int]) != 1000 + || to_type(val, [string]) != "1000" + || to_type(val, [float]) != 1000.0) + return 0; + val <<= 1000; bigint val2 = restore_value(save_value(val)); if (val != val2) @@ -349,6 +355,10 @@ void run_test() || e.get_value() != 101) return 0; + /* Check to_type() using __convert__(). */ + if (to_type(b, [int|string]) != 101) + return 0; + /* And works with complex data structures. */ b.set_value(({20,30})); c = restore_value(save_value(b)); diff --git a/test/t-python/startup.py b/test/t-python/startup.py index be15e9e87..79b69bf57 100644 --- a/test/t-python/startup.py +++ b/test/t-python/startup.py @@ -1216,6 +1216,9 @@ def __copy__(self): def __save__(self): return self.value + def __convert__(self, target, opts): + return ldmud.efuns.to_type(self.value, target, *(o for o in (opts,) if o is not None)) + @staticmethod def __restore__(val): return box(val) From 30e9791e1d4e8f87e8cf7a14319af7de504fb211 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Sun, 19 Nov 2023 23:04:07 +0100 Subject: [PATCH 17/21] Fix cleanup when building arguments for LPC calls from Python When Python code called into LPC with invalid arguments, there was an off-by-one error when cleaning up arguments on the stack. --- src/pkg-python.c | 6 +++--- test/t-python/startup.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/pkg-python.c b/src/pkg-python.c index f442495fa..949c80066 100644 --- a/src/pkg-python.c +++ b/src/pkg-python.c @@ -3735,7 +3735,7 @@ ldmud_program_lfun_call (ldmud_program_and_index_t *lfun, PyObject *arg, PyObjec if (err != NULL) { PyErr_SetString(PyExc_ValueError, err); - pop_n_elems(i, sp); + pop_n_elems(i, sp-1); return NULL; } } @@ -8804,7 +8804,7 @@ ldmud_closure_call (ldmud_closure_t *cl, PyObject *arg, PyObject *kw) if (err != NULL) { PyErr_SetString(PyExc_ValueError, err); - pop_n_elems(i, sp); + pop_n_elems(i, sp-1); return NULL; } } @@ -11660,7 +11660,7 @@ ldmud_efun_call (ldmud_efun_t *func, PyObject *arg, PyObject *kw) if (err != NULL) { PyErr_SetString(PyExc_ValueError, err); - pop_n_elems(i, sp); + pop_n_elems(i, sp-1); return NULL; } } diff --git a/test/t-python/startup.py b/test/t-python/startup.py index 79b69bf57..5cc91da56 100644 --- a/test/t-python/startup.py +++ b/test/t-python/startup.py @@ -85,6 +85,8 @@ def testFunctionInfo(self): self.assertEqual(args[1].type, ldmud.Array[ldmud.String]) self.assertEqual(fun(10, "A", "B", "C"), 3) + with self.assertRaises(ValueError): + fun(ldmud.Array([1]), ldmud) def testVariableInfo(self): ob = ldmud.Object("/testob") @@ -576,6 +578,8 @@ def testEfun(self): s2 = ldmud.Closure(self.master, "this_object") self.assertEqual(s2, s) self.assertIn(s2, set((s,))) + with self.assertRaises(ValueError): + s(ldmud.Array([1]), ldmud) def testLWOEfun(self): lwob = ldmud.LWObject("/testob") @@ -602,6 +606,8 @@ def testLfun(self): s2 = ldmud.Closure(self.master, "master_fun", self.master) self.assertEqual(s2, s) self.assertIn(s2, set((s,))) + with self.assertRaises(ValueError): + s(ldmud.Array([1]), ldmud) def testDestructedLfun(self): ob = ldmud.Object("/testob") @@ -1011,6 +1017,10 @@ def testCalls(self): self.assertEqual(ldmud.efuns.call_other(master, "master_fun"), 54321) self.assertEqual(ldmud.efuns.object_name(master), "/master") + def testCallWithInvalidArg(self): + with self.assertRaises(ValueError): + ldmud.efuns.call_other(ldmud.get_master(), "fun", ldmud.Array([1]), ldmud) + class TestRegisteredEfuns(unittest.TestCase): def testDir(self): self.assertIn("python_test", dir(ldmud.registered_efuns)) From d299287d2d016da34e49895f1b7e67efcc7ae3f4 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Mon, 20 Nov 2023 00:02:45 +0100 Subject: [PATCH 18/21] Fix Python warning about not untracked objects Python representations of LPC type objects inherit from the Python base type object which does support GC. Therefore upon destruction we need to untrack them. --- src/pkg-python.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pkg-python.c b/src/pkg-python.c index 949c80066..5e4c584b9 100644 --- a/src/pkg-python.c +++ b/src/pkg-python.c @@ -1829,6 +1829,8 @@ ldmud_lpctype_type_dealloc (ldmud_gc_lpctype_type_t* self) remove_gc_lpctype_object(self); + if (Py_TYPE(self)->tp_flags & Py_TPFLAGS_HAVE_GC) + PyObject_GC_UnTrack(self); Py_XDECREF(self->ldmud_lpctype.type_base.tp_base); Py_XDECREF(self->ldmud_lpctype.type_base.tp_dict); Py_XDECREF(self->ldmud_lpctype.type_base.tp_bases); @@ -2128,6 +2130,8 @@ ldmud_concrete_array_type_dealloc (ldmud_concrete_array_type_t* self) */ { + if (Py_TYPE(self)->tp_flags & Py_TPFLAGS_HAVE_GC) + PyObject_GC_UnTrack(self); Py_XDECREF(self->element_type); Py_XDECREF(self->ldmud_lpctype.type_base.tp_base); Py_XDECREF(self->ldmud_lpctype.type_base.tp_dict); @@ -2400,6 +2404,8 @@ ldmud_concrete_struct_type_dealloc (ldmud_concrete_struct_type_t* self) free_struct_name(self->name); REMOVE_GC_OBJECT(gc_struct_type_list, self); + if (Py_TYPE(self)->tp_flags & Py_TPFLAGS_HAVE_GC) + PyObject_GC_UnTrack(self); Py_XDECREF(self->ldmud_lpctype.type_base.tp_base); Py_XDECREF(self->ldmud_lpctype.type_base.tp_dict); Py_XDECREF(self->ldmud_lpctype.type_base.tp_bases); From 2e703cf791e621f634b288eb530590e037fa525b Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Mon, 20 Nov 2023 22:21:13 +0100 Subject: [PATCH 19/21] Added Menaures' Crasher as a test setup With './run.sh crasher' the crasher can be called for testing the driver without a mudlib. The config.h contains some configuration options. --- test/crasher/a.c | 5 + test/crasher/config.h | 22 ++ test/crasher/crasher.c | 685 +++++++++++++++++++++++++++++++++++++++++ test/crasher/inc | 1 + test/crasher/master.c | 36 +++ test/crasher/sys | 1 + 6 files changed, 750 insertions(+) create mode 100644 test/crasher/a.c create mode 100644 test/crasher/config.h create mode 100644 test/crasher/crasher.c create mode 120000 test/crasher/inc create mode 100644 test/crasher/master.c create mode 120000 test/crasher/sys diff --git a/test/crasher/a.c b/test/crasher/a.c new file mode 100644 index 000000000..7887f5720 --- /dev/null +++ b/test/crasher/a.c @@ -0,0 +1,5 @@ +#pragma lightweight, clone + +void create() +{ +} \ No newline at end of file diff --git a/test/crasher/config.h b/test/crasher/config.h new file mode 100644 index 000000000..bac44823a --- /dev/null +++ b/test/crasher/config.h @@ -0,0 +1,22 @@ +/* Configuration for the crasher. */ + +/* The file to replay. */ +#define REPLAY_FILE "/crash/LAST_EXEC" + +/* If defined and the replay file doesn't exist, generate a new file. */ +#define GENERATE_IF_NO_REPLAY_FILE + +/* Number of calls to do in one run. */ +#define CALLS 100000 + +/* Maximum number of arguments for efun calls. */ +#define MAX_ARGS 5 + +/* Reinitialization after this many calls. */ +#define REINIT_LENGTH 1000 + +/* Prints each executed function before executing. */ +#undef VERBOSE + +/* Test a specific efun. */ +// #define TEST_EFUN #'to_type diff --git a/test/crasher/crasher.c b/test/crasher/crasher.c new file mode 100644 index 000000000..a377dd1eb --- /dev/null +++ b/test/crasher/crasher.c @@ -0,0 +1,685 @@ +/*--------------------------------------------------------------------------- + * Fuzzy efun tester + * + * Author: Menaures (07.12.2001) + *--------------------------------------------------------------------------- + * Calls efuns with random arguments. + * + * For configuration options see config.h + * + * Generate + * -------- + * Randomly chooses functions to be called and their arguments. These + * decisions are stored in a savefile under /crash with the current time. + * + * After generation the functions will be executed. The efun call result + * is of no consequence. It's just important, that the driver doesn't crash. + * The current function and its arguments are stored in /crash/LAST_EXEC + * and removed upon a succussful call. + * + * Replay + * ------ + * When REPLAY_FILE is defined, this file will be read instead and executed. + * + *--------------------------------------------------------------------------- + */ + +#include "/inc/msg.inc" +#include "/config.h" + +struct Foo { + symbol value; +}; + +struct Bar(Foo) { + int num; +}; + +mixed efuns; +mixed values; + +void reset_all() +{ + mixed val; + string str = "Hallihallo"; + + efuns = + ({ + #'[, + #'abs, + #'acos, + #'add_action, + #'all_environment, + #'all_inventory, + #'allocate, + #'and_bits, + #'apply, + #'asin, + #'atan, + #'atan2, +#if __EFUN_DEFINED__(attach_erq_demon) + #'attach_erq_demon, +#endif + #'baseof, + #'binary_message, + #'bind_lambda, + #'blueprint, +#if __EFUN_DEFINED__(break_point) + #'break_point, +#endif + #'bytesp, + #'call_coroutine, + #'call_direct, + #'call_direct_resolved, + #'call_direct_strict, + #'caller_stack, + #'caller_stack_depth, + #'call_other, + #'call_out, + #'call_out_info, + #'call_resolved, + #'call_strict, + #'capitalize, + #'catch, + #'ceil, + #'check_type, + #'clear_bit, + #'clone_object, + #'clonep, + #'clones, + #'closurep, + #'command, + #'command_stack, + #'command_stack_depth, + #'compile_string, + #'configure_driver, + #'configure_interactive, + #'configure_lwobject, + #'configure_object, + #'copy, + #'copy_bits, + #'copy_file, + #'coroutinep, + #'cos, + #'count_bits, +#if __EFUN_DEFINED__(creator) + #'creator, +#endif + #'crypt, + #'ctime, +#ifdef __MYSQL__ + #'db_affected_rows, + #'db_close, + #'db_coldefs, + #'db_connect, + #'db_conv_string, + #'db_error, + #'db_exec, + #'db_fetch, + #'db_handles, + #'db_insert_id, +#endif + #'debug_message, + #'deep_copy, + #'deep_inventory, + #'destruct, + #'driver_info, + #'dump_driver_info, + #'ed, + #'environment, + #'exec, + #'execute_command, + #'exp, + #'expand_define, + #'explode, + #'extern_call, + #'file_size, + #'filter, + #'filter_indices, + #'filter_objects, + #'find_call_out, + #'find_input_to, + #'find_object, + #'first_inventory, + #'floatp, + #'floor, + #'funcall, + #'function_exists, + #'functionlist, + #'garbage_collection, + #'get_dir, + #'get_error_file, + #'geteuid, +#if __EFUN_DEFINED__(getuid) + #'getuid, +#endif + #'get_eval_cost, + #'get_extra_wizinfo, + #'get_type_info, + #'getuid, + #'gmtime, + #'hash, + #'heart_beat_info, + #'hmac, +#ifdef __IDNA__ + #'idna_stringprep, + #'idna_to_ascii, + #'idna_to_unicode, +#endif + #'implode, + #'include_list, + #'inherit_list, + #'input_to, + #'input_to_info, + #'interactive, + #'interactive_info, + #'intp, + #'invert_bits, +#ifdef __JSON__ + #'json_parse, + #'json_serialize, +#endif + #'lambda, + #'last_bit, +#if __EFUN_DEFINED__(last_instructions) + #'last_instructions, +#endif + #'limited, + #'living, + #'load_name, + #'load_object, + #'localtime, + #'log, + #'lower_case, + #'lpctypep, + #'lwobject_info, + #'lwobjectp, + #'m_add, + #'make_shared_string, + #'m_allocate, + #'map, + #'map_indices, + #'map_objects, + #'mappingp, + #'master, + #'match_command, + #'max, + #'m_contains, + #'md5, + #'md5_crypt, + #'m_delete, + #'member, + #'m_entry, + #'min, + #'m_indices, + #'mkdir, + #'mkmapping, + #'mktime, + #'move_object, + #'m_reallocate, + #'m_values, + #'negate, + #'net_connect, + #'new_lwobject, + #'next_bit, + #'next_inventory, + #'notify_fail, + #'object_info, + #'object_name, + #'objectp, + #'objects, + #'object_time, + #'or_bits, +#if __EFUN_DEFINED__(parse_command) + #'parse_command, +#endif +#ifdef __PGSQL__ + #'pg_close, + #'pg_connect, + #'pg_conv_string, + #'pg_pending, + #'pg_query, +#endif + #'pointerp, + #'pow, + #'present, + #'present_clone, + #'previous_object, + #'printf, +#if __EFUN_DEFINED__(process_string) + #'process_string, +#endif + #'program_name, + #'program_time, + #'query_actions, + #'query_command, + #'query_notify_fail, + #'query_verb, + #'quote, + #'raise_error, + #'random, + #'read_bytes, + #'read_file, + #'referencep, + #'regexp, + #'regexplode, + #'regexp_package, + #'regmatch, + #'regreplace, + #'remove_action, + #'remove_call_out, + #'remove_input_to, + #'remove_interactive, + #'rename, + #'rename_object, + #'replace_program, + #'restore_object, + #'restore_value, + #'reverse, + #'rm, + #'rmdir, + #'rmember, + #'rusage, + #'save_object, + #'save_value, + #'say, +#if __EFUN_DEFINED__(send_erq) + #'send_erq, +#endif + #'send_udp, + #'set_bit, + #'set_driver_hook, + #'set_environment, + #'set_extra_wizinfo, + #'set_next_reset, + #'set_this_object, + #'set_this_player, + #'sgn, + #'sha1, + #'shadow, + //#'shutdown, + #'sin, + #'sizeof, +#ifdef __SQLITE__ + #'sl_close, + #'sl_exec, + #'sl_insert_id, + #'sl_open, +#endif + #'snoop, + #'sort_array, + #'sprintf, + #'sqrt, + #'sscanf, + #'strftime, + #'stringp, + #'strrstr, + #'strstr, + #'struct_info, + #'structp, +#if __EFUN_DEFINED__(swap) + #'swap, +#endif + #'symbol_function, + #'symbolp, + #'symbol_variable, + #'tan, + #'tell_object, + #'tell_room, + #'terminal_colour, + #'test_bit, + #'text_width, + #'this_coroutine, + #'this_interactive, + #'this_object, + #'this_player, + #'throw, + #'time, + #'tls_available, + #'tls_check_certificate, + #'tls_deinit_connection, + #'tls_error, + #'tls_init_connection, + #'tls_query_connection_info, + #'tls_query_connection_state, + #'tls_refresh_certs, + #'to_array, + #'to_bytes, + #'to_float, + #'to_int, + #'to_lpctype, + #'to_object, + #'to_string, + #'to_struct, + #'to_text, + #'to_type, + #'trace, + #'traceprefix, +#if __EFUN_DEFINED__(transfer) + #'transfer, +#endif + #'transpose_array, + #'trim, + #'typeof, + #'unbound_lambda, + #'unique_array, + #'unmkmapping, + #'unquote, + #'unshadow, + #'upper_case, + #'users, + #'utime, + #'variable_exists, + #'variable_list, + #'walk_mapping, + #'widthof, + #'wizlist_info, + #'write, + #'write_bytes, + #'write_file, +#ifdef __XML_DOM__ + #'xml_generate, + #'xml_parse, +#endif + #'xor_bits, + + #',, + #'=, + #'+=, + #'-=, + #'&=, + #'|=, + #'^=, + #'<<=, + #'>>=, + #'>>>=, + #'*=, + #'%=, + #'/=, + #'?, + #'?!, + #'||, + #'&&, + #'|, + #'^, + #'&, + #'==, + #'!=, + #'>, + #'>=, + #'<, + #'<=, + #'<<, + #'>>, + #'>>>, + #'+, + #'-, + #'*, + #'%, + #'/, + #'++, + #'--, + #'negate, + #'!, + #'~, + #'({, + #'([, + #'[, + #'[<, + #'[>, + #'[,], + #'[,<], + #'[,>], + #'[..], + #'[..<], + #'[..>], + #'[<..], + #'[<..<], + #'[<..>], + #'[>..], + #'[>..<], + #'[>..>], + #'[.., + #'[<.., + #'[>.., + #'[,..], + #'[,..<], + #'[,..>], + #'[,<..], + #'[,<..<], + #'[,<..>], + #'[,>..], + #'[,>..<], + #'[,>..>], + #'({, + #'([, + #'(<, + + #'reset_all /* Needs to be the last entry. */ + }); + + values = + ({ + /* --- Integers: --- */ + 0, + 1, + -1, + 32, + -32, + 40, + -40, + 29292929, + -5050211, + __INT_MAX__, + __INT_MIN__, + + /* --- Floats: --- */ + 0.0, + 0.5, + -0.5, + 55.5, + -55.5, + 999999999999999999999999999999999999999999999.99, + -999999999999999999999999999999999999999999999.99, + + /* --- Strings: --- */ + "", + "foo bar", + "%d %q %T", + "0", + "", + " ", + "_", + "^^^^^^", + "#^# #^#", + " ^", + "^ ", + " - - - - ", + "? ? ? ? ? ? ????? ???? ??", + "\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0\u00b0", + "\\/ "*32, + " !"*42, + + /* --- Objekte: --- */ + __MASTER_OBJECT__, + clone_object("/a"), + new_lwobject("/a"), + + /* --- Closures: --- */ + (: :), + (: (: 1 :) :), + #'sizeof, + #'efun::input_to, + #'switch, + #'++, + symbol_function("create", load_object("/a")), + lambda( ({'x, 'y}), ({#'?, ({#'>, 'x, 'y}), 'x, 'y}) ), + unbound_lambda( ({'x}), ({#'==, 0, 'x}) ), + + /* --- Arrays: --- */ + ({ }), + ({ 0 }), + ({ "" }), + ({ ({ }) }), + ({ ([ ]) }), + allocate(3000), + + /* --- Mappings: --- */ + ([ ]), + ([ (: :) ]), + m_allocate(5,5), + mkmapping(allocate(30), allocate(30, ({})), allocate(30, (:1:))), + + /* --- Structs: --- */ + ( 'abc), + ( 'xyz, 10), + ( use_object_functions: 1, detect_end: 1), + ( source_encoding: "iso8859-1", target_encoding: "utf-8"), + + /* --- References: --- */ + &val, + &(str[3..7]), + + /* --- Coroutines: --- */ + async function void() {}, + + /* --- LPC types: --- */ + [int], + [string|bytes*], + [struct Foo], + }); + + while(remove_call_out("execute_file") != -1); + while(remove_call_out("do_replay") != -1); + while(remove_call_out("do_record") != -1); +} + +void check_arguments(closure efun, mixed *arguments) +{ + switch (to_string(efun)) + { + case "#'md5": + case "#'sha1": + if (sizeof(arguments) > 1 && intp(arguments[1]) && arguments[1] > 5) + arguments[1] = 3; + break; + case "#'hash": + if (sizeof(arguments) > 2 && intp(arguments[2]) && arguments[2] > 5) + arguments[2] = 3; + break; + } +} + +int execute_file(string file, int show) +{ + int line; + string str; + + if(!stringp(file) || file_size(file) <= 0) + { + msg("ERROR: Cannot execute '%s': Invalid file.\n", file); + return 0; + } + + msg("Executing: %s\n", file); + + for(line = 1; (str=read_file(file, line, 2)) && sizeof(str); line += 2) + { + mixed task = restore_value(str); + write_file("/crash/LAST_EXEC", str, 1); + +#ifdef TEST_EFUN + closure efun = task[0] < 0 ? TEST_EFUN : efuns[ task[0] ]; +#else + closure efun = efuns[ task[0] ]; +#endif + mixed arguments = map( task[1], (: values[$1] :)); + check_arguments(efun, arguments); + +#ifdef VERBOSE + msg("File: %s, Line: %d\n", file, line); +#endif + if (show) + msg(" %Q %Q\n", efun, arguments); + + catch(funcall(efun, arguments...)); + + rm("/crash/LAST_EXEC"); + } + + return 1; +} + +int generate_file(string file) +{ + if(!stringp(file) || !write_file(file, "", 1)) + { + msg("ERROR: Cannot write file: %s\n", file); + return 0; + } + + msg("Recording: %s\n", file); + + foreach (int i: CALLS) + { +#ifdef TEST_EFUN + int efun_index = -1; +#else + int efun_index = random(sizeof(efuns)-1); +#endif + int *value_index = ({}); + + if (i && !(i % REINIT_LENGTH)) + write_file(file, save_value( ({ sizeof(efuns)-1, ({}) }) )); + + foreach (int j: random(MAX_ARGS+1)) + value_index += ({ random(sizeof(values)) }); + + write_file(file, save_value( ({efun_index, value_index}) )); + } + + return 1; +} + +void replay(string file) +{ + execute_file(file, 1); + + __MASTER_OBJECT__->finish(1); +} + +void record(string file) +{ + if(generate_file(file)) + execute_file(file, 0); + + __MASTER_OBJECT__->finish(1); +} + +void create() +{ + mkdir("/crash"); + reset_all(); + +#ifdef REPLAY_FILE + string str = read_file(REPLAY_FILE); + if(str) + { + replay(REPLAY_FILE); + return; + } +#ifndef GENERATE_IF_NO_REPLAY_FILE + else + { + msg("Replay file not found: %s\n", REPLAY_FILE); + __MASTER_OBJECT__->finish(0); + return; + } +#endif +#endif + + int* ut = utime(); + record(sprintf("/crash/%d-%06d", ut[0], ut[1])); +} + +/* --- End of file. --- */ diff --git a/test/crasher/inc b/test/crasher/inc new file mode 120000 index 000000000..22dc542a6 --- /dev/null +++ b/test/crasher/inc @@ -0,0 +1 @@ +../inc \ No newline at end of file diff --git a/test/crasher/master.c b/test/crasher/master.c new file mode 100644 index 000000000..0dc535628 --- /dev/null +++ b/test/crasher/master.c @@ -0,0 +1,36 @@ +#include "/inc/base.inc" +#include "/inc/gc.inc" + +#include "/sys/configuration.h" + +void run_test() +{ + msg("\nRunning crasher...\n" + "------------------\n"); + + call_out("finish", 1); + load_object("crasher"); + return 0; +} + +void finish(int success) +{ + remove_call_out("finish"); + + if (success) + start_gc(#'shutdown); + else + shutdown(1); +} + +string *epilog(int eflag) +{ + set_driver_hook(H_RESET, "reset"); + set_driver_hook(H_CREATE_OB, "create"); + set_driver_hook(H_CREATE_CLONE, "create"); + + configure_driver(DC_MEMORY_LIMIT, ({ 1024*1024*1024, 2048*1024*1024 })); + + run_test(); + return 0; +} diff --git a/test/crasher/sys b/test/crasher/sys new file mode 120000 index 000000000..587910c70 --- /dev/null +++ b/test/crasher/sys @@ -0,0 +1 @@ +../sys \ No newline at end of file From 243ffdcdebdf5e1aba516ad173d1cef0df0ace74 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Mon, 25 Dec 2023 11:32:57 +0100 Subject: [PATCH 20/21] Fix crash when zeroing a closure during a GC When the garbage collection zeroes a closure, it should not continue processing that closure, because that would lead to a null pointer dereference. --- src/gcollect.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gcollect.c b/src/gcollect.c index 43c44b8b2..75f394b10 100644 --- a/src/gcollect.c +++ b/src/gcollect.c @@ -1977,6 +1977,7 @@ gc_count_ref_in_malloced_closure (svalue_t *csvp) else { put_number(csvp, 0); + return; } } else From 4b7c7d38af5ea434fb821be9d4d335529b05b0f2 Mon Sep 17 00:00:00 2001 From: Alexander Motzkau Date: Tue, 16 Jan 2024 23:43:27 +0100 Subject: [PATCH 21/21] Fix non-virtual variable lookup in compile_string() When use_object_variables is true and the object has virtual as well as non-virtual variables, the access to the non-virtual variables by compile_string() was off by the number of virtual variables. --- src/prolang.y | 13 +++++-------- test/t-inherit/master.c | 27 ++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/prolang.y b/src/prolang.y index b6d95abf1..a98b1a332 100644 --- a/src/prolang.y +++ b/src/prolang.y @@ -15267,10 +15267,11 @@ expr4: i = LAMBDA_VARIABLE(i).object_index; varp = prog->variables+i; - if (i < prog->num_virtual_variables) - i |= VIRTUAL_VAR_TAG; - else - i -= prog->num_virtual_variables; + /* No special handling for virtual variables required + * here, as lambdas will always be executed with + * variable_index_offset = 0, thus F_IDENTIFIER will + * also serve virtual variables. + */ } else { @@ -15635,10 +15636,6 @@ name_lvalue: i = LAMBDA_VARIABLE(i).object_index; varp = prog->variables+i; - if (i < prog->num_virtual_variables) - i |= VIRTUAL_VAR_TAG; - else - i -= prog->num_virtual_variables; } else { diff --git a/test/t-inherit/master.c b/test/t-inherit/master.c index 7f40820db..477ef45b6 100644 --- a/test/t-inherit/master.c +++ b/test/t-inherit/master.c @@ -14,13 +14,14 @@ void run_test() { object ob; - closure cl_symbol_variable; + closure cl_symbol_variable, cl_compile_string; msg("\nRunning test for runtime inherit handling:\n" "------------------------------------------\n"); ob = load_object("main"); cl_symbol_variable = bind_lambda(#'symbol_variable, ob); + cl_compile_string = bind_lambda(#'compile_string, ob); run_array( ({ @@ -72,6 +73,30 @@ void run_test() return funcall(funcall(cl_symbol_variable, "virtual2_var")) == "virtual2"; } }), + ({ "Global variable access via compile_string() of main", 0, + function int() + { + return funcall(funcall(cl_compile_string, 0, "main_var", ( use_object_variables: 1))) == "main"; + } + }), + ({ "Global variable access via compile_string() of regular", 0, + function int() + { + return funcall(funcall(cl_compile_string, 0, "regular_var", ( use_object_variables: 1))) == "regular"; + } + }), + ({ "Global variable access via compile_string() of virtual1", 0, + function int() + { + return funcall(funcall(cl_compile_string, 0, "virtual1_var", ( use_object_variables: 1))) == "virtual1"; + } + }), + ({ "Global variable access via compile_string() of virtual2", 0, + function int() + { + return funcall(funcall(cl_compile_string, 0, "virtual2_var", ( use_object_variables: 1))) == "virtual2"; + } + }), }), function void(int errors) { shutdown(errors);