Bug 1484496: Part 1 - Add support for symbol properties to XPIDL. r=nika
authorKris Maglione <maglione.k@gmail.com>
Tue, 21 Aug 2018 14:08:35 -0700
changeset 481497 10d2e81f3c8a157151bf5cca7133e65458a859eb
parent 481496 b88f5fa7dca4ba5cb92d9c905f3e52d043977498
child 481498 062e4138bfde6fb0f010d3fabb82b052b2a1b301
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersnika
bugs1484496
milestone63.0a1
Bug 1484496: Part 1 - Add support for symbol properties to XPIDL. r=nika This patch allows us to define methods or getters/setters for any of the current set of well-known symbols. Those are defined by adding the [symbol] attribute to a method: [symbol] Iterator iterator(); which causes the method to define a property with the well-known symbol which matches its method name (Symbol.iterator, in this case). Due to the implementation details of the XPIDL parser, this currently does not support defining a non-symbol function with the same name as a symbol function: [symbol] Iterator iterator(); [binaryname(OtherIterator)] Thing iterator(in nsIDRef aIID); throws for a duplicate method name, even though there is no actual conflict. Differential Revision: https://phabricator.services.mozilla.com/D3724
js/xpconnect/src/XPCWrappedJSClass.cpp
js/xpconnect/src/XPCWrappedNativeInfo.cpp
xpcom/idl-parser/xpidl/jsonxpt.py
xpcom/idl-parser/xpidl/xpidl.py
xpcom/reflect/xptinfo/xptcodegen.py
xpcom/reflect/xptinfo/xptinfo.cpp
xpcom/reflect/xptinfo/xptinfo.h
--- a/js/xpconnect/src/XPCWrappedJSClass.cpp
+++ b/js/xpconnect/src/XPCWrappedJSClass.cpp
@@ -938,17 +938,16 @@ nsXPCWrappedJSClass::CallMethod(nsXPCWra
 {
     Value* sp = nullptr;
     Value* argv = nullptr;
     uint8_t i;
     nsresult retval = NS_ERROR_FAILURE;
     bool success;
     bool readyToDoTheCall = false;
     nsID  param_iid;
-    const char* name = info->GetName();
     bool foundDependentParam;
 
     // We're about to call into script via an XPCWrappedJS, so we need an
     // AutoEntryScript. This is probably Gecko-specific at this point, and
     // definitely will be when we turn off XPConnect for the web.
     RootedObject obj(RootingCx(), wrapper->GetJSObject());
     nsIGlobalObject* nativeGlobal = NativeGlobal(js::UncheckedUnwrap(obj));
     AutoEntryScript aes(nativeGlobal, "XPCWrappedJS method call",
@@ -957,16 +956,29 @@ nsXPCWrappedJSClass::CallMethod(nsXPCWra
     if (!ccx.IsValid())
         return retval;
 
     JSContext* cx = ccx.GetJSContext();
 
     if (!cx || !IsReflectable(methodIndex))
         return NS_ERROR_FAILURE;
 
+    JS::RootedId id(cx);
+    const char* name;
+    nsAutoCString symbolName;
+    if (info->IsSymbol()) {
+        info->GetSymbolDescription(cx, symbolName);
+        name = symbolName.get();
+        id = SYMBOL_TO_JSID(info->GetSymbol(cx));
+    } else {
+        name = info->GetName();
+        if (!AtomizeAndPinJSString(cx, id.get(), name))
+            return NS_ERROR_FAILURE;
+    }
+
     // We passed the unwrapped object's global to AutoEntryScript so we now need
     // to enter the realm corresponding with the (maybe wrapper) object.
     RootedObject scope(cx, wrapper->GetJSObjectGlobal());
     JSAutoRealm ar(cx, scope);
 
     // [optional_argc] has a different calling convention, which we don't
     // support for JS-implemented components.
     if (info->WantsOptArgc()) {
@@ -1025,17 +1037,17 @@ nsXPCWrappedJSClass::CallMethod(nsXPCWra
         // In the normal (non-function) case we just lookup the property by
         // name and as long as the object has such a named property we go ahead
         // and try to make the call. If it turns out the named property is not
         // a callable object then the JS engine will throw an error and we'll
         // pass this along to the caller as an exception/result code.
 
         fval = ObjectValue(*obj);
         if (!isFunction || JS_TypeOfValue(ccx, fval) != JSTYPE_FUNCTION) {
-            if (!JS_GetProperty(cx, obj, name, &fval))
+            if (!JS_GetPropertyById(cx, obj, id, &fval))
                 goto pre_call_clean_up;
             // XXX We really want to factor out the error reporting better and
             // specifically report the failure to find a function with this name.
             // This is what we do below if the property is found but is not a
             // function. We just need to factor better so we can get to that
             // reporting path from here.
 
             thisObj = obj;
--- a/js/xpconnect/src/XPCWrappedNativeInfo.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeInfo.cpp
@@ -82,17 +82,23 @@ XPCNativeMember::Resolve(XPCCallContext&
             argc-- ;
 
         callback = XPC_WN_CallMethod;
     } else {
         argc = 0;
         callback = XPC_WN_GetterSetter;
     }
 
-    JSFunction* fun = js::NewFunctionByIdWithReserved(ccx, callback, argc, 0, GetName());
+    JSFunction* fun;
+    jsid name = GetName();
+    if (JSID_IS_STRING(name)) {
+        fun = js::NewFunctionByIdWithReserved(ccx, callback, argc, 0, GetName());
+    } else {
+        fun = js::NewFunctionWithReserved(ccx, callback, argc, 0, nullptr);
+    }
     if (!fun)
         return false;
 
     JSObject* funobj = JS_GetFunctionObject(fun);
     if (!funobj)
         return false;
 
     js::SetFunctionNativeReserved(funobj, XPC_FUNCTION_NATIVE_MEMBER_SLOT,
@@ -287,23 +293,28 @@ XPCNativeInterface::NewInstance(const ns
 
         // don't reflect Addref or Release
         if (i == 1 || i == 2)
             continue;
 
         if (!XPCConvert::IsMethodReflectable(*info))
             continue;
 
-        str = JS_AtomizeAndPinString(cx, info->GetName());
-        if (!str) {
-            NS_ERROR("bad method name");
-            failed = true;
-            break;
+        jsid name;
+        if (info->IsSymbol()) {
+            name = SYMBOL_TO_JSID(info->GetSymbol(cx));
+        } else {
+            str = JS_AtomizeAndPinString(cx, info->GetName());
+            if (!str) {
+                NS_ERROR("bad method name");
+                failed = true;
+                break;
+            }
+            name = INTERNED_STRING_TO_JSID(cx, str);
         }
-        jsid name = INTERNED_STRING_TO_JSID(cx, str);
 
         if (info->IsSetter()) {
             MOZ_ASSERT(realTotalCount,"bad setter");
             // Note: ASSUMES Getter/Setter pairs are next to each other
             // This is a rule of the typelib spec.
             cur = &members[realTotalCount-1];
             MOZ_ASSERT(cur->GetName() == name,"bad setter");
             MOZ_ASSERT(cur->IsReadOnlyAttribute(),"bad setter");
--- a/xpcom/idl-parser/xpidl/jsonxpt.py
+++ b/xpcom/idl-parser/xpidl/jsonxpt.py
@@ -116,33 +116,35 @@ def mk_param(type, in_=0, out=0, optiona
             ('in', in_),
             ('out', out),
             ('optional', optional),
         ),
     }
 
 
 def mk_method(name, params, getter=0, setter=0, notxpcom=0,
-              hidden=0, optargc=0, context=0, hasretval=0):
+              hidden=0, optargc=0, context=0, hasretval=0,
+              symbol=0):
     return {
         'name': name,
         # NOTE: We don't include any return value information here, as we'll
         # never call the methods if they're marked notxpcom, and all xpcom
         # methods return the same type (nsresult).
         # XXX: If we ever use these files for other purposes than xptcodegen we
         # may want to write that info.
         'params': params,
         'flags': flags(
             ('getter', getter),
             ('setter', setter),
             ('notxpcom', notxpcom),
             ('hidden', hidden),
             ('optargc', optargc),
             ('jscontext', context),
             ('hasretval', hasretval),
+            ('symbol', symbol),
         ),
     }
 
 
 def attr_param_idx(p, m, attr):
     if hasattr(p, attr) and getattr(p, attr):
         for i, param in enumerate(m.params):
             if param.name == getattr(p, attr):
@@ -181,29 +183,31 @@ def build_interface(iface):
         hasretval = len(m.params) > 0 and m.params[-1].retval
         if not m.notxpcom and m.realtype.name != 'void':
             hasretval = True
             params.append(mk_param(get_type(m.realtype, 'out'), out=1))
 
         methods.append(mk_method(
             m.name, params, notxpcom=m.notxpcom, hidden=m.noscript,
             optargc=m.optional_argc, context=m.implicit_jscontext,
-            hasretval=hasretval))
+            hasretval=hasretval, symbol=m.symbol))
 
     def build_attr(a):
         # Write the getter
         param = mk_param(get_type(a.realtype, 'out'), out=1)
         methods.append(mk_method(a.name, [param], getter=1, hidden=a.noscript,
-                                 context=a.implicit_jscontext, hasretval=1))
+                                 context=a.implicit_jscontext, hasretval=1,
+                                 symbol=a.symbol))
 
         # And maybe the setter
         if not a.readonly:
             param = mk_param(get_type(a.realtype, 'in'), in_=1)
             methods.append(mk_method(a.name, [param], setter=1, hidden=a.noscript,
-                                     context=a.implicit_jscontext))
+                                     context=a.implicit_jscontext,
+                                     symbol=a.symbol))
 
     for member in iface.members:
         if isinstance(member, xpidl.ConstMember):
             build_const(member)
         elif isinstance(member, xpidl.Attribute):
             build_attr(member)
         elif isinstance(member, xpidl.Method):
             build_method(member)
--- a/xpcom/idl-parser/xpidl/xpidl.py
+++ b/xpcom/idl-parser/xpidl/xpidl.py
@@ -907,16 +907,17 @@ class ConstMember(object):
     def count(self):
         return 0
 
 
 class Attribute(object):
     kind = 'attribute'
     noscript = False
     readonly = False
+    symbol = False
     implicit_jscontext = False
     nostdcall = False
     must_use = False
     binaryname = None
     null = None
     undefined = None
     infallible = False
 
@@ -958,16 +959,18 @@ class Attribute(object):
                                    aloc)
                 self.undefined = value
             else:
                 if value is not None:
                     raise IDLError("Unexpected attribute value", aloc)
 
                 if name == 'noscript':
                     self.noscript = True
+                elif name == 'symbol':
+                    self.symbol = True
                 elif name == 'implicit_jscontext':
                     self.implicit_jscontext = True
                 elif name == 'nostdcall':
                     self.nostdcall = True
                 elif name == 'must_use':
                     self.must_use = True
                 elif name == 'infallible':
                     self.infallible = True
@@ -1014,16 +1017,17 @@ class Attribute(object):
     def count(self):
         return self.readonly and 1 or 2
 
 
 class Method(object):
     kind = 'method'
     noscript = False
     notxpcom = False
+    symbol = False
     binaryname = None
     implicit_jscontext = False
     nostdcall = False
     must_use = False
     optional_argc = False
 
     def __init__(self, type, name, attlist, paramlist, location, doccomments, raises):
         self.type = type
@@ -1045,16 +1049,18 @@ class Method(object):
 
             if value is not None:
                 raise IDLError("Unexpected attribute value", aloc)
 
             if name == 'noscript':
                 self.noscript = True
             elif name == 'notxpcom':
                 self.notxpcom = True
+            elif name == 'symbol':
+                self.symbol = True
             elif name == 'implicit_jscontext':
                 self.implicit_jscontext = True
             elif name == 'optional_argc':
                 self.optional_argc = True
             elif name == 'nostdcall':
                 self.nostdcall = True
             elif name == 'must_use':
                 self.must_use = True
--- a/xpcom/reflect/xptinfo/xptcodegen.py
+++ b/xpcom/reflect/xptinfo/xptcodegen.py
@@ -79,16 +79,17 @@ nsXPTMethodInfo = mkstruct(
     "mNumParams",
     "mGetter",
     "mSetter",
     "mNotXPCOM",
     "mHidden",
     "mOptArgc",
     "mContext",
     "mHasRetval",
+    "mIsSymbol",
 )
 
 ##########################################################
 # Ensure these fields are in the same order as xptinfo.h #
 ##########################################################
 nsXPTDOMObjectInfo = mkstruct(
     "mUnwrap",
     "mWrap",
@@ -239,16 +240,19 @@ def link_to_cpp(interfaces, fd):
         elif len(strings):
             # Get the last string we inserted (should be O(1) on OrderedDict).
             last_s = next(reversed(strings))
             strings[s] = strings[last_s] + len(last_s) + 1
         else:
             strings[s] = 0
         return strings[s]
 
+    def lower_symbol(s):
+        return "uint32_t(JS::SymbolCode::%s)" % s
+
     def lower_extra_type(type):
         key = describe_type(type)
         idx = type_cache.get(key)
         if idx is None:
             idx = type_cache[key] = len(types)
             types.append(lower_type(type))
         return idx
 
@@ -310,20 +314,26 @@ def link_to_cpp(interfaces, fd):
                              in_='in' in param['flags'],
                              out='out' in param['flags'],
                              optional='optional' in param['flags'])
         ))
 
     def lower_method(method, ifacename):
         methodname = "%s::%s" % (ifacename, method['name'])
 
