pidl: Change PyGetSetDef in generated python bindings to use C99 initialisers
[obnox/samba/samba-obnox.git] / pidl / lib / Parse / Pidl / Samba4 / Python.pm
index ff68e7a3d9cce70541baeadc0b5cae479939cb3b..f2cb5387751dc0a4acfc708ef6e15835c06d1b75 100644 (file)
@@ -10,7 +10,7 @@ use Exporter;
 
 use strict;
 use Parse::Pidl qw(warning fatal error);
-use Parse::Pidl::Typelist qw(hasType resolveType getType mapTypeName expandAlias);
+use Parse::Pidl::Typelist qw(hasType resolveType getType mapTypeName expandAlias bitmap_type_fn enum_type_fn);
 use Parse::Pidl::Util qw(has_property ParseExpr unmake_str);
 use Parse::Pidl::NDR qw(GetPrevLevel GetNextLevel ContainsDeferred ContainsPipe is_charset_array);
 use Parse::Pidl::CUtil qw(get_value_of get_pointer_to);
@@ -237,9 +237,16 @@ sub PythonStruct($$$$$$)
                $self->pidl("static PyGetSetDef ".$getsetters."[] = {");
                $self->indent;
                foreach my $e (@{$d->{ELEMENTS}}) {
-                       $self->pidl("{ discard_const_p(char, \"$e->{NAME}\"), py_$name\_get_$e->{NAME}, py_$name\_set_$e->{NAME} },");
+                       $self->pidl("{");
+                       $self->indent;
+                       $self->pidl(".name = discard_const_p(char, \"$e->{NAME}\"),");
+                       $self->pidl(".get = py_$name\_get_$e->{NAME},");
+                       $self->pidl(".set = py_$name\_set_$e->{NAME},");
+                       $self->pidl(".doc = discard_const_p(char, \"PIDL-generated element $e->{NAME}\")");
+                       $self->deindent;
+                       $self->pidl("},");
                }
-               $self->pidl("{ NULL }");
+               $self->pidl("{ .name = NULL }");
                $self->deindent;
                $self->pidl("};");
                $self->pidl("");
@@ -282,7 +289,7 @@ sub PythonStruct($$$$$$)
                $self->indent;
                $self->pidl("$cname *object = ($cname *)pytalloc_get_ptr(py_obj);");
                $self->pidl("DATA_BLOB blob;");
-               $self->pidl("int blob_length = 0;");
+               $self->pidl("Py_ssize_t blob_length = 0;");
                $self->pidl("enum ndr_err_code err;");
                $self->pidl("const char * const kwnames[] = { \"data_blob\", \"allow_remaining\", NULL };");
                $self->pidl("PyObject *allow_remaining_obj = NULL;");
@@ -953,14 +960,58 @@ sub ConvertObjectFromPythonData($$$$$$;$)
                $actual_ctype = $actual_ctype->{DATA};
        }
 
