Bug 1474369 - Part 4: Add support for Sequence<T> types to xpidl and XPConnect, r=mccr8
authorNika Layzell <nika@thelayzells.com>
Tue, 10 Jul 2018 21:24:48 -0400
changeset 429473 4c78a885c77da5eb250e8fdeeda5b7270730a169
parent 429472 ff1530e9970a964991b9e9960d54dde48ed0c62f
child 429474 e28ef672bfd5dd2622fd55a1acc9863b7e401a4f
push id105907
push usernika@thelayzells.com
push dateTue, 31 Jul 2018 21:53:40 +0000
treeherdermozilla-inbound@69e4230dc38a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1474369
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1474369 - Part 4: Add support for Sequence<T> types to xpidl and XPConnect, r=mccr8 Summary: This patch adds support for the `Sequence<T>` type. This is largely a straightforward type propagation patch, but there are a few notable things: 1. We allow `[iid_is(x)] Sequence<nsQIResult>`, so Sequence can be Dependent. 2. `Sequence<T>` is reflected into C++ as a `nsTArray<T>`, which is different than WebIDL's `mozilla::dom::Sequence<T>` type. This decision was made for general ergonomics reasons, as `nsTArray<T>` is more prevailent throughout the codebase, and lengths in this case cannot be controlled by content, as XPConnect is only exposed to Chrome JS. 3. Owned pointers in `Sequence<T>` are not reflected as their owned counterparts. For example, `Sequence<nsISupports>` is reflected as `nsTArray<nsISupports*>` rather than `nsTArray<RefPtr<nsISupports>>`. This was done to avoid depending on `RefPtr<T>` and `T*` having the same in-memory representation, however if that is considered an acceptable dependency, it would be nice to support that. 4. We also don't reflect singly-owned pointers as their owned counterparts. For example, `nsTArray<nsIIDPtr>` would be reflected as `nsTArray<nsIID*>` rather than `nsTArray<mozilla::UniquePtr<nsIID>>`. If we are willing to depend on `mozilla::UniquePtr<T>`'s in-memory representation, we could also do this, however. 5. There are no restrictions on what types can appear inside of a `Sequence<T>` or what can appear inside an `[array] T`. We may want to add restrictions either at the xpidl level or in XPConnect. Depends On D2109 Reviewers: mccr8! Tags: #secure-revision Bug #: 1474369 Differential Revision: https://phabricator.services.mozilla.com/D2110
js/xpconnect/src/XPCConvert.cpp
js/xpconnect/src/XPCWrappedNative.cpp
xpcom/idl-parser/xpidl/jsonxpt.py
xpcom/idl-parser/xpidl/xpidl.py
xpcom/reflect/xptcall/xptcall.h
xpcom/reflect/xptinfo/xptcodegen.py
xpcom/reflect/xptinfo/xptinfo.h
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -384,16 +384,23 @@ XPCConvert::NativeData2JS(MutableHandleV
         d.setObject(*jsobj);
         return true;
     }
 
     case nsXPTType::T_ARRAY:
         return NativeArray2JS(d, *static_cast<const void* const*>(s),
                               type.ArrayElementType(), iid, arrlen, pErr);
 
+    case nsXPTType::T_SEQUENCE:
+    {
+        auto* sequence = static_cast<const xpt::detail::UntypedSequence*>(s);
+        return NativeArray2JS(d, sequence->Elements(), type.ArrayElementType(),
+                              iid, sequence->Length(), pErr);
+    }
+
     default:
         NS_ERROR("bad type");
         return false;
     }
     return true;
 }
 
 /***************************************************************************/
@@ -873,16 +880,37 @@ XPCConvert::JSData2Native(void* d, Handl
         if (!ok && *dest) {
             // An error occurred, free any allocated backing buffer.
             free(*dest);
             *dest = nullptr;
         }
         return ok;
     }
 