+        isSymbol = 'symbol' in method['flags']
+
         if 'notxpcom' in method['flags'] or 'hidden' in method['flags']:
             paramidx = name = numparams = 0  # hide parameters
         else:
-            name = lower_string(method['name'])
+            if isSymbol:
+                name = lower_symbol(method['name'])
+            else:
+                name = lower_string(method['name'])
+
             numparams = len(method['params'])
 
             # Check cache for parameters
             cachekey = json.dumps(method['params'])
             paramidx = param_cache.get(cachekey)
             if paramidx is None:
                 paramidx = param_cache[cachekey] = len(params)
                 for idx, param in enumerate(method['params']):
@@ -341,16 +351,17 @@ def link_to_cpp(interfaces, fd):
             # Flags
             mGetter='getter' in method['flags'],
             mSetter='setter' in method['flags'],
             mNotXPCOM='notxpcom' in method['flags'],
             mHidden='hidden' in method['flags'],
             mOptArgc='optargc' in method['flags'],
             mContext='jscontext' in method['flags'],
             mHasRetval='hasretval' in method['flags'],
+            mIsSymbol=isSymbol,
         ))
 
     def lower_const(const, ifacename):
         assert const['type']['tag'] in \
             ['TD_INT16', 'TD_INT32', 'TD_UINT16', 'TD_UINT32']
         is_signed = const['type']['tag'] in ['TD_INT16', 'TD_INT32']
 
         # Constants are always either signed or unsigned 16 or 32 bit integers,
