Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref. mozilla#780 fix Object.assign() when there's a prototype #1242

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/org/mozilla/javascript/ScriptableObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -2483,6 +2483,14 @@ boolean putImpl(Object key, int index, Scriptable start, Object value, boolean i
if (slot == null) {
return false;
}
// If this object is not the Receiver (start) object
// and if the slot is a Writeable data descriptor
// then invoke putImpl() on the Receiver (start) object
if (slot.isValueSlot()
&& !slot.isReadOnly(isThrow)
&& start instanceof ScriptableObject) {
return ((ScriptableObject) start).putImpl(key, index, start, value, isThrow);
}
} else if (!isExtensible) {
slot = slotMap.query(key, index);
if ((slot == null
Expand Down
23 changes: 19 additions & 4 deletions src/org/mozilla/javascript/Slot.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ boolean isSetterSlot() {
return false;
}

/**
* Return true if this Slot has the {@link ScriptableObject#READONLY read-only} {@link
* #getAttributes() attribute}. Callers may wish to check if this is a {@link #isValueSlot()
* value slot} first.
*
* @param isThrow set to true to throw a type error instead of returning true
* @return true if this slot is read-only and {@code isThrow} is false
*/
boolean isReadOnly(boolean isThrow) {
if ((attributes & ScriptableObject.READONLY) != 0) {
if (isThrow) {
throw ScriptRuntime.typeErrorById("msg.modify.readonly", name);
}
return true;
}
return false;
}

protected Slot(Slot oldSlot) {
name = oldSlot.name;
indexOrHash = oldSlot.indexOrHash;
Expand All @@ -61,10 +79,7 @@ public final boolean setValue(Object value, Scriptable owner, Scriptable start)
}

public boolean setValue(Object value, Scriptable owner, Scriptable start, boolean isThrow) {
if ((attributes & ScriptableObject.READONLY) != 0) {
if (isThrow) {
throw ScriptRuntime.typeErrorById("msg.modify.readonly", name);
}
if (isReadOnly(isThrow)) {
return true;
}
if (owner == start) {
Expand Down
70 changes: 70 additions & 0 deletions testsrc/org/mozilla/javascript/tests/es6/NativeObjectTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,76 @@ public void testAssignUnwritable() {
"error");
}

@Test
public void testAssignVariousKeyTypes() {
evaluateAndAssert(
"var strKeys = Object.assign({}, {a: 1, b: 2});\n"
+ "var arrayKeys = Object.assign({}, ['a', 'b']);\n"
+ "var res = 'strKeys: ' + JSON.stringify(strKeys) + "
+ "' arrayKeys:' + JSON.stringify(arrayKeys);\n"
+ "res",
"strKeys: {\"a\":1,\"b\":2} arrayKeys:{\"0\":\"a\",\"1\":\"b\"}");
}

@Test
public void testAssignWithPrototypeStringKeys() {
final String script =
"var targetProto = { a: 1, b: 2 };\n"
+ "var target = Object.create(targetProto);\n"
+ "var source = { b: 4, c: 5 };\n"
+ "var assigned = Object.assign(target, source);\n"
+ "var res = 'targetProto: ' + JSON.stringify(targetProto) + "
+ "' target: ' + JSON.stringify(target) + "
+ "' assigned: ' + JSON.stringify(assigned);\n"
+ "res";

evaluateAndAssert(
script,
"targetProto: {\"a\":1,\"b\":2} target: {\"b\":4,\"c\":5} assigned: {\"b\":4,\"c\":5}");
}

@Test
public void testAssignWithPrototypeNumericKeys() {
final String script =
"var targetProto = {0: 'a', 1: 'b'};\n"
+ "var target = Object.create(targetProto);\n"
+ "var source = {1: 'c', 2: 'd'};\n"
+ "var assigned = Object.assign(target, source);\n"
+ "var res = 'targetProto: ' + JSON.stringify(targetProto) + "
+ "' target: ' + JSON.stringify(target) + "
+ "' assigned: ' + JSON.stringify(assigned);\n"
+ "res";

evaluateAndAssert(
script,
"targetProto: {\"0\":\"a\",\"1\":\"b\"} target: {\"1\":\"c\",\"2\":\"d\"} assigned: {\"1\":\"c\",\"2\":\"d\"}");
}

@Test
public void testAssignWithPrototypeWithGettersAndSetters() {
final String script =
"var targetProto = {"
+ "_a: 1, get a() { return this._a }, set a(val) { this._a = val+10 },"
+ " _b: 2, get b() { return this._b }, set b(val) { this._b = val+10 }"
+ "};\n"
+ "var target = Object.create(targetProto);\n"
+ "var source = {"
+ "_b: 4, get b() { return this._b * 4 }, set b(val) { this._b = val+10 },"
+ " _c: 5, get c() { return this._c }, set c(val) { this._c = val+10 }};\n"
+ "var assigned = Object.assign(target, source);\n"
+ "var res = 'targetProto: ' + JSON.stringify(targetProto) + "
+ "' target: ' + JSON.stringify(target) + "
+ "' assigned: ' + JSON.stringify(assigned) + "
+ "' assigned.a: ' + assigned.a + "
+ "' assigned.b: ' + assigned.b + "
+ "' assigned.c: ' + assigned.c;\n"
+ "res";

evaluateAndAssert(
script,
"targetProto: {\"_a\":1,\"a\":1,\"_b\":2,\"b\":2} target: {\"_b\":26,\"_c\":5,\"c\":5} assigned: {\"_b\":26,\"_c\":5,\"c\":5} assigned.a: 1 assigned.b: 26 assigned.c: 5");
}

@Test
public void testSetPrototypeOfNull() {
evaluateAndAssert(
Expand Down