Title: [197467] trunk/Source/_javascript_Core
Revision
197467
Author
[email protected]
Date
2016-03-02 14:39:02 -0800 (Wed, 02 Mar 2016)

Log Message

[[SetPrototypeOf]] should be a fully virtual method in ClassInfo::methodTable
https://bugs.webkit.org/show_bug.cgi?id=154897

Reviewed by Filip Pizlo.

This patch makes us more consistent with how the ES6 specification models the
[[SetPrototypeOf]] trap. Moving this method into ClassInfo::methodTable 
is a prerequisite for implementing Proxy.[[SetPrototypeOf]]. This patch
still allows directly setting the prototype for situations where this
is the desired behavior. This is equivalent to setting the internal
[[Prototype]] field as described in the specification. 

* API/JSClassRef.cpp:
(OpaqueJSClass::prototype):
* API/JSObjectRef.cpp:
(JSObjectMake):
(JSObjectSetPrototype):
(JSObjectHasProperty):
* API/JSWrapperMap.mm:
(makeWrapper):
* runtime/ClassInfo.h:
* runtime/IntlCollatorConstructor.cpp:
(JSC::constructIntlCollator):
* runtime/IntlDateTimeFormatConstructor.cpp:
(JSC::constructIntlDateTimeFormat):
* runtime/IntlNumberFormatConstructor.cpp:
(JSC::constructIntlNumberFormat):
* runtime/JSCell.cpp:
(JSC::JSCell::isExtensible):
(JSC::JSCell::setPrototypeOf):
* runtime/JSCell.h:
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::resetPrototype):
* runtime/JSGlobalObjectFunctions.cpp:
(JSC::globalFuncProtoSetter):
* runtime/JSObject.cpp:
(JSC::JSObject::switchToSlowPutArrayStorage):
(JSC::JSObject::setPrototypeDirect):
(JSC::JSObject::setPrototypeWithCycleCheck):
(JSC::JSObject::setPrototypeOf):
(JSC::JSObject::allowsAccessFrom):
(JSC::JSObject::setPrototype): Deleted.
* runtime/JSObject.h:
(JSC::JSObject::setPrototypeOfInline):
(JSC::JSObject::mayInterceptIndexedAccesses):
* runtime/JSProxy.cpp:
(JSC::JSProxy::setTarget):
* runtime/ObjectConstructor.cpp:
(JSC::objectConstructorSetPrototypeOf):
* runtime/ReflectObject.cpp:
(JSC::reflectObjectSetPrototypeOf):

Modified Paths

Diff

Modified: trunk/Source/_javascript_Core/API/JSClassRef.cpp (197466 => 197467)


--- trunk/Source/_javascript_Core/API/JSClassRef.cpp	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/API/JSClassRef.cpp	2016-03-02 22:39:02 UTC (rev 197467)
@@ -204,7 +204,7 @@
     JSObject* prototype = JSCallbackObject<JSDestructibleObject>::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->callbackObjectStructure(), prototypeClass, &jsClassData); // set jsClassData as the object's private data, so it can clear our reference on destruction
     if (parentClass) {
         if (JSObject* parentPrototype = parentClass->prototype(exec))
-            prototype->setPrototype(exec->vm(), parentPrototype);
+            prototype->setPrototypeDirect(exec->vm(), parentPrototype);
     }
 
     jsClassData.cachedPrototype = Weak<JSObject>(prototype);

Modified: trunk/Source/_javascript_Core/API/JSObjectRef.cpp (197466 => 197467)


--- trunk/Source/_javascript_Core/API/JSObjectRef.cpp	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/API/JSObjectRef.cpp	2016-03-02 22:39:02 UTC (rev 197467)
@@ -117,7 +117,7 @@
 
     JSCallbackObject<JSDestructibleObject>* object = JSCallbackObject<JSDestructibleObject>::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->callbackObjectStructure(), jsClass, data);
     if (JSObject* prototype = jsClass->prototype(exec))
-        object->setPrototype(exec->vm(), prototype);
+        object->setPrototypeDirect(exec->vm(), prototype);
 
     return toRef(object);
 }
@@ -291,7 +291,7 @@
         // Someday we might use proxies for something other than JSGlobalObjects, but today is not that day.
         RELEASE_ASSERT_NOT_REACHED();
     }
-    jsObject->setPrototypeWithCycleCheck(exec, jsValue.isObject() ? jsValue : jsNull());
+    jsObject->setPrototypeOfInline(exec->vm(), exec, jsValue.isObject() ? jsValue : jsNull());
 }
 
 bool JSObjectHasProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName)