--- a/xpcom/reflect/xptinfo/xptinfo.cpp
+++ b/xpcom/reflect/xptinfo/xptinfo.cpp
@@ -4,16 +4,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 #include "xptinfo.h"
 #include "nsISupports.h"
 #include "mozilla/dom/DOMJSClass.h"
 #include "mozilla/ArrayUtils.h"
 
+#include "jsfriendapi.h"
+
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace xpt::detail;
 
 
 ////////////////////////////////////
 // Constant Lookup Helper Methods //
 ////////////////////////////////////
@@ -163,8 +165,27 @@ nsXPTInterfaceInfo::HasAncestor(const ns
 }
 
 nsresult
 nsXPTInterfaceInfo::IsMainProcessScriptableOnly(bool* aRetval) const
 {
   *aRetval = IsMainProcessScriptableOnly();
   return NS_OK;
 }
+
+////////////////////////////////////
+// nsXPTMethodInfo symbol helpers //
+////////////////////////////////////
+
+void
+nsXPTMethodInfo::GetSymbolDescription(JSContext* aCx, nsACString& aID) const
+{
+  JS::RootedSymbol symbol(aCx, GetSymbol(aCx));
+  JSString* desc = JS::GetSymbolDescription(symbol);
+  MOZ_ASSERT(JS_StringHasLatin1Chars(desc));
+
+  JS::AutoAssertNoGC nogc(aCx);
+  size_t length;
+  const JS::Latin1Char* chars = JS_GetLatin1StringCharsAndLength(
+    aCx, nogc, desc, &length);
+
+  aID.AssignASCII(reinterpret_cast<const char*>(chars), length);
+}
--- a/xpcom/reflect/xptinfo/xptinfo.h
+++ b/xpcom/reflect/xptinfo/xptinfo.h
@@ -11,16 +11,17 @@
  */
 
 #ifndef xptinfo_h
 #define xptinfo_h
 
 #include <stdint.h>
 #include "nsID.h"
 #include "mozilla/Assertions.h"