-       if ($actual_ctype->{TYPE} eq "ENUM" or $actual_ctype->{TYPE} eq "BITMAP") {
+       # We need to cover ENUMs, BITMAPS and SCALAR values here, as
+       # all could otherwise be assigned invalid integer values
+       my $ctype_alias = "";
+       my $uint_max = "";
+       if ($actual_ctype->{TYPE} eq "ENUM") {
+               # Importantly, ENUM values are unsigned in pidl, and
+               # typically map to uint32
+               $ctype_alias = enum_type_fn($actual_ctype);
+       } elsif ($actual_ctype->{TYPE} eq "BITMAP") {
+               $ctype_alias = bitmap_type_fn($actual_ctype);
+       } elsif ($actual_ctype->{TYPE} eq "SCALAR") {
+               $ctype_alias = expandAlias($actual_ctype->{NAME});
+       }
+
+       # This is the unsigned Python Integer -> C integer validation
+       # case.  The signed case is below.
+       if ($ctype_alias  =~ /^(uint[0-9]*|hyper|udlong|udlongr
+                                |NTTIME_hyper|NTTIME|NTTIME_1sec
+                                |uid_t|gid_t)$/x) {
+               $self->pidl("{");
+               $self->indent;
+               $self->pidl("const unsigned long long uint_max = ndr_sizeof2uintmax(sizeof($target));");
                $self->pidl("if (PyLong_Check($cvar)) {");
                $self->indent;
-               $self->pidl("$target = PyLong_AsLongLong($cvar);");
+               $self->pidl("unsigned long long test_var;");
+               $self->pidl("test_var = PyLong_AsUnsignedLongLong($cvar);");
+               $self->pidl("if (PyErr_Occurred() != NULL) {");
+               $self->indent;
+               $self->pidl($fail);
+               $self->deindent;
+               $self->pidl("}");
+               $self->pidl("if (test_var > uint_max) {");
+               $self->indent;
+               $self->pidl("PyErr_Format(PyExc_OverflowError, \"Expected type %s or %s within range 0 - %llu, got %llu\",\\");
+               $self->pidl("  PyInt_Type.tp_name, PyLong_Type.tp_name, uint_max, test_var);");
+               $self->pidl($fail);
+               $self->deindent;
+               $self->pidl("}");
+               $self->pidl("$target = test_var;");
                $self->deindent;
                $self->pidl("} else if (PyInt_Check($cvar)) {");
                $self->indent;
-               $self->pidl("$target = PyInt_AsLong($cvar);");
+               $self->pidl("long test_var;");
+               $self->pidl("test_var = PyInt_AsLong($cvar);");
+               $self->pidl("if (test_var < 0 || test_var > uint_max) {");
+               $self->indent;
+               $self->pidl("PyErr_Format(PyExc_OverflowError, \"Expected type %s or %s within range 0 - %llu, got %ld\",\\");
+               $self->pidl("  PyInt_Type.tp_name, PyLong_Type.tp_name, uint_max, test_var);");
+               $self->pidl($fail);
+               $self->deindent;
+               $self->pidl("}");
+               $self->pidl("$target = test_var;");
                $self->deindent;
                $self->pidl("} else {");
                $self->indent;
@@ -969,34 +1020,61 @@ sub ConvertObjectFromPythonData($$$$$$;$)
                $self->pidl($fail);
                $self->deindent;
                $self->pidl("}");
+               $self->deindent;
+               $self->pidl("}");
                return;
        }
-       if ($actual_ctype->{TYPE} eq "SCALAR" ) {
-               if (expandAlias($actual_ctype->{NAME}) =~ /^(u?int64|hyper|dlong|udlong|udlongr
-                                                           |NTTIME_hyper|NTTIME|NTTIME_1sec
-                                                           |uid_t|gid_t)$/x) {
-                       $self->pidl("if (PyLong_Check($cvar)) {");
-                       $self->indent;
-                       $self->pidl("$target = PyLong_AsLongLong($cvar);");
-                       $self->deindent;
-                       $self->pidl("} else if (PyInt_Check($cvar)) {");
-                       $self->indent;
-                       $self->pidl("$target = PyInt_AsLong($cvar);");
-                       $self->deindent;
-                       $self->pidl("} else {");
-                       $self->indent;
-                       $self->pidl("PyErr_Format(PyExc_TypeError, \"Expected type %s or %s\",\\");
-                       $self->pidl("  PyInt_Type.tp_name, PyLong_Type.tp_name);");
-                       $self->pidl($fail);
-                       $self->deindent;
-                       $self->pidl("}");
-                       return;
-               }
-               if (expandAlias($actual_ctype->{NAME}) =~ /^(char|u?int[0-9]*|time_t)$/) {
-                       $self->pidl("PY_CHECK_TYPE(&PyInt_Type, $cvar, $fail);");
-                       $self->pidl("$target = PyInt_AsLong($cvar);");
-                       return;
-               }
+
+       # Confirm the signed python integer fits in the C type
+       # correctly.  It is subtly different from the unsigned case
+       # above, so while it looks like a duplicate, it is not
+       # actually a duplicate.
+       if ($ctype_alias  =~ /^(dlong|char|int[0-9]*|time_t)$/x) {
+               $self->pidl("{");
+               $self->indent;
+               $self->pidl("const long long int_max = ndr_sizeof2intmax(sizeof($target));");
+               $self->pidl("const long long int_min = -int_max - 1;");
+               $self->pidl("if (PyLong_Check($cvar)) {");
+               $self->indent;
+               $self->pidl("long long test_var;");
+               $self->pidl("test_var = PyLong_AsLongLong($cvar);");
+               $self->pidl("if (PyErr_Occurred() != NULL) {");
+               $self->indent;
+               $self->pidl($fail);
+               $self->deindent;
+               $self->pidl("}");
+               $self->pidl("if (test_var < int_min || test_var > int_max) {");
+               $self->indent;
+               $self->pidl("PyErr_Format(PyExc_OverflowError, \"Expected type %s or %s within range %lld - %lld, got %lld\",\\");
+               $self->pidl("  PyInt_Type.tp_name, PyLong_Type.tp_name, int_min, int_max, test_var);");
+               $self->pidl($fail);
+               $self->deindent;
+               $self->pidl("}");
+               $self->pidl("$target = test_var;");
+               $self->deindent;
+               $self->pidl("} else if (PyInt_Check($cvar)) {");
+               $self->indent;
+               $self->pidl("long test_var;");
+               $self->pidl("test_var = PyInt_AsLong($cvar);");
+               $self->pidl("if (test_var < int_min || test_var > int_max) {");
+               $self->indent;
+               $self->pidl("PyErr_Format(PyExc_OverflowError, \"Expected type %s or %s within range %lld - %lld, got %ld\",\\");
+               $self->pidl("  PyInt_Type.tp_name, PyLong_Type.tp_name, int_min, int_max, test_var);");
+               $self->pidl($fail);
+               $self->deindent;
+               $self->pidl("}");
+               $self->pidl("$target = test_var;");
+               $self->deindent;
+               $self->pidl("} else {");
+               $self->indent;
+               $self->pidl("PyErr_Format(PyExc_TypeError, \"Expected type %s or %s\",\\");
+               $self->pidl("  PyInt_Type.tp_name, PyLong_Type.tp_name);");
+               $self->pidl($fail);
+               $self->deindent;
+               $self->pidl("}");
+               $self->deindent;
+               $self->pidl("}");
+               return;
        }
 
        if ($actual_ctype->{TYPE} eq "STRUCT" or $actual_ctype->{TYPE} eq "INTERFACE") {
@@ -1152,6 +1230,13 @@ sub ConvertObjectFromPythonLevel($$$$$$$$)
                                $self->pidl("$var_name = talloc_array_ptrtype($mem_ctx, $var_name, PyList_GET_SIZE($py_var));");
                                $self->pidl("if (!$var_name) { $fail; }");
                                $self->pidl("talloc_set_name_const($var_name, \"ARRAY: $var_name\");");
+                       } else {
+                               $self->pidl("if (ARRAY_SIZE($var_name) != PyList_GET_SIZE($py_var)) {");
+                               $self->indent;
+                               $self->pidl("PyErr_Format(PyExc_TypeError, \"Expected list of type %s, length %zu, got %zd\", Py_TYPE($py_var)->tp_name, ARRAY_SIZE($var_name),  PyList_GET_SIZE($py_var));");
+                               $self->pidl("$fail");
+                               $self->deindent;
+                               $self->pidl("}");
                        }
                        $self->pidl("for ($counter = 0; $counter < PyList_GET_SIZE($py_var); $counter++) {");
                        $self->indent;
@@ -1202,17 +1287,29 @@ sub ConvertScalarToPython($$$)
        $ctypename = expandAlias($ctypename);
 
        if ($ctypename =~ /^(int64|dlong)$/) {
-               return "PyLong_FromLongLong($cvar)";
+               return "ndr_PyLong_FromLongLong($cvar)";
        }
 
-       if ($ctypename =~ /^(uint64|hyper|udlong|udlongr|NTTIME_hyper|NTTIME|NTTIME_1sec|uid_t|gid_t)$/) {
-               return "PyLong_FromUnsignedLongLong($cvar)";
+       if ($ctypename =~ /^(uint64|hyper|NTTIME_hyper|NTTIME|NTTIME_1sec|udlong|udlongr|uid_t|gid_t)$/) {
+               return "ndr_PyLong_FromUnsignedLongLong($cvar)";
        }
 
-       if ($ctypename =~ /^(char|u?int[0-9]*|time_t)$/) {
+       if ($ctypename =~ /^(char|int|int8|int16|int32|time_t)$/) {
                return "PyInt_FromLong($cvar)";
        }
 
+       # Needed to ensure unsigned values in a 32 or 16 bit enum is
+       # cast correctly to a uint32_t, not sign extended to a a
+       # possibly 64 bit unsigned long.  (enums are signed in C,
+       # unsigned in NDR)
+       if ($ctypename =~ /^(uint32|uint3264)$/) {
+               return "ndr_PyLong_FromUnsignedLongLong((uint32_t)$cvar)";
+       }
+
+       if ($ctypename =~ /^(uint|uint8|uint16|uint1632)$/) {
+               return "PyInt_FromLong((uint16_t)$cvar)";
+       }
+
        if ($ctypename eq "DATA_BLOB") {
                return "PyString_FromStringAndSize((char *)($cvar).data, ($cvar).length)";
        }
@@ -1400,6 +1497,7 @@ sub Parse($$$$$)
 
     $self->pidl_hdr("
 /* Python wrapper functions auto-generated by pidl */
+#define PY_SSIZE_T_CLEAN 1 /* We use Py_ssize_t for PyArg_ParseTupleAndKeywords */
 #include <Python.h>
 #include \"includes.h\"
 #include <pytalloc.h>
@@ -1408,6 +1506,61 @@ sub Parse($$$$$)
 #include \"$hdr\"
 #include \"$ndr_hdr\"
 
+/*
+ * These functions are here to ensure they can be optomised out by
+ * the compiler based on the constant input values
+ */
+
+static inline unsigned long long ndr_sizeof2uintmax(size_t var_size)
+{
+       switch (var_size) {
+       case 8:
+               return UINT64_MAX;
+       case 4:
+               return UINT32_MAX;
+       case 2:
+               return UINT16_MAX;
+       case 1:
+               return UINT8_MAX;
+       }
+
+       return 0;
+}
+
+static inline long long ndr_sizeof2intmax(size_t var_size)
+{
+       switch (var_size) {
+       case 8:
+               return INT64_MAX;
+       case 4:
+               return INT32_MAX;
+       case 2:
+               return INT16_MAX;
+       case 1:
+               return INT8_MAX;
+       }
+
+       return 0;
+}
+
+static inline PyObject *ndr_PyLong_FromLongLong(long long v)
+{
+       if (v > LONG_MAX || v < LONG_MIN) {
+               return PyLong_FromLongLong(v);
+       } else {
+               return PyInt_FromLong(v);
+       }
+}
+
+static inline PyObject *ndr_PyLong_FromUnsignedLongLong(unsigned long long v)
+{
+       if (v > LONG_MAX) {
+               return PyLong_FromUnsignedLongLong(v);
+       } else {
+               return PyInt_FromLong(v);
+       }
+}
+
 ");
 
        foreach my $x (@$ndr) {
@@ -1487,7 +1640,7 @@ sub Parse($$$$$)
                my $py_obj;
                my ($ctype, $cvar) = @{$h->{'val'}};
                if ($cvar =~ /^[0-9]+$/ or $cvar =~ /^0x[0-9a-fA-F]+$/) {
-                       $py_obj = "PyInt_FromLong($cvar)";
+                       $py_obj = "ndr_PyLong_FromUnsignedLongLong($cvar)";
                } elsif ($cvar =~ /^".*"$/) {
                        $py_obj = "PyString_FromString($cvar)";
                } else {