Modified: trunk/Source/_javascript_Core/API/JSWrapperMap.mm (197466 => 197467)


--- trunk/Source/_javascript_Core/API/JSWrapperMap.mm	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/API/JSWrapperMap.mm	2016-03-02 22:39:02 UTC (rev 197467)
@@ -117,7 +117,7 @@
     JSC::JSCallbackObject<JSC::JSAPIWrapperObject>* object = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->objcWrapperObjectStructure(), jsClass, 0);
     object->setWrappedObject(wrappedObject);
     if (JSC::JSObject* prototype = jsClass->prototype(exec))
-        object->setPrototype(exec->vm(), prototype);
+        object->setPrototypeDirect(exec->vm(), prototype);
 
     return object;
 }

Modified: trunk/Source/_javascript_Core/ChangeLog (197466 => 197467)


--- trunk/Source/_javascript_Core/ChangeLog	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/ChangeLog	2016-03-02 22:39:02 UTC (rev 197467)
@@ -1,5 +1,59 @@
 2016-03-02  Saam barati  <[email protected]>
 
+        [[SetPrototypeOf]] should be a fully virtual method in ClassInfo::methodTable
+        https://bugs.webkit.org/show_bug.cgi?id=154897
+
+        Reviewed by Filip Pizlo.
+
+        This patch makes us more consistent with how the ES6 specification models the
+        [[SetPrototypeOf]] trap. Moving this method into ClassInfo::methodTable 
+        is a prerequisite for implementing Proxy.[[SetPrototypeOf]]. This patch
+        still allows directly setting the prototype for situations where this
+        is the desired behavior. This is equivalent to setting the internal
+        [[Prototype]] field as described in the specification. 
+
+        * API/JSClassRef.cpp:
+        (OpaqueJSClass::prototype):
+        * API/JSObjectRef.cpp:
+        (JSObjectMake):
+        (JSObjectSetPrototype):
+        (JSObjectHasProperty):
+        * API/JSWrapperMap.mm:
+        (makeWrapper):
+        * runtime/ClassInfo.h:
+        * runtime/IntlCollatorConstructor.cpp:
+        (JSC::constructIntlCollator):
+        * runtime/IntlDateTimeFormatConstructor.cpp:
+        (JSC::constructIntlDateTimeFormat):
+        * runtime/IntlNumberFormatConstructor.cpp:
+        (JSC::constructIntlNumberFormat):
+        * runtime/JSCell.cpp:
+        (JSC::JSCell::isExtensible):
+        (JSC::JSCell::setPrototypeOf):
+        * runtime/JSCell.h:
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::resetPrototype):
+        * runtime/JSGlobalObjectFunctions.cpp:
+        (JSC::globalFuncProtoSetter):
+        * runtime/JSObject.cpp:
+        (JSC::JSObject::switchToSlowPutArrayStorage):
+        (JSC::JSObject::setPrototypeDirect):
+        (JSC::JSObject::setPrototypeWithCycleCheck):
+        (JSC::JSObject::setPrototypeOf):
+        (JSC::JSObject::allowsAccessFrom):
+        (JSC::JSObject::setPrototype): Deleted.
+        * runtime/JSObject.h:
+        (JSC::JSObject::setPrototypeOfInline):
+        (JSC::JSObject::mayInterceptIndexedAccesses):
+        * runtime/JSProxy.cpp:
+        (JSC::JSProxy::setTarget):
+        * runtime/ObjectConstructor.cpp:
+        (JSC::objectConstructorSetPrototypeOf):
+        * runtime/ReflectObject.cpp:
+        (JSC::reflectObjectSetPrototypeOf):
+
+2016-03-02  Saam barati  <[email protected]>
+
         SIGSEGV in Proxy [[Get]] and [[Set]] recursion
         https://bugs.webkit.org/show_bug.cgi?id=154854
 