+#include "jsapi.h"
 #include "js/Value.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
 // Forward Declarations
 namespace mozilla {
 namespace dom {
 struct NativePropertyHooks;
@@ -435,21 +436,23 @@ static_assert(sizeof(nsXPTParamInfo) == 
  * A nsXPTMethodInfo is used to describe a single interface method.
  */
 struct nsXPTMethodInfo
 {
   bool IsGetter() const { return mGetter; }
   bool IsSetter() const { return mSetter; }
   bool IsNotXPCOM() const { return mNotXPCOM; }
   bool IsHidden() const { return mHidden; }
+  bool IsSymbol() const { return mIsSymbol; }
   bool WantsOptArgc() const { return mOptArgc; }
   bool WantsContext() const { return mContext; }
   uint8_t ParamCount() const { return mNumParams; }
 
   const char* Name() const {
+    MOZ_ASSERT(!IsSymbol());
     return xpt::detail::GetString(mName);
   }
   const nsXPTParamInfo& Param(uint8_t aIndex) const {
     MOZ_ASSERT(aIndex < mNumParams);
     return xpt::detail::GetParam(mParams + aIndex);
   }
 
   bool HasRetval() const { return mHasRetval; }
@@ -473,16 +476,30 @@ struct nsXPTMethodInfo
     return ParamCount() - uint8_t(HasRetval());
   }
 
   /////////////////////////////////////////////
   // nsXPTMethodInfo backwards compatibility //
   /////////////////////////////////////////////
 
   const char* GetName() const { return Name(); }
+
+  JS::SymbolCode GetSymbolCode() const
+  {
+    MOZ_ASSERT(IsSymbol());
+    return JS::SymbolCode(mName);
+  }
+
+  JS::Symbol* GetSymbol(JSContext* aCx) const
+  {
+    return JS::GetWellKnownSymbol(aCx, GetSymbolCode());
+  }
+
+  void GetSymbolDescription(JSContext* aCx, nsACString& aID) const;
+
   uint8_t GetParamCount() const { return ParamCount(); }
   const nsXPTParamInfo& GetParam(uint8_t aIndex) const {
     return Param(aIndex);
   }
 
   ////////////////////////////////////////////////////////////////
   // Ensure these fields are in the same order as xptcodegen.py //
   ////////////////////////////////////////////////////////////////
@@ -493,17 +510,17 @@ struct nsXPTMethodInfo
 
   uint8_t mGetter : 1;
   uint8_t mSetter : 1;
   uint8_t mNotXPCOM : 1;
   uint8_t mHidden : 1;
   uint8_t mOptArgc : 1;
   uint8_t mContext : 1;
   uint8_t mHasRetval : 1;
-  // uint8_t unused : 1;
+  uint8_t mIsSymbol : 1;
 };
 
 // The fields in nsXPTMethodInfo were carefully ordered to minimize size.
 static_assert(sizeof(nsXPTMethodInfo) == 8, "wrong size");
 
 /**
  * A nsXPTConstantInfo is used to describe a single interface constant.
  */