+    case nsXPTType::T_SEQUENCE:
+    {
+        auto* dest = (xpt::detail::UntypedSequence*)d;
+        const nsXPTType& elty = type.ArrayElementType();
+
+        bool ok = JSArray2Native(s, elty, iid, pErr, [&] (uint32_t* aLength) -> void* {
+            if (!dest->SetLength(elty, *aLength)) {
+                if (pErr)
+                    *pErr = NS_ERROR_OUT_OF_MEMORY;
+                return nullptr;
+            }
+            return dest->Elements();
+        });
+
+        if (!ok) {
+            // An error occurred, free any allocated backing buffer.
+            dest->Clear();
+        }
+        return ok;
+    }
+
     default:
         NS_ERROR("bad type");
         return false;
     }
     return true;
 }
 
 /***************************************************************************/
@@ -1580,16 +1608,29 @@ xpc::InnerCleanupValue(const nsXPTType& 
 
             for (uint32_t i = 0; i < aArrayLen; ++i) {
                 CleanupValue(elty, elty.ElementPtr(elements, i));
             }
             free(elements);
             break;
         }
 
+        // Sequence Type
+        case nsXPTType::T_SEQUENCE:
+        {
+            const nsXPTType& elty = aType.ArrayElementType();
+            auto* sequence = (xpt::detail::UntypedSequence*)aValue;
+
+            for (uint32_t i = 0; i < sequence->Length(); ++i) {
+                CleanupValue(elty, elty.ElementPtr(sequence->Elements(), i));
+            }
+            sequence->Clear();
+            break;
+        }
+
         // Clear the JS::Value to `undefined`
         case nsXPTType::T_JSVAL:
             ((JS::Value*)aValue)->setUndefined();
             break;
     }
 
     // Null out the pointer if we have it.
     if (aType.HasPointerRepr()) {
@@ -1615,14 +1656,18 @@ xpc::InitializeValue(const nsXPTType& aT
         case nsXPTType::T_DOMSTRING:
             new (aValue) nsString();
             break;
         case nsXPTType::T_CSTRING:
         case nsXPTType::T_UTF8STRING:
             new (aValue) nsCString();
             break;
 
+        case nsXPTType::T_SEQUENCE:
+            new (aValue) xpt::detail::UntypedSequence();
+            break;
+
         // The remaining types all have valid states where all bytes are '0'.
         default:
             memset(aValue, 0, aType.Stride());
             break;
     }
 }
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -1677,20 +1677,20 @@ static void
 TraceParam(JSTracer* aTrc, void* aVal, const nsXPTType& aType,
            uint32_t aArrayLen = 0)
 {
     if (aType.Tag() == nsXPTType::T_JSVAL) {
         JS::UnsafeTraceRoot(aTrc, (JS::Value*)aVal,
                             "XPCWrappedNative::CallMethod param");
     } else if (aType.Tag() == nsXPTType::T_SEQUENCE) {
         auto* sequence = (xpt::detail::UntypedSequence*)aVal;
+        const nsXPTType& elty = aType.ArrayElementType();
 
-        const nsXPTType& elty = aType.ArrayElementType();
         for (uint32_t i = 0; i < sequence->Length(); ++i) {
-            TraceParam(aTrc, sequence->ElementAt(elty, i), elty);
+            TraceParam(aTrc, elty.ElementPtr(sequence->Elements(), i), elty);
         }
     } else if (aType.Tag() == nsXPTType::T_ARRAY && *(void**)aVal) {
         const nsXPTType& elty = aType.ArrayElementType();
 
         for (uint32_t i = 0; i < aArrayLen; ++i) {
             TraceParam(aTrc, elty.ElementPtr(*(void**)aVal, i), elty);
         }
     }
--- a/xpcom/idl-parser/xpidl/jsonxpt.py
+++ b/xpcom/idl-parser/xpidl/jsonxpt.py
@@ -57,16 +57,24 @@ def get_type(type, calltype, iid_is=None
 
     if isinstance(type, xpidl.Builtin):
         ret = {'tag': TypeMap[type.name]}
         if type.name in ['string', 'wstring'] and size_is is not None:
             ret['tag'] += '_SIZE_IS'
             ret['size_is'] = size_is
         return ret
 
+    if isinstance(type, xpidl.Sequence):
+        # NB: For a Sequence<T> we pass down the iid_is to get the type of T.
+        #     This allows Arrays of InterfaceIs types to work.
+        return {
+            'tag': 'TD_SEQUENCE',
+            'element': get_type(type.type, calltype, iid_is),
+        }
+
     if isinstance(type, xpidl.Array):
         # NB: For an Array<T> we pass down the iid_is to get the type of T.
         #     This allows Arrays of InterfaceIs types to work.
         return {
             'tag': 'TD_ARRAY',
             'size_is': size_is,
             'element': get_type(type.type, calltype, iid_is),
         }
--- a/xpcom/idl-parser/xpidl/xpidl.py
+++ b/xpcom/idl-parser/xpidl/xpidl.py
@@ -317,23 +317,30 @@ class Include(object):
             parent.deps.extend(self.IDL.deps)
             return
 
         raise IDLError("File '%s' not found" % self.filename, self.location)
 
 
 class IDL(object):
     def __init__(self, productions):
+        self.hasSequence = False
         self.productions = productions
         self.deps = []
 
     def setName(self, object):
         self.namemap.set(object)
 
     def getName(self, id, location):
+        if id.name == 'Sequence':
+            if id.params is None or len(id.params) != 1:
+                raise IDLError("Sequence takes exactly 1 parameter", location)
+            self.hasSequence = True
+            return Sequence(self.getName(id.params[0], location), location)
+
         if id.params is not None:
             raise IDLError("Generic type '%s' unrecognized" % id.name, location)
 
         try:
             return self.namemap[id.name]
         except KeyError:
             raise IDLError("type '%s' not found" % id.name, location)
 
@@ -353,16 +360,18 @@ class IDL(object):
         self.webidlconfig = webidlconfig
         for p in self.productions:
             p.resolve(self)
 
     def includes(self):
         for p in self.productions:
             if p.kind == 'include':
                 yield p
+        if self.hasSequence:
+            yield Include("nsTArray.h", BuiltinLocation)
 
     def needsJSTypes(self):
         for p in self.productions:
             if p.kind == 'interface' and p.needsJSTypes():
                 return True
         return False
 
 
@@ -1233,16 +1242,46 @@ class Array(object):
                             '*' if 'out' in calltype else '')
 
     def rustType(self, calltype, const=False):
         return "%s%s%s" % ('*mut ' if 'out' in calltype else '',
                            '*const ' if const else '*mut ',
                            self.type.rustType('element'))
 
 
+class Sequence(object):
+    kind = 'sequence'
+
+    def __init__(self, type, location):
+        self.type = type
+        self.location = location
+
+    @property
+    def name(self):
+        return "Sequence<%s>" % self.type.name
+
+    def resolve(self, idl):
+        idl.getName(self.type, self.location)
+
+    def isScriptable(self):
+        return self.type.isScriptable()
+
+    def nativeType(self, calltype):
+        base = 'nsTArray<%s>' % self.type.nativeType('element')
+        if 'out' in calltype:
+            return '%s& ' % base
+        elif 'in' == calltype:
+            return 'const %s& ' % base
+        else:
+            return base
+
+    def rustType(self, calltype):
+        raise RustNoncompat("Sequence<...> types")
+
+
 TypeId = namedtuple('TypeId', 'name params')
 
 
 # Make str(TypeId) produce a nicer value
 TypeId.__str__ = lambda self: \
     "%s<%s>" % (self.name, ', '.join(str(p) for p in self.params)) \
     if self.params is not None \
     else self.name
--- a/xpcom/reflect/xptcall/xptcall.h
+++ b/xpcom/reflect/xptcall/xptcall.h
@@ -53,16 +53,17 @@ struct nsXPTCVariant
     //
     // nsXPTCVariant contains enough space to store ExtendedVal inline, which
     // can be used to store these types when IsIndirect() is true.
         nsXPTCMiniVariant mini;
 
         nsCString  nscstr;
         nsString   nsstr;
         JS::Value  jsval;
+        xpt::detail::UntypedSequence sequence;
 
         // This type contains non-standard-layout types, so needs an explicit
         // Ctor/Dtor - we'll just delete them.
         ExtendedVal() = delete;
         ~ExtendedVal() = delete;
     };
 
     union