Modified: trunk/Source/_javascript_Core/runtime/ClassInfo.h (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/ClassInfo.h	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/ClassInfo.h	2016-03-02 22:39:02 UTC (rev 197467)
@@ -109,6 +109,9 @@
     typedef bool (*IsExtensibleFunctionPtr)(JSObject*, ExecState*);
     IsExtensibleFunctionPtr isExtensible;
 
+    typedef bool (*SetPrototypeOfFunctionPtr)(JSObject*, ExecState*, JSValue);
+    SetPrototypeOfFunctionPtr setPrototypeOf;
+
     typedef void (*DumpToStreamFunctionPtr)(const JSCell*, PrintStream&);
     DumpToStreamFunctionPtr dumpToStream;
 
@@ -162,6 +165,7 @@
         &ClassName::getTypedArrayImpl, \
         &ClassName::preventExtensions, \
         &ClassName::isExtensible, \
+        &ClassName::setPrototypeOf, \
         &ClassName::dumpToStream, \
         &ClassName::estimatedSize \
     }, \

Modified: trunk/Source/_javascript_Core/runtime/IntlCollatorConstructor.cpp (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/IntlCollatorConstructor.cpp	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/IntlCollatorConstructor.cpp	2016-03-02 22:39:02 UTC (rev 197467)
@@ -97,7 +97,9 @@
     IntlCollator* collator = IntlCollator::create(vm, jsCast<IntlCollatorConstructor*>(state->callee()));
     if (collator && !jsDynamicCast<IntlCollatorConstructor*>(newTarget)) {
         JSValue proto = asObject(newTarget)->getDirect(vm, vm.propertyNames->prototype);
-        asObject(collator)->setPrototypeWithCycleCheck(state, proto);
+        asObject(collator)->setPrototypeOfInline(vm, state, proto);
+        if (vm.exception())
+            return JSValue::encode(JSValue());
     }
 
     // 3. ReturnIfAbrupt(collator).

Modified: trunk/Source/_javascript_Core/runtime/IntlDateTimeFormatConstructor.cpp (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/IntlDateTimeFormatConstructor.cpp	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/IntlDateTimeFormatConstructor.cpp	2016-03-02 22:39:02 UTC (rev 197467)
@@ -96,7 +96,9 @@
     IntlDateTimeFormat* dateTimeFormat = IntlDateTimeFormat::create(vm, jsCast<IntlDateTimeFormatConstructor*>(state->callee()));
     if (dateTimeFormat && !jsDynamicCast<IntlDateTimeFormatConstructor*>(newTarget)) {
         JSValue proto = asObject(newTarget)->getDirect(vm, vm.propertyNames->prototype);
-        asObject(dateTimeFormat)->setPrototypeWithCycleCheck(state, proto);
+        asObject(dateTimeFormat)->setPrototypeOfInline(vm, state, proto);
+        if (vm.exception())
+            return JSValue::encode(JSValue());
     }
 
     // 3. ReturnIfAbrupt(dateTimeFormat).

Modified: trunk/Source/_javascript_Core/runtime/IntlNumberFormatConstructor.cpp (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/IntlNumberFormatConstructor.cpp	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/IntlNumberFormatConstructor.cpp	2016-03-02 22:39:02 UTC (rev 197467)
@@ -96,7 +96,9 @@
     IntlNumberFormat* numberFormat = IntlNumberFormat::create(vm, jsCast<IntlNumberFormatConstructor*>(state->callee()));
     if (numberFormat && !jsDynamicCast<IntlNumberFormatConstructor*>(newTarget)) {
         JSValue proto = asObject(newTarget)->getDirect(vm, vm.propertyNames->prototype);
-        asObject(numberFormat)->setPrototypeWithCycleCheck(state, proto);
+        asObject(numberFormat)->setPrototypeOfInline(vm, state, proto);
+        if (vm.exception())
+            return JSValue::encode(JSValue());
     }
 
     // 3. ReturnIfAbrupt(numberFormat).

Modified: trunk/Source/_javascript_Core/runtime/JSCell.cpp (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/JSCell.cpp	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/JSCell.cpp	2016-03-02 22:39:02 UTC (rev 197467)
@@ -280,4 +280,9 @@
     RELEASE_ASSERT_NOT_REACHED();
 }
 
+bool JSCell::setPrototypeOf(JSObject*, ExecState*, JSValue)
+{
+    RELEASE_ASSERT_NOT_REACHED();
+}
+
 } // namespace JSC

Modified: trunk/Source/_javascript_Core/runtime/JSCell.h (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/JSCell.h	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/JSCell.h	2016-03-02 22:39:02 UTC (rev 197467)
@@ -208,6 +208,7 @@
     static NO_RETURN_DUE_TO_CRASH void getGenericPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode);
     static NO_RETURN_DUE_TO_CRASH bool preventExtensions(JSObject*, ExecState*);
     static NO_RETURN_DUE_TO_CRASH bool isExtensible(JSObject*, ExecState*);
+    static NO_RETURN_DUE_TO_CRASH bool setPrototypeOf(JSObject*, ExecState*, JSValue);
 
     static String className(const JSObject*);
     JS_EXPORT_PRIVATE static bool customHasInstance(JSObject*, ExecState*, JSValue);

Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObject.cpp	2016-03-02 22:39:02 UTC (rev 197467)
@@ -807,12 +807,12 @@
 // Set prototype, and also insert the object prototype at the end of the chain.
 void JSGlobalObject::resetPrototype(VM& vm, JSValue prototype)
 {
-    setPrototype(vm, prototype);
+    setPrototypeDirect(vm, prototype);
 
     JSObject* oldLastInPrototypeChain = lastInPrototypeChain(this);
     JSObject* objectPrototype = m_objectPrototype.get();
     if (oldLastInPrototypeChain != objectPrototype)
-        oldLastInPrototypeChain->setPrototype(vm, objectPrototype);
+        oldLastInPrototypeChain->setPrototypeDirect(vm, objectPrototype);
 
     // Whenever we change the prototype of the global object, we need to create a new JSProxy with the correct prototype.
     setGlobalThis(vm, JSProxy::create(vm, JSProxy::createStructure(vm, this, prototype, PureForwardingProxyType), this));

Modified: trunk/Source/_javascript_Core/runtime/JSGlobalObjectFunctions.cpp (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/JSGlobalObjectFunctions.cpp	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/JSGlobalObjectFunctions.cpp	2016-03-02 22:39:02 UTC (rev 197467)
@@ -891,8 +891,13 @@
     if (!isExtensible)
         return throwVMError(exec, createTypeError(exec, StrictModeReadonlyPropertyWriteError));
 
-    if (!thisObject->setPrototypeWithCycleCheck(exec, value))
-        exec->vm().throwException(exec, createError(exec, ASCIILiteral("cyclic __proto__ value")));
+    VM& vm = exec->vm();
+    if (!thisObject->setPrototypeOfInline(vm, exec, value)) {
+        if (!vm.exception())
+            vm.throwException(exec, createError(exec, ASCIILiteral("cyclic __proto__ value")));
+        return JSValue::encode(jsUndefined());
+    }
+    ASSERT(!exec->hadException());
     return JSValue::encode(jsUndefined());
 }
     

Modified: trunk/Source/_javascript_Core/runtime/JSObject.cpp (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/JSObject.cpp	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/JSObject.cpp	2016-03-02 22:39:02 UTC (rev 197467)
@@ -1171,7 +1171,7 @@
     }
 }
 
-void JSObject::setPrototype(VM& vm, JSValue prototype)
+void JSObject::setPrototypeDirect(VM& vm, JSValue prototype)
 {
     ASSERT(prototype);
     if (prototype.isObject())
@@ -1197,19 +1197,25 @@
     switchToSlowPutArrayStorage(vm);
 }
 
-bool JSObject::setPrototypeWithCycleCheck(ExecState* exec, JSValue prototype)
+bool JSObject::setPrototypeWithCycleCheck(VM& vm, ExecState* exec, JSValue prototype)
 {
-    ASSERT(methodTable(exec->vm())->toThis(this, exec, NotStrictMode) == this);
+    UNUSED_PARAM(exec);
+    ASSERT(methodTable(vm)->toThis(this, exec, NotStrictMode) == this);
     JSValue nextPrototype = prototype;
     while (nextPrototype && nextPrototype.isObject()) {
         if (nextPrototype == this)
             return false;
         nextPrototype = asObject(nextPrototype)->prototype();
     }
-    setPrototype(exec->vm(), prototype);
+    setPrototypeDirect(vm, prototype);
     return true;
 }
 
+bool JSObject::setPrototypeOf(JSObject* object, ExecState* exec, JSValue prototype)
+{
+    return object->setPrototypeWithCycleCheck(exec->vm(), exec, prototype);
+}
+
 bool JSObject::allowsAccessFrom(ExecState* exec)
 {
     JSGlobalObject* globalObject = this->globalObject();

Modified: trunk/Source/_javascript_Core/runtime/JSObject.h (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/JSObject.h	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/JSObject.h	2016-03-02 22:39:02 UTC (rev 197467)
@@ -103,8 +103,31 @@
     JS_EXPORT_PRIVATE static String calculatedClassName(JSObject*);
 
     JSValue prototype() const;
-    JS_EXPORT_PRIVATE void setPrototype(VM&, JSValue prototype);
-    JS_EXPORT_PRIVATE bool setPrototypeWithCycleCheck(ExecState*, JSValue prototype);
+    // This sets the prototype without checking for cycles and without
+    // doing dynamic dispatch on [[SetPrototypeOf]] operation in the specification.
+    // It is not valid to use this when performing a [[SetPrototypeOf]] operation in
+    // the specification. It is valid to use though when you know that you want to directly
+    // set it without consulting the method table and when you definitely won't
+    // introduce a cycle in the prototype chain. This is akin to setting the
+    // [[Prototype]] internal field directly as described in the specification.
+    JS_EXPORT_PRIVATE void setPrototypeDirect(VM&, JSValue prototype);
+private:
+    // This is OrdinarySetPrototypeOf in the specification. Section 9.1.2.1
+    // https://tc39.github.io/ecma262/#sec-ordinarysetprototypeof
+    JS_EXPORT_PRIVATE bool setPrototypeWithCycleCheck(VM&, ExecState*, JSValue prototype);
+public:
+    // This is the fully virtual [[SetPrototypeOf]] internal function defined
+    // in the ECMAScript 6 specification. Use this when doing a [[SetPrototypeOf]] 
+    // operation as dictated in the specification.
+    ALWAYS_INLINE bool setPrototypeOfInline(VM& vm, ExecState* exec, JSValue prototype)
+    {
+        auto setPrototypeOfMethod = methodTable(vm)->setPrototypeOf;
+        if (LIKELY(setPrototypeOfMethod == JSObject::setPrototypeOf))
+            return setPrototypeWithCycleCheck(vm, exec, prototype);
+
+        return setPrototypeOfMethod(this, exec, prototype);
+    }
+    JS_EXPORT_PRIVATE static bool setPrototypeOf(JSObject*, ExecState*, JSValue prototype);
         
     bool mayInterceptIndexedAccesses()
     {

Modified: trunk/Source/_javascript_Core/runtime/JSProxy.cpp (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/JSProxy.cpp	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/JSProxy.cpp	2016-03-02 22:39:02 UTC (rev 197467)
@@ -47,7 +47,7 @@
 {
     ASSERT_ARG(globalObject, globalObject);
     m_target.set(vm, this, globalObject);
-    setPrototype(vm, globalObject->prototype());
+    setPrototypeDirect(vm, globalObject->prototype());
 
     PrototypeMap& prototypeMap = vm.prototypeMap;
     if (!prototypeMap.isPrototype(this))

Modified: trunk/Source/_javascript_Core/runtime/ObjectConstructor.cpp (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/ObjectConstructor.cpp	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/ObjectConstructor.cpp	2016-03-02 22:39:02 UTC (rev 197467)
@@ -218,8 +218,12 @@
     if (!isExtensible)
         return throwVMError(exec, createTypeError(exec, StrictModeReadonlyPropertyWriteError));
 
-    if (!object->setPrototypeWithCycleCheck(exec, protoValue)) {
-        exec->vm().throwException(exec, createError(exec, ASCIILiteral("cyclic __proto__ value")));
+    VM& vm = exec->vm();
+    bool didSetPrototype = object->setPrototypeOfInline(vm, exec, protoValue);
+    if (vm.exception())
+        return JSValue::encode(JSValue());
+    if (!didSetPrototype) {
+        vm.throwException(exec, createError(exec, ASCIILiteral("cyclic __proto__ value")));
         return JSValue::encode(jsUndefined());
     }
 

Modified: trunk/Source/_javascript_Core/runtime/ReflectObject.cpp (197466 => 197467)


--- trunk/Source/_javascript_Core/runtime/ReflectObject.cpp	2016-03-02 22:37:57 UTC (rev 197466)
+++ trunk/Source/_javascript_Core/runtime/ReflectObject.cpp	2016-03-02 22:39:02 UTC (rev 197467)
@@ -221,7 +221,11 @@
     if (!isExtensible)
         return JSValue::encode(jsBoolean(false));
 
-    return JSValue::encode(jsBoolean(object->setPrototypeWithCycleCheck(exec, proto)));
+    VM& vm = exec->vm();
+    bool didSetPrototype = object->setPrototypeOfInline(vm, exec, proto);
+    if (vm.exception())
+        return JSValue::encode(JSValue());
+    return JSValue::encode(jsBoolean(didSetPrototype));
 }
 
 } // namespace JSC
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to