[gjs/wip/gcampax/70-arg-cache: 4/5] arg-cache: extend to handle interface types too



commit 42651a904745f5747f2f193e90ea87fab73af718
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Mon Apr 22 18:28:52 2013 +0200

    arg-cache: extend to handle interface types too
    
    Handle enums, objects and boxed types through the argument cache
    rather than using the generic marshaller.
    
    (Philip Chimento: rebased and fixed coding style, and added stubs for
    GParamSpec and GdkAtom.)

 gi/arg-cache.cpp | 423 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gi/arg-cache.h   |  16 +++
 2 files changed, 439 insertions(+)
---
diff --git a/gi/arg-cache.cpp b/gi/arg-cache.cpp
index 7c221911..dde44ce2 100644
--- a/gi/arg-cache.cpp
+++ b/gi/arg-cache.cpp
@@ -23,6 +23,7 @@
 
 #include <config.h>
 
+#include <inttypes.h>
 #include <string.h>
 
 #include <girepository.h>
@@ -37,8 +38,15 @@
 
 #include "gi/arg-cache.h"
 #include "gi/arg.h"
+#include "gi/boxed.h"
+#include "gi/foreign.h"
 #include "gi/function.h"
+#include "gi/gerror.h"
 #include "gi/gtype.h"
+#include "gi/object.h"
+#include "gi/union.h"
+#include "gi/value.h"
+#include "gjs/byteArray.h"
 #include "gjs/jsapi-util.h"
 
 // The global entry point for any invocations of GDestroyNotify; look up the
@@ -126,6 +134,18 @@ static bool report_primitive_type_mismatch(JSContext* cx,
     return false;
 }
 
