[JSC] add missing RequireObjectCoercible() step in destructuring
https://bugs.webkit.org/show_bug.cgi?id=151596

Patch by Caitlin Potter <caitp@igalia.com> on 2015-12-01
Reviewed by Darin Adler.

* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitRequireObjectCoercible):
* bytecompiler/BytecodeGenerator.h:
* bytecompiler/NodesCodegen.cpp:
(JSC::ObjectPatternNode::bindValue):
* tests/stress/destructuring-assignment-require-object-coercible.js: Added.
(testTypeError):
(testOK):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@192899 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index a397c30..bd944fb 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,19 @@
+2015-12-01  Caitlin Potter  <caitp@igalia.com>
+
+        [JSC] add missing RequireObjectCoercible() step in destructuring
+        https://bugs.webkit.org/show_bug.cgi?id=151596
+
+        Reviewed by Darin Adler.
+
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::emitRequireObjectCoercible):
+        * bytecompiler/BytecodeGenerator.h:
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::ObjectPatternNode::bindValue):
+        * tests/stress/destructuring-assignment-require-object-coercible.js: Added.
+        (testTypeError):
+        (testOK):
+
 2015-12-01  Mark Lam  <mark.lam@apple.com>
 
         Refactor FTL sub snippet code to support general binary op snippets.
diff --git a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
index 1abbba4..41a64c9 100644
--- a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
+++ b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
@@ -3855,4 +3855,17 @@
     return result;
 }
 
+void BytecodeGenerator::emitRequireObjectCoercible(RegisterID* value, const String& error)
+{
+    // FIXME: op_jneq_null treats "undetectable" objects as null/undefined. RequireObjectCoercible
+    // thus incorrectly throws a TypeError for interfaces like HTMLAllCollection.
+    RefPtr<Label> target = newLabel();
+    size_t begin = instructions().size();
+    emitOpcode(op_jneq_null);
+    instructions().append(value->index());
+    instructions().append(target->bind(begin, instructions().size()));
+    emitThrowTypeError(error);
+    emitLabel(target.get());
+}
+
 } // namespace JSC
diff --git a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
index 07316b1..6ab390e 100644
--- a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
+++ b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
@@ -595,6 +595,7 @@
 
         RegisterID* emitIsObject(RegisterID* dst, RegisterID* src);
         RegisterID* emitIsUndefined(RegisterID* dst, RegisterID* src);
+        void emitRequireObjectCoercible(RegisterID* value, const String& error);
 
         RegisterID* emitIteratorNext(RegisterID* dst, RegisterID* iterator, const ThrowableExpressionData* node);
         void emitIteratorClose(RegisterID* iterator, const ThrowableExpressionData* node);
diff --git a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
index 242738e..6eee442 100644
--- a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
+++ b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
@@ -3337,6 +3337,7 @@
     
 void ObjectPatternNode::bindValue(BytecodeGenerator& generator, RegisterID* rhs) const
 {
+    generator.emitRequireObjectCoercible(rhs, ASCIILiteral("Right side of assignment cannot be destructured"));
     for (size_t i = 0; i < m_targetPatterns.size(); i++) {
         auto& target = m_targetPatterns[i];
         RefPtr<RegisterID> temp = generator.newTemporary();
diff --git a/Source/JavaScriptCore/tests/stress/destructuring-assignment-require-object-coercible.js b/Source/JavaScriptCore/tests/stress/destructuring-assignment-require-object-coercible.js
new file mode 100644
index 0000000..c25bb1b
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/destructuring-assignment-require-object-coercible.js
@@ -0,0 +1,68 @@
+function testTypeError(script, message) {
+    var error = null;
+    try {
+        eval(script);
+    } catch (e) {
+        error = e;
+    }
+    if (!error)
+        throw new Error("Expected type error not thrown by `" + script + "`");
+
+    if (String(error) !== message)
+        throw new Error("Bad error: " + String(error));
+}
+
+function testOK(script) {
+    var error = null;
+    try {
+        eval(script);
+    } catch (e) {
+        error = e;
+    }
+    if (error)
+        throw new Error("Bad error: " + String(error));
+}
+
+testTypeError(`({ } = null)`, "TypeError: Right side of assignment cannot be destructured");
+testTypeError(`({ a } = null)`, "TypeError: Right side of assignment cannot be destructured");
+testTypeError(`({ a: { b } = null } = { })`, "TypeError: Right side of assignment cannot be destructured");
+testTypeError(`({ a: { b } } = { a: null })`, "TypeError: Right side of assignment cannot be destructured");
+testTypeError(`({ } = undefined)`, "TypeError: Right side of assignment cannot be destructured");
+testTypeError(`({ a } = undefined)`, "TypeError: Right side of assignment cannot be destructured");
+testTypeError(`({ a: { b } = undefined } = { })`, "TypeError: Right side of assignment cannot be destructured");
+testTypeError(`({ a: { b } } = { a: undefined })`, "TypeError: Right side of assignment cannot be destructured");
+
+testOK(`({ } = 123)`);
+testOK(`({ a } = 123)`);
+testOK(`({ a: { b } = 123 } = { })`);
+testOK(`({ a: { b } } = { a: 123 })`);
+
+testOK(`({ } = 0.5)`);
+testOK(`({ a } = 0.5)`);
+testOK(`({ a: { b } = 0.5 } = { })`);
+testOK(`({ a: { b } } = { a: 0.5 })`);
+
+testOK(`({ } = NaN)`);
+testOK(`({ a } = NaN)`);
+testOK(`({ a: { b } = NaN } = { })`);
+testOK(`({ a: { b } } = { a: NaN })`);
+
+testOK(`({ } = true)`);
+testOK(`({ a } = true)`);
+testOK(`({ a: { b } = true } = { })`);
+testOK(`({ a: { b } } = { a: true })`);
+
+testOK(`({ } = {})`);
+testOK(`({ a } = {})`);
+testOK(`({ a: { b } = {} } = { })`);
+testOK(`({ a: { b } } = { a: {} })`);
+
+testOK(`({ } = [])`);
+testOK(`({ a } = [])`);
+testOK(`({ a: { b } = [] } = { })`);
+testOK(`({ a: { b } } = { a: [] })`);
+
+testOK(`({ } = /1/)`);
+testOK(`({ a } = /1/)`);
+testOK(`({ a: { b } = /1/ } = { })`);
+testOK(`({ a: { b } } = { a: /1/ })`);