More work on arg unwrapping
authorBoris Zbarsky <bzbarsky@mit.edu>
Wed, 08 Feb 2012 00:11:36 -0500
changeset 86013 f1866b7282c9ff8f49341aa99f960d7bc5c6b71c
parent 86012 c22458a8e35d46f417c1b1938444c69f6d9fb2e5
child 86457 8f12170e8f206796778237432b641907610e95a4
push id93
push userbzbarsky@mozilla.com
push dateWed, 08 Feb 2012 05:11:48 +0000
milestone13.0a1
More work on arg unwrapping
dom/bindings/Codegen.py
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -386,27 +386,159 @@ class CGGetProtoObjectMethod(CGAbstractM
     ourProto = protoArray[id::%s] = CreateProtoObject(aCx, aGlobal);
   }
 
   /* ourProto might _still_ be null, but that's OK */
   return ourProto;""" % (self.descriptor.name, self.descriptor.name)
 
 builtinNames = {
     IDLType.Tags.bool: 'bool',
-    IDLType.Tags.uint8: 'uint8_t',
+    IDLType.Tags.int8: 'int8_t',
     IDLType.Tags.int16: 'int16_t',
     IDLType.Tags.int32: 'int32_t',
     IDLType.Tags.int64: 'int64_t',
+    IDLType.Tags.uint8: 'uint8_t',
     IDLType.Tags.uint16: 'uint16_t',
     IDLType.Tags.uint32: 'uint32_t',
     IDLType.Tags.uint64: 'uint64_t',
     IDLType.Tags.float: 'float',
     IDLType.Tags.double: 'double'
 }
 
+def getArgumentConversionTemplate(type):
+    if type.isInterface():
+        # XXXbz need more checking to figure out what to do.  In particular,
+        # need to know here how interface names map to internal types
+        return """
+  // XXXbz Need conversion for argument type '%s'""" % type
+
+    # XXXbz handle strings here eventually.  Need to figure
+    # out string behavior?  Also, how to detect them?
+#            return """
+#  xpc_qsDOMString arg%(index)d(cx, %(argVal)s, %(argPtr)s,
+#                        xpc_qsDOMString::eDefaultNullBehavior,
+#                        xpc_qsDOMString::eDefaultUndefinedBehavior);
+#  if (!arg%(index)d.IsValid()) {
+#    return false;
+#  }"""
+
+    if not type.isPrimitive():
+        return """
+  // XXXbz Need conversion for argument type '%s'""" % type
+
+    tag = type.tag()
+    if tag == IDLType.Tags.bool:
+        return ("  JSBool ${name};\n"
+                "  if (!JS_ValueToBoolean(cx, ${argVal}, &${name})) {\n"
+                "    return false;\n"
+                "}\n")
+    elif tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16,
+                 IDLType.Tags.int32, IDLType.Tags.uint32]:
+        # XXXbz need to add support for [EnforceRange] and [Clamp]
+        # The output of JS_ValueToECMAInt32 is determined as follows:
+        #   1) The value is converted to a double
+        #   2) Anything that's not a finite double returns 0
+        #   3) The double is rounded towards zero to the nearest integer
+        #   4) The resulting integer is reduced mod 2^32.  The output of this
+        #      operation is an integer in the range [0, 2^32).
+        #   5) If the resulting number is >= 2^31, 2^32 is subtracted from it.
+        #
+        # The result of all this is a number in the range [-2^31, 2^31)
+        #
+        # WebIDL conversions for the 8-bit, 16-bit, and 32-bit integer types
+        # are defined in the same way, except that step 4 uses reduction mod
+        # 2^8 and 2^16 for the 8-bit and 16-bit types respectively, and step 5
+        # is only done for the signed types.
+        #
+        # C/C++ define integer conversion semantics to unsigned types as taking
+        # your input integer mod (1 + largest value repreesntable in the
+        # unsigned type).  Since 2^32 is zero mod 2^8, 2^16, and 2^32,
+        # converting to the unsigned int of the relevant width will correctly
+        # perform step 4; in particular, the 2^32 possibly subtracted in step 5
+        # will become 0.
+        #
+        # Once we have step 4 done, we're just going to assume 2s-complement
+        # representation and cast directly to the type we really want.
+        #
+        # So we can cast directly for all unsigned types an for int32_t; for
+        # the smaller-width signed types we need to cast through the
+        # corresponding unsigned type.
+        if tag is IDLType.Tags.int8:
+            intermediate_type = "(uint8_t)"
+        elif tag is IDLType.Tags.int16:
+            intermediate_type = "(uint16_t)"
+        else:
+            intermediate_type = ""
+        return ("  int32_t ${name}_int32;\n"
+                "  if (!JS_ValueToECMAInt32(cx, ${argVal}, &${name}_int32)) {\n"
+                "    return false;\n"
+                "  }\n"
+                "  ${typeName} ${name} = (${typeName})%s${name}_int32;" %
+                intermediate_type)
+    elif tag is IDLType.Tags.int64:
+        # XXXbz this may not match what WebIDL says to do in terms of reducing
+        # mod 2^64.  Should we check?
+        return ("  PRInt64 ${name};\n"
+                "  if (!xpc_qsValueToInt64(cx, ${argVal}, &${name})) {\n"
+                "    return false;\n"
+                "  }")
+    elif tag is IDLType.Tags.uint64:
+        return ("  PRUint64 ${name};\n"
+                "  if (!xpc_qsValueToUint64(cx, ${argVal}, &${name})) {\n"
+                "    return false;\n"
+                "  }")
+    elif tag in [IDLType.Tags.float, IDLType.Tags.double]:
+        return ("  jsdouble ${name}_jsdouble;\n"
+                "  if (!JS_ValueToNumber(cx, ${argVal}, &${name}_jsdouble) {\n"
+                "    return false;\n"
+                "  }\n"
+                "  ${typeName} ${name} = (${typeName})${name}_jsdouble;")
+    else:
+            pass
+    return """
+  // XXXbz Need conversion for argument type '%s'""" % type
+
+class ArgumentConverter():
+    """
+    A class that takes an IDL argument object, its index in the
+    argument list, and the argv and argc strings and generates code to
+    unwrap the argument to the right native type.
+    """
+    def __init__(self, argument, index, argv, argc):
+        self.argument = argument
+        # XXXbz should optional jsval args get JSVAL_VOID? What about
+        # others?
+        self.replacementVariables = {
+            "index" : index,
+            "argc" : argc,
+            "argv" : argv,
+            "defaultValue" : "JSVAL_NULL",
+            "name" : "arg%d" % index
+            }
+        if argument.optional:
+            self.replacementVariables["argVal"] = string.Template(
+                "(${index} < ${argc} ? ${argv}[${index}] : ${defaultValue})"
+                ).substitute(self.replacementVariables)
+            self.replacementVariables["argPtr"] = string.Template(
+                "(${index} < ${argc} ? &${argv}[${index}] : NULL)"
+                ).substitute(self.replacementVariables)
+        else:
+            self.replacementVariables["argVal"] = string.Template(
+                "${argv}[${index}]"
+                ).substitute(self.replacementVariables)
+            self.replacementVariables["argPtr"] = (
+                "&" + self.replacementVariables["argVal"])
+        if argument.type.isPrimitive():
+            self.replacementVariables["typeName"] = builtinNames[argument.type.tag()]
+
+    def __str__(self):
+        return string.Template(
+            "\n" + getArgumentConversionTemplate(self.argument.type)
+            ).substitute(self.replacementVariables)
+
 def getWrapTemplateForType(type):
     if type.isVoid():
         return """
   ${jsvalRef} = JSVAL_VOID;
   return true;"""
 
     if type.isInterface():
         # Wrap the object