--- a/xpcom/reflect/xptinfo/xptcodegen.py
+++ b/xpcom/reflect/xptinfo/xptcodegen.py
@@ -279,16 +279,21 @@ def link_to_cpp(interfaces, fd):
     def lower_type(type, in_=False, out=False, optional=False):
         tag = type['tag']
         d1 = d2 = 0
 
         if tag == 'TD_ARRAY':
             d1 = type['size_is']
             d2 = lower_extra_type(type['element'])
 
+        elif tag == 'TD_SEQUENCE':
+            # NOTE: TD_SEQUENCE can hold 16 bits of type index, while TD_ARRAY
+            # can only hold 8.
+            d1, d2 = splitint(lower_extra_type(type['element']))
+
         elif tag == 'TD_INTERFACE_TYPE':
             d1, d2 = splitint(interface_idx(type['name']))
 
         elif tag == 'TD_INTERFACE_IS_TYPE':
             d1 = type['iid_is']
 
         elif tag == 'TD_DOMOBJECT':
             d1, d2 = splitint(lower_domobject(type))
--- a/xpcom/reflect/xptinfo/xptinfo.h
+++ b/xpcom/reflect/xptinfo/xptinfo.h
@@ -13,16 +13,17 @@
 #ifndef xptinfo_h
 #define xptinfo_h
 
 #include <stdint.h>
 #include "nsID.h"
 #include "mozilla/Assertions.h"
 #include "js/Value.h"
 #include "nsString.h"
+#include "nsTArray.h"
 
 // Forward Declarations
 namespace mozilla {
 namespace dom {
 struct NativePropertyHooks;
 } // namespace dom
 } // namespace mozilla
 
@@ -147,78 +148,91 @@ static_assert(sizeof(nsXPTInterfaceInfo)
 /*
  * The following enum represents contains the different tag types which
  * can be found in nsXPTTypeInfo::mTag.
  *
  * WARNING: mTag is 5 bits wide, supporting at most 32 tags.
  */
 enum nsXPTTypeTag : uint8_t
 {
+  // Arithmetic (POD) Types
   TD_INT8              = 0,
   TD_INT16             = 1,
   TD_INT32             = 2,
   TD_INT64             = 3,
   TD_UINT8             = 4,
   TD_UINT16            = 5,
   TD_UINT32            = 6,
   TD_UINT64            = 7,
   TD_FLOAT             = 8,
   TD_DOUBLE            = 9,
   TD_BOOL              = 10,
   TD_CHAR              = 11,
   TD_WCHAR             = 12,
+
+  // Non-Arithmetic Types
   TD_VOID              = 13,
   TD_PNSIID            = 14,
   TD_DOMSTRING         = 15,
   TD_PSTRING           = 16,
   TD_PWSTRING          = 17,
   TD_INTERFACE_TYPE    = 18,
   TD_INTERFACE_IS_TYPE = 19,
   TD_ARRAY             = 20,
   TD_PSTRING_SIZE_IS   = 21,
   TD_PWSTRING_SIZE_IS  = 22,
   TD_UTF8STRING        = 23,
   TD_CSTRING           = 24,
   TD_ASTRING           = 25,
   TD_JSVAL             = 26,
   TD_DOMOBJECT         = 27,
-  TD_PROMISE           = 28
+  TD_PROMISE           = 28,
+  TD_SEQUENCE          = 29
 };
 
 
 /*
  * A nsXPTType is a union used to identify the type of a method argument or
  * return value. The internal data is stored as an 5-bit tag, and two 8-bit
  * integers, to keep alignment requirements low.
  *
  * nsXPTType contains 3 extra bits, reserved for use by nsXPTParamInfo.
  */
 struct nsXPTType
 {
   // NOTE: This is uint8_t instead of nsXPTTypeTag so that it can be compared
   // with the nsXPTType::* re-exports.
   uint8_t Tag() const { return mTag; }
 
+  // The index in the function argument list which should be used when
+  // determining the iid_is or size_is properties of this dependent type.
   uint8_t ArgNum() const {
     MOZ_ASSERT(Tag() == TD_INTERFACE_IS_TYPE ||
                Tag() == TD_PSTRING_SIZE_IS ||
                Tag() == TD_PWSTRING_SIZE_IS ||
                Tag() == TD_ARRAY);
     return mData1;
   }
 
-  const nsXPTType& ArrayElementType() const {
-    MOZ_ASSERT(Tag() == TD_ARRAY);
-    return xpt::detail::GetType(mData2);
-  }
-
 private:
+  // Helper for reading 16-bit data values split between mData1 and mData2.
   uint16_t Data16() const { return ((uint16_t)mData1 << 8) | mData2; }
 
 public:
+  // Get the type of the element in the current array or sequence. Arrays only
+  // fit 8 bits of type data, while sequences support up to 16 bits of type data
+  // due to not needing to store an ArgNum.
+  const nsXPTType& ArrayElementType() const {
+    if (Tag() == TD_ARRAY) {
+      return xpt::detail::GetType(mData2);
+    }
+    MOZ_ASSERT(Tag() == TD_SEQUENCE);
+    return xpt::detail::GetType(Data16());
+  }
+
   // We store the 16-bit iface value as two 8-bit values in order to
   // avoid 16-bit alignment requirements for XPTTypeDescriptor, which
   // reduces its size and also the size of XPTParamDescriptor.
   const nsXPTInterfaceInfo* GetInterface() const {
     MOZ_ASSERT(Tag() == TD_INTERFACE_TYPE);
     return xpt::detail::GetInterface(Data16());
   }
 
@@ -238,23 +252,30 @@ public:
 
   bool IsInterfacePointer() const {
     return Tag() == TD_INTERFACE_TYPE || Tag() == TD_INTERFACE_IS_TYPE;
   }
 
   bool IsArray() const { return Tag() == TD_ARRAY; }
 
   bool IsDependent() const {
-    return Tag() == TD_INTERFACE_IS_TYPE || Tag() == TD_ARRAY ||
+    return (Tag() == TD_SEQUENCE && InnermostType().IsDependent()) ||
+           Tag() == TD_INTERFACE_IS_TYPE || Tag() == TD_ARRAY ||
            Tag() == TD_PSTRING_SIZE_IS || Tag() == TD_PWSTRING_SIZE_IS;
   }
 
+  bool IsAlwaysIndirect() const {
+    return Tag() == TD_ASTRING || Tag() == TD_DOMSTRING ||
+           Tag() == TD_CSTRING || Tag() == TD_UTF8STRING ||
+           Tag() == TD_JSVAL || Tag() == TD_SEQUENCE;
+  }
+
   // Unwrap a nested type to its innermost value (e.g. through arrays).
   const nsXPTType& InnermostType() const {
-    if (Tag() == TD_ARRAY) {
+    if (Tag() == TD_ARRAY || Tag() == TD_SEQUENCE) {
       return ArrayElementType().InnermostType();
     }
     return *this;
   }
 
   // Helper methods for working with the type's native representation.
   inline size_t Stride() const;
   inline bool HasPointerRepr() const;
@@ -329,17 +350,18 @@ public:
     T_ARRAY             = TD_ARRAY            ,
     T_PSTRING_SIZE_IS   = TD_PSTRING_SIZE_IS  ,
     T_PWSTRING_SIZE_IS  = TD_PWSTRING_SIZE_IS ,
     T_UTF8STRING        = TD_UTF8STRING       ,
     T_CSTRING           = TD_CSTRING          ,
     T_ASTRING           = TD_ASTRING          ,
     T_JSVAL             = TD_JSVAL            ,
     T_DOMOBJECT         = TD_DOMOBJECT        ,
-    T_PROMISE           = TD_PROMISE
+    T_PROMISE           = TD_PROMISE          ,
+    T_SEQUENCE          = TD_SEQUENCE
   };
 
   ////////////////////////////////////////////////////////////////
   // Ensure these fields are in the same order as xptcodegen.py //
   ////////////////////////////////////////////////////////////////
 
   uint8_t mTag : 5;
 
@@ -374,22 +396,17 @@ struct nsXPTParamInfo
   // Get the type of this parameter.
   const nsXPTType& Type() const { return mType; }
   const nsXPTType& GetType() const { return Type(); } // XXX remove (backcompat)
 
   // Whether this parameter is passed indirectly on the stack. All out/inout
   // params are passed indirectly, although some types are passed indirectly
   // unconditionally.
   bool IsIndirect() const {
-    return IsOut() ||
-      mType.Tag() == TD_JSVAL ||
-      mType.Tag() == TD_ASTRING ||
-      mType.Tag() == TD_DOMSTRING ||
-      mType.Tag() == TD_CSTRING ||
-      mType.Tag() == TD_UTF8STRING;
+    return IsOut() || Type().IsAlwaysIndirect();
   }
 
   ////////////////////////////////////////////////////////////////
   // Ensure these fields are in the same order as xptcodegen.py //
   ////////////////////////////////////////////////////////////////
 
   nsXPTType mType;
 };
@@ -497,16 +514,44 @@ struct nsXPTDOMObjectInfo
   bool (*mWrap) (JSContext* aCx, void* aObj, JS::MutableHandleValue aHandle);
   void (*mCleanup) (void* aObj);
 };
 
 
 namespace xpt {
 namespace detail {
 
+// The UntypedSequence type allows low-level access from XPConnect to nsTArray
+// internals without static knowledge of the array element type in question.
+class UntypedSequence
+  : public nsTArray_base<nsTArrayFallibleAllocator, nsTArray_CopyWithMemutils>
+{
+public:
+  void* Elements() const {
+    return static_cast<void*>(Hdr() + 1);
+  }
+
+  // Changes the length and capacity to be at least large enough for aTo elements.
+  bool SetLength(const nsXPTType& aEltTy, uint32_t aTo) {
+    if (!EnsureCapacity<nsTArrayFallibleAllocator>(aTo, aEltTy.Stride())) {
+      return false;
+    }
+    mHdr->mLength = aTo;
+    return true;
+  }
+
+  // Free backing memory for the nsTArray object.
+  void Clear() {
+    if (mHdr != EmptyHdr() && !UsesAutoArrayBuffer()) {
+      nsTArrayFallibleAllocator::Free(mHdr);
+    }
+    mHdr = EmptyHdr();
+  }
+};
+
 /**
  * The compressed representation of constants from XPT. Not part of the public
  * interface, as we also need to support Shim interfaces.
  */
 struct ConstInfo
 {
   ////////////////////////////////////////////////////////////////
   // Ensure these fields are in the same order as xptcodegen.py //
@@ -650,14 +695,15 @@ nsXPTType::Stride() const
     case TD_PSTRING_SIZE_IS:   return sizeof(char*);
     case TD_PWSTRING_SIZE_IS:  return sizeof(char16_t*);
     case TD_UTF8STRING:        return sizeof(nsCString);
     case TD_CSTRING:           return sizeof(nsCString);
     case TD_ASTRING:           return sizeof(nsString);
     case TD_JSVAL:             return sizeof(JS::Value);
     case TD_DOMOBJECT:         return sizeof(void*);
     case TD_PROMISE:           return sizeof(void*);
+    case TD_SEQUENCE:          return sizeof(xpt::detail::UntypedSequence);
   }
 
   MOZ_CRASH("Unknown type");
 }
 
 #endif /* xptinfo_h */