+GJS_JSAPI_RETURN_CONVENTION
+static bool report_object_primitive_type_mismatch(JSContext* cx,
+                                                  GjsArgumentCache* self,
+                                                  JS::Value value,
+                                                  GType expected) {
+    gjs_throw(cx,
+              "Expected an object of type %s for argument '%s' but got type %s",
+              g_type_name(expected), self->arg_name,
+              JS::InformalValueTypeName(value));
+    return false;
+}
+
 GJS_JSAPI_RETURN_CONVENTION
 static bool report_out_of_range(JSContext* cx, GjsArgumentCache* self,
                                 GITypeTag tag) {
@@ -457,6 +477,214 @@ static bool gjs_marshal_string_in_in(JSContext* cx, GjsArgumentCache* self,
     return true;
 }
 
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_enum_in_in(JSContext* cx, GjsArgumentCache* self,
+                                   GjsFunctionCallState*, GIArgument* arg,
+                                   JS::HandleValue value) {
+    int64_t number;
+    if (!JS::ToInt64(cx, value, &number))
+        return false;
+
+    if (number > self->contents.enum_type.enum_max ||
+        number < self->contents.enum_type.enum_min) {
+        gjs_throw(cx, "%" PRId64 " is not a valid value for enum argument %s",
+                  number, self->arg_name);
+        return false;
+    }
+
+    if (self->contents.enum_type.enum_max <= G_MAXINT32)
+        arg->v_int = number;
+    else if (self->contents.enum_type.enum_max <= G_MAXUINT32)
+        arg->v_uint = number;
+    else
+        arg->v_int64 = number;
+
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_flags_in_in(JSContext* cx, GjsArgumentCache* self,
+                                    GjsFunctionCallState*, GIArgument* arg,
+                                    JS::HandleValue value) {
+    int64_t number;
+    if (!JS::ToInt64(cx, value, &number))
+        return false;
+
+    if ((uint64_t(number) & self->contents.flags_mask) != uint64_t(number)) {
+        gjs_throw(cx, "%" PRId64 " is not a valid value for flags argument %s",
+                  number, self->arg_name);
+        return false;
+    }
+
+    if (self->contents.flags_mask <= G_MAXUINT32)
+        arg->v_uint = number;
+    else
+        arg->v_uint64 = number;
+
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_foreign_in_in(JSContext* cx, GjsArgumentCache* self,
+                                      GjsFunctionCallState*, GIArgument* arg,
+                                      JS::HandleValue value) {
+    GIStructInfo* foreign_info = g_type_info_get_interface(&self->type_info);
+    self->contents.tmp_foreign_info = foreign_info;
+    return gjs_struct_foreign_convert_to_g_argument(
+        cx, value, foreign_info, self->arg_name, GJS_ARGUMENT_ARGUMENT,
+        self->transfer, self->nullable, arg);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_gvalue_in_in(JSContext* cx, GjsArgumentCache*,
+                                     GjsFunctionCallState*, GIArgument* arg,
+                                     JS::HandleValue value) {
+    GValue gvalue = G_VALUE_INIT;
+
+    if (!gjs_value_to_g_value(cx, value, &gvalue))
+        return false;
+
+    arg->v_pointer = g_boxed_copy(G_TYPE_VALUE, &gvalue);
+
+    g_value_unset(&gvalue);
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_boxed_in_in(JSContext* cx, GjsArgumentCache* self,
+                                    GjsFunctionCallState*, GIArgument* arg,
+                                    JS::HandleValue value) {
+    if (value.isNull()) {
+        if (!self->nullable)
+            return report_invalid_null(cx, self);
+
+        arg->v_pointer = nullptr;
+        return true;
+    }
+
+    GType gtype = self->contents.object.gtype;
+
+    if (!value.isObject())
+        return report_object_primitive_type_mismatch(cx, self, value, gtype);
+
+    JS::RootedObject object(cx, &value.toObject());
+    if (gtype == G_TYPE_ERROR) {
+        return ErrorBase::transfer_to_gi_argument(
+            cx, object, arg, GI_DIRECTION_IN, self->transfer);
+    }
+
+    return BoxedBase::transfer_to_gi_argument(cx, object, arg, GI_DIRECTION_IN,
+                                              self->transfer, gtype,
+                                              self->contents.object.info);
+}
+
+// Unions include ClutterEvent and GdkEvent, which occur fairly often in an
+// interactive application, so they're worth a special case in a different
+// virtual function.
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_union_in_in(JSContext* cx, GjsArgumentCache* self,
+                                    GjsFunctionCallState*, GIArgument* arg,
+                                    JS::HandleValue value) {
+    if (value.isNull()) {
+        if (!self->nullable)
+            return report_invalid_null(cx, self);
+
+        arg->v_pointer = nullptr;
+        return true;
+    }
+
+    GType gtype = self->contents.object.gtype;
+    g_assert(gtype != G_TYPE_NONE);
+
+    if (!value.isObject())
+        return report_object_primitive_type_mismatch(cx, self, value, gtype);
+
+    JS::RootedObject object(cx, &value.toObject());
+    return UnionBase::transfer_to_gi_argument(cx, object, arg, GI_DIRECTION_IN,
+                                              self->transfer, gtype,
+                                              self->contents.object.info);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_gclosure_in_in(JSContext* cx, GjsArgumentCache* self,
+                                       GjsFunctionCallState*, GIArgument* arg,
+                                       JS::HandleValue value) {
+    if (value.isNull()) {
+        if (!self->nullable)
+            return report_invalid_null(cx, self);
+
+        arg->v_pointer = nullptr;
+        return true;
+    }
+
+    if (!(JS_TypeOfValue(cx, value) == JSTYPE_FUNCTION))
+        return report_primitive_type_mismatch(cx, self, value, JSTYPE_FUNCTION);
+
+    JS::RootedFunction func(cx, JS_GetObjectFunction(&value.toObject()));
+    GClosure* closure = gjs_closure_new_marshaled(cx, func, "boxed");
+    arg->v_pointer = closure;
+    g_closure_ref(closure);
+    g_closure_sink(closure);
+
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_gbytes_in_in(JSContext* cx, GjsArgumentCache* self,
+                                     GjsFunctionCallState*, GIArgument* arg,
+                                     JS::HandleValue value) {
+    if (value.isNull()) {
+        if (!self->nullable)
+            return report_invalid_null(cx, self);
+
+        arg->v_pointer = nullptr;
+        return true;
+    }
+
+    if (!value.isObject())
+        return report_object_primitive_type_mismatch(cx, self, value,
+                                                     G_TYPE_BYTES);
+
+    JS::RootedObject object(cx, &value.toObject());
+    if (JS_IsUint8Array(object)) {
+        arg->v_pointer = gjs_byte_array_get_bytes(object);
+    } else {
+        if (!BoxedBase::transfer_to_gi_argument(
+                cx, object, arg, GI_DIRECTION_IN, self->transfer /* FIXME? */,
+                G_TYPE_BYTES, self->contents.object.info))
+            return false;
+
+        // The bytearray path is taking an extra ref irrespective of transfer
+        // ownership, so we need to do the same here.
+        g_bytes_ref(static_cast<GBytes*>(arg->v_pointer));
+    }
+
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_object_in_in(JSContext* cx, GjsArgumentCache* self,
+                                     GjsFunctionCallState*, GIArgument* arg,
+                                     JS::HandleValue value) {
+    if (value.isNull()) {
+        if (!self->nullable)
+            return report_invalid_null(cx, self);
+
+        arg->v_pointer = nullptr;
+        return true;
+    }
+
+    GType gtype = self->contents.object.gtype;
+    g_assert(gtype != G_TYPE_NONE);
+
+    if (!value.isObject())
+        return report_object_primitive_type_mismatch(cx, self, value, gtype);
+
+    JS::RootedObject object(cx, &value.toObject());
+    return ObjectBase::transfer_to_gi_argument(cx, object, arg, GI_DIRECTION_IN,
+                                               self->transfer, gtype);
+}
+
 GJS_JSAPI_RETURN_CONVENTION
 static bool gjs_marshal_skipped_out(JSContext*, GjsArgumentCache*,
                                     GjsFunctionCallState*, GIArgument*,
@@ -621,6 +849,34 @@ static bool gjs_marshal_string_in_release(JSContext*, GjsArgumentCache*,
     return true;
 }
 
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_foreign_in_release(JSContext* cx,
+                                           GjsArgumentCache* self,
+                                           GjsFunctionCallState*,
+                                           GIArgument* in_arg,
+                                           GIArgument* out_arg G_GNUC_UNUSED) {
+    bool ok = true;
+
+    if (self->transfer == GI_TRANSFER_NOTHING)
+        ok = gjs_struct_foreign_release_g_argument(
+            cx, self->transfer, self->contents.tmp_foreign_info, in_arg);
+
+    g_base_info_unref(self->contents.tmp_foreign_info);
+    return ok;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_boxed_in_release(JSContext*, GjsArgumentCache* self,
+                                         GjsFunctionCallState*,
+                                         GIArgument* in_arg,
+                                         GIArgument* out_arg G_GNUC_UNUSED) {
+    GType gtype = self->contents.object.gtype;
+    g_assert(g_type_is_a(gtype, G_TYPE_BOXED));
+
+    g_boxed_free(gtype, in_arg->v_pointer);
+    return true;
+}
+
 static inline void gjs_arg_cache_set_skip_all(GjsArgumentCache* self) {
     self->marshal_in = gjs_marshal_skipped_in;
     self->marshal_out = gjs_marshal_skipped_out;
@@ -685,6 +941,171 @@ bool gjs_arg_cache_build_return(GjsArgumentCache* self,
     return true;
 }
 
+static void gjs_arg_cache_build_enum_bounds(GjsArgumentCache* self,
+                                            GIEnumInfo* enum_info) {
+    int64_t min = G_MAXINT64;
+    int64_t max = G_MININT64;
+    int n = g_enum_info_get_n_values(enum_info);
+    for (int i = 0; i < n; i++) {
+        GIValueInfo* value_info = g_enum_info_get_value(enum_info, i);
+        int64_t value = g_value_info_get_value(value_info);
+
+        if (value > max)
+            max = value;
+        if (value < min)
+            min = value;
+
+        g_base_info_unref(value_info);
+    }
+
+    self->contents.enum_type.enum_min = min;
+    self->contents.enum_type.enum_max = max;
+}
+
+static void gjs_arg_cache_build_flags_mask(GjsArgumentCache* self,
+                                           GIEnumInfo* enum_info) {
+    uint64_t mask = 0;
+    int n = g_enum_info_get_n_values(enum_info);
+    for (int i = 0; i < n; i++) {
+        GIValueInfo* value_info = g_enum_info_get_value(enum_info, i);
+        uint64_t value = uint64_t(g_value_info_get_value(value_info));
+        mask |= value;
+
+        g_base_info_unref(value_info);
+    }
+
+    self->contents.flags_mask = mask;
+}
+
+GJS_USE
+static inline bool is_gdk_atom(GIBaseInfo* info) {
+    return strcmp("Atom", g_base_info_get_name(info)) == 0 &&
+           strcmp("Gdk", g_base_info_get_namespace(info)) == 0;
+}
+
+static bool gjs_arg_cache_build_interface_in_arg(GjsArgumentCache* self) {
+    GjsAutoBaseInfo interface_info =
+        g_type_info_get_interface(&self->type_info);
+    GIInfoType interface_type = g_base_info_get_type(interface_info);
+
+    // We do some transfer magic later, so let's ensure we don't mess up.
+    // Should not happen in practice.
+    if (G_UNLIKELY(self->transfer == GI_TRANSFER_CONTAINER))
+        return false;
+
+    switch (interface_type) {
+        case GI_INFO_TYPE_ENUM:
+            gjs_arg_cache_build_enum_bounds(self, interface_info);
+            self->marshal_in = gjs_marshal_enum_in_in;
+            return true;
+
+        case GI_INFO_TYPE_FLAGS:
+            gjs_arg_cache_build_flags_mask(self, interface_info);
+            self->marshal_in = gjs_marshal_flags_in_in;
+            return true;
+
+        case GI_INFO_TYPE_STRUCT:
+            if (g_struct_info_is_foreign(interface_info)) {
+                self->marshal_in = gjs_marshal_foreign_in_in;
+                self->release = gjs_marshal_foreign_in_release;
+                return true;
+            }
+            // fall through
+        case GI_INFO_TYPE_BOXED:
+        case GI_INFO_TYPE_OBJECT:
+        case GI_INFO_TYPE_INTERFACE:
+        case GI_INFO_TYPE_UNION: {
+            GType gtype = g_registered_type_info_get_g_type(interface_info);
+            self->contents.object.gtype = gtype;
+            self->contents.object.info = interface_info;
+            g_base_info_ref(self->contents.object.info);
+
+            // Transfer handling is a bit complex here, because some of our _in
+            // marshallers know not to copy stuff if we don't need to.
+
+            if (gtype == G_TYPE_VALUE) {
+                self->marshal_in = gjs_marshal_gvalue_in_in;
+                if (self->transfer == GI_TRANSFER_NOTHING)
+                    self->release = gjs_marshal_boxed_in_release;
+                return true;
+            }
+
+            if (is_gdk_atom(interface_info)) {
+                // Fall back to the generic marshaller
+                self->marshal_in = gjs_marshal_generic_in_in;
+                self->release = gjs_marshal_generic_in_release;
+                return true;
+            }
+
+            if (gtype == G_TYPE_CLOSURE) {
+                self->marshal_in = gjs_marshal_gclosure_in_in;
+                if (self->transfer == GI_TRANSFER_NOTHING)
+                    self->release = gjs_marshal_boxed_in_release;
+                return true;
+            }
+
+            if (gtype == G_TYPE_BYTES) {
+                self->marshal_in = gjs_marshal_gbytes_in_in;
+                if (self->transfer == GI_TRANSFER_NOTHING)
+                    self->release = gjs_marshal_boxed_in_release;
+                return true;
+            }
+
+            if (g_type_is_a(gtype, G_TYPE_OBJECT) ||
+                g_type_is_a(gtype, G_TYPE_INTERFACE)) {
+                self->marshal_in = gjs_marshal_object_in_in;
+                // This is a smart marshaller, no release needed
+                return true;
+            }
+
+            if (g_type_is_a(gtype, G_TYPE_PARAM)) {
+                // Fall back to the generic marshaller
+                self->marshal_in = gjs_marshal_generic_in_in;
+                self->release = gjs_marshal_generic_in_release;
+                return true;
+            }
+
+            if (interface_type == GI_INFO_TYPE_UNION) {
+                if (gtype == G_TYPE_NONE)
+                    return false;  // Can't handle unions without a GType
+
+                self->marshal_in = gjs_marshal_union_in_in;
+                // This is a smart marshaller, no release needed
+                return true;
+            }
+
+            // generic boxed type
+            if (gtype == G_TYPE_NONE && self->transfer != GI_TRANSFER_NOTHING) {
+                // Can't transfer ownership of a structure type not
+                // registered as a boxed
+                return false;
+            }
+
+            self->marshal_in = gjs_marshal_boxed_in_in;
+            // This is a smart marshaller, no release needed
+            return true;
+        } break;
+
+        case GI_INFO_TYPE_INVALID:
+        case GI_INFO_TYPE_FUNCTION:
+        case GI_INFO_TYPE_CALLBACK:
+        case GI_INFO_TYPE_CONSTANT:
+        case GI_INFO_TYPE_INVALID_0:
+        case GI_INFO_TYPE_VALUE:
+        case GI_INFO_TYPE_SIGNAL:
+        case GI_INFO_TYPE_VFUNC:
+        case GI_INFO_TYPE_PROPERTY:
+        case GI_INFO_TYPE_FIELD:
+        case GI_INFO_TYPE_ARG:
+        case GI_INFO_TYPE_TYPE:
+        case GI_INFO_TYPE_UNRESOLVED:
+        default:
+            // Don't know how to handle this interface type (should not happen
+            // in practice, for typelibs emitted by g-ir-compiler)
+            return false;
+    }
+}
+
 GJS_JSAPI_RETURN_CONVENTION
 static bool gjs_arg_cache_build_normal_in_arg(GjsArgumentCache* self,
                                               GITypeTag tag) {
@@ -759,6 +1180,8 @@ static bool gjs_arg_cache_build_normal_in_arg(GjsArgumentCache* self,
             break;
 
         case GI_TYPE_TAG_INTERFACE:
+            return gjs_arg_cache_build_interface_in_arg(self);
+
         case GI_TYPE_TAG_ARRAY:
         case GI_TYPE_TAG_GLIST:
         case GI_TYPE_TAG_GSLIST:
diff --git a/gi/arg-cache.h b/gi/arg-cache.h
index c9d7c257..2944c75b 100644
--- a/gi/arg-cache.h
+++ b/gi/arg-cache.h
@@ -74,6 +74,22 @@ typedef struct _GjsArgumentCache {
             bool is_unsigned : 1;
         } number;
 
+        // boxed / union / GObject
+        struct {
+            GType gtype;
+            GIBaseInfo* info;
+        } object;
+
+        // foreign structures
+        GIStructInfo* tmp_foreign_info;
+
+        // enum / flags
+        struct {
+            int64_t enum_min;
+            int64_t enum_max;
+        } enum_type;
+        uint64_t flags_mask;
+
         // string / filename
         bool string_is_filename : 1;
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]