@@ -416,22 +548,26 @@ def getWrapTemplateForType(type):
 
     if not type.isPrimitive():
         return """
   // XXXbz need to learn to wrap other things
   return false;"""
 
     tag = type.tag()
     
-    if tag in [IDLType.Tags.uint8, IDLType.Tags.int16, IDLType.Tags.int32, IDLType.Tags.uint16]:
+    if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16,
+               IDLType.Tags.uint16, IDLType.Tags.int32]:
         return """
   ${jsvalRef} = INT_TO_JSVAL(int32_t(result));
   return true;"""
 
-    elif tag in [IDLType.Tags.int64, IDLType.Tags.uint64, IDLType.Tags.float, IDLType.Tags.double]:
+    elif tag in [IDLType.Tags.int64, IDLType.Tags.uint64, IDLType.Tags.float,
+                 IDLType.Tags.double]:
+        # XXXbz will cast to double do the "even significand" thing that webidl
+        # calls for for 64-bit ints?  Do we care?
         return """
   return JS_NewNumberValue(cx, double(result), ${jsvalPtr});"""
 
     elif tag == IDLType.Tags.uint32:
         return """
   ${jsvalRef} = UINT_TO_JSVAL(result);
   return true;"""
 
@@ -455,36 +591,47 @@ class PerSignatureCall():
        actual return value (e.g. this is an attribute setter) or an
        IDLType if there's an IDL type involved (including |void|).
     2) An argument list, which is allowed to be empty.
     3) A name of a native method to call.
 
     We also need to know whether this is a method or a getter/setter
     to do error reporting correctly.
     """
-    # XXXbz For now each entry in the argument list is an IDLType, but
-    # longer-term we may want to have ways of flagging things like
-    # JSContext* or optional_argc in there.
+    # XXXbz For now each entry in the argument list is either an
+    # IDLArgument or a FakeArgument, but longer-term we may want to
+    # have ways of flagging things like JSContext* or optional_argc in
+    # there.
     
     # XXXbz void methods have a signature with a isVoid() type object
     #       as first element.  Methods with no args have length-0 arg
     #       lists as second element in signaure.
     # XXXbz if isInterface() true on a type, type.inner is the interface object
     # XXXbz is isPrimitive() true on a type, then .tag() will return an
     #       IDLType.Tags value.  So you can compare
     #       type.tag() == IDLType.Tags.int8 or whatever.
     def __init__(self, returnType, arguments, nativeMethodName,
                  scriptableInterfaceAndName):
         self.returnType = returnType
         self.arguments = arguments
         self.nativeMethodName = nativeMethodName
         self.scriptableInterfaceAndName = scriptableInterfaceAndName
 
+    def getArgv(self):
+        assert(False) # Override me
+    def getArgc(self):
+        assert(False) # Override me
+    def getErrorReport(self):
+        assert(False) # Override me
+
     def unwrap_arguments(self):
-        return ""
+        args = [ArgumentConverter(self.arguments[i], i, self.getArgv(),
+                                  self.getArgc()) for
+                i in range(len(self.arguments))]
+        return "".join([str(arg) for arg in args])
 
     def generate_call(self):
         # XXXbz add provisions for infallible calls here?
         nativeArgs = ["arg" + str(i) for i in range(len(self.arguments))]
         # XXXbz arguments that have to go in outparams go here?
         nativeArgs.append("&rv")
         # XXXbz need to have full config to do this retval business right
         if self.returnType is None or self.returnType.isVoid():
@@ -519,36 +666,63 @@ class PerSignatureCall():
         return (self.unwrap_arguments() + self.generate_call() +
                 self.wrap_return_value())
 
 class PerSignatureMethodCall(PerSignatureCall):
     def __init__(self, returnType, arguments, nativeMethodName,
                  scriptableInterfaceAndName):
         PerSignatureCall.__init__(self, returnType, arguments, nativeMethodName,
                                   scriptableInterfaceAndName)
+    def getArgv(self):
+        return "argv" if len(self.arguments) > 0 else ""
+    def getArgc(self):
+        return "argc"
+    def unwrap_arguments(self):
+        requiredArgs = len(self.arguments)
+        while requiredArgs and self.arguments[requiredArgs-1].optional:
+            requiredArgs -= 1
+        argv = "jsval *argv = JS_ARGV(cx, vp);" if len(self.arguments) > 0 else ""
+        return ("""
+  // XXXbz is this the right place for this check?  Or should it be more
+  // up-front somewhere, not per-signature?
+  if (argc < %d) {
+    return xpc_qsThrow(cx, NS_ERROR_XPC_NOT_ENOUGH_ARGS);
+  }
+  %s""" % (requiredArgs, argv)) + PerSignatureCall.unwrap_arguments(self)
 
 class GetterSetterCall(PerSignatureCall):
     def __init__(self, returnType, arguments, nativeMethodName,
                  scriptableInterfaceAndName):
         PerSignatureCall.__init__(self, returnType, arguments, nativeMethodName,
                                   scriptableInterfaceAndName)
+    def getArgv(self):
+        return "vp"
 
 class GetterCall(GetterSetterCall):
     def __init__(self, returnType, nativeMethodName,
                  scriptableInterfaceAndName):
         GetterSetterCall.__init__(self, returnType, [], nativeMethodName,
                                   scriptableInterfaceAndName)
+    def getArgc(self):
+        return "0"
+
+class FakeArgument():
+    def __init__(self, type):
+        self.type = type
+        self.optional = False
 
 class SetterCall(GetterSetterCall):
     def __init__(self, argType, nativeMethodName, scriptableInterfaceAndName):
-        GetterSetterCall.__init__(self, None, [argType], nativeMethodName,
-                                  scriptableInterfaceAndName)
+        GetterSetterCall.__init__(self, None, [FakeArgument(argType)],
+                                  nativeMethodName, scriptableInterfaceAndName)
     def wrap_return_value(self):
         # We have no return value
         return "\n  return true;"
+    def getArgc(self):
+        return "1"
 
 class CGAbstractBindingMethod(CGAbstractStaticMethod):
     def __init__(self, descriptor, name, returnType, args):
         CGAbstractStaticMethod.__init__(self, descriptor, name,
                                         returnType, args)
     def definition_body(self):
         return (self.unwrap_this() + self.generate_code())