8157160: JSON.stringify does not work on ScriptObjectMirror objects
Reviewed-by: hannesw, mhaupt
This commit is contained in:
parent
a3cdceaa83
commit
2479973df8
nashorn
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects
test/script/basic
@ -35,7 +35,10 @@ import java.util.IdentityHashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
import jdk.nashorn.api.scripting.JSObject;
|
||||
import jdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||
import jdk.nashorn.internal.objects.annotations.Attribute;
|
||||
import jdk.nashorn.internal.objects.annotations.Function;
|
||||
import jdk.nashorn.internal.objects.annotations.ScriptClass;
|
||||
@ -68,6 +71,17 @@ public final class NativeJSON extends ScriptObject {
|
||||
});
|
||||
}
|
||||
|
||||
private static final Object JSOBJECT_INVOKER = new Object();
|
||||
|
||||
private static MethodHandle getJSOBJECT_INVOKER() {
|
||||
return Global.instance().getDynamicInvoker(JSOBJECT_INVOKER,
|
||||
new Callable<MethodHandle>() {
|
||||
@Override
|
||||
public MethodHandle call() {
|
||||
return Bootstrap.createDynamicCallInvoker(Object.class, Object.class, Object.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static final Object REPLACER_INVOKER = new Object();
|
||||
|
||||
@ -77,7 +91,7 @@ public final class NativeJSON extends ScriptObject {
|
||||
@Override
|
||||
public MethodHandle call() {
|
||||
return Bootstrap.createDynamicCallInvoker(Object.class,
|
||||
ScriptFunction.class, ScriptObject.class, Object.class, Object.class);
|
||||
Object.class, Object.class, Object.class, Object.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -127,9 +141,10 @@ public final class NativeJSON extends ScriptObject {
|
||||
final StringifyState state = new StringifyState();
|
||||
|
||||
// If there is a replacer, it must be a function or an array.
|
||||
if (replacer instanceof ScriptFunction) {
|
||||
state.replacerFunction = (ScriptFunction) replacer;
|
||||
if (Bootstrap.isCallable(replacer)) {
|
||||
state.replacerFunction = replacer;
|
||||
} else if (isArray(replacer) ||
|
||||
isJSObjectArray(replacer) ||
|
||||
replacer instanceof Iterable ||
|
||||
(replacer != null && replacer.getClass().isArray())) {
|
||||
|
||||
@ -201,18 +216,19 @@ public final class NativeJSON extends ScriptObject {
|
||||
// stringify helpers.
|
||||
|
||||
private static class StringifyState {
|
||||
final Map<ScriptObject, ScriptObject> stack = new IdentityHashMap<>();
|
||||
final Map<Object, Object> stack = new IdentityHashMap<>();
|
||||
|
||||
StringBuilder indent = new StringBuilder();
|
||||
String gap = "";
|
||||
List<String> propertyList = null;
|
||||
ScriptFunction replacerFunction = null;
|
||||
Object replacerFunction = null;
|
||||
}
|
||||
|
||||
// Spec: The abstract operation Str(key, holder).
|
||||
private static Object str(final Object key, final ScriptObject holder, final StringifyState state) {
|
||||
Object value = holder.get(key);
|
||||
private static Object str(final Object key, final Object holder, final StringifyState state) {
|
||||
assert holder instanceof ScriptObject || holder instanceof JSObject;
|
||||
|
||||
Object value = getProperty(holder, key);
|
||||
try {
|
||||
if (value instanceof ScriptObject) {
|
||||
final InvokeByName toJSONInvoker = getTO_JSON();
|
||||
@ -221,6 +237,12 @@ public final class NativeJSON extends ScriptObject {
|
||||
if (Bootstrap.isCallable(toJSON)) {
|
||||
value = toJSONInvoker.getInvoker().invokeExact(toJSON, svalue, key);
|
||||
}
|
||||
} else if (value instanceof JSObject) {
|
||||
final JSObject jsObj = (JSObject)value;
|
||||
final Object toJSON = jsObj.getMember("toJSON");
|
||||
if (Bootstrap.isCallable(toJSON)) {
|
||||
value = getJSOBJECT_INVOKER().invokeExact(toJSON, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.replacerFunction != null) {
|
||||
@ -262,10 +284,10 @@ public final class NativeJSON extends ScriptObject {
|
||||
|
||||
final JSType type = JSType.of(value);
|
||||
if (type == JSType.OBJECT) {
|
||||
if (isArray(value)) {
|
||||
return JA((ScriptObject)value, state);
|
||||
} else if (value instanceof ScriptObject) {
|
||||
return JO((ScriptObject)value, state);
|
||||
if (isArray(value) || isJSObjectArray(value)) {
|
||||
return JA(value, state);
|
||||
} else if (value instanceof ScriptObject || value instanceof JSObject) {
|
||||
return JO(value, state);
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,7 +295,9 @@ public final class NativeJSON extends ScriptObject {
|
||||
}
|
||||
|
||||
// Spec: The abstract operation JO(value) serializes an object.
|
||||
private static String JO(final ScriptObject value, final StringifyState state) {
|
||||
private static String JO(final Object value, final StringifyState state) {
|
||||
assert value instanceof ScriptObject || value instanceof JSObject;
|
||||
|
||||
if (state.stack.containsKey(value)) {
|
||||
throw typeError("JSON.stringify.cyclic");
|
||||
}
|
||||
@ -284,7 +308,8 @@ public final class NativeJSON extends ScriptObject {
|
||||
|
||||
final StringBuilder finalStr = new StringBuilder();
|
||||
final List<Object> partial = new ArrayList<>();
|
||||
final List<String> k = state.propertyList == null ? Arrays.asList(value.getOwnKeys(false)) : state.propertyList;
|
||||
final List<String> k = state.propertyList == null ?
|
||||
Arrays.asList(getOwnKeys(value)) : state.propertyList;
|
||||
|
||||
for (final Object p : k) {
|
||||
final Object strP = str(p, value, state);
|
||||
@ -349,7 +374,9 @@ public final class NativeJSON extends ScriptObject {
|
||||
}
|
||||
|
||||
// Spec: The abstract operation JA(value) serializes an array.
|
||||
private static Object JA(final ScriptObject value, final StringifyState state) {
|
||||
private static Object JA(final Object value, final StringifyState state) {
|
||||
assert value instanceof ScriptObject || value instanceof JSObject;
|
||||
|
||||
if (state.stack.containsKey(value)) {
|
||||
throw typeError("JSON.stringify.cyclic");
|
||||
}
|
||||
@ -359,7 +386,7 @@ public final class NativeJSON extends ScriptObject {
|
||||
state.indent.append(state.gap);
|
||||
final List<Object> partial = new ArrayList<>();
|
||||
|
||||
final int length = JSType.toInteger(value.getLength());
|
||||
final int length = JSType.toInteger(getLength(value));
|
||||
int index = 0;
|
||||
|
||||
while (index < length) {
|
||||
@ -413,4 +440,48 @@ public final class NativeJSON extends ScriptObject {
|
||||
|
||||
return finalStr.toString();
|
||||
}
|
||||
|
||||
private static String[] getOwnKeys(final Object obj) {
|
||||
if (obj instanceof ScriptObject) {
|
||||
return ((ScriptObject)obj).getOwnKeys(false);
|
||||
} else if (obj instanceof ScriptObjectMirror) {
|
||||
return ((ScriptObjectMirror)obj).getOwnKeys(false);
|
||||
} else if (obj instanceof JSObject) {
|
||||
// No notion of "own keys" or "proto" for general JSObject! We just
|
||||
// return all keys of the object. This will be useful for POJOs
|
||||
// implementing JSObject interface.
|
||||
return ((JSObject)obj).keySet().toArray(new String[0]);
|
||||
} else {
|
||||
throw new AssertionError("should not reach here");
|
||||
}
|
||||
}
|
||||
|
||||
private static Object getLength(final Object obj) {
|
||||
if (obj instanceof ScriptObject) {
|
||||
return ((ScriptObject)obj).getLength();
|
||||
} else if (obj instanceof JSObject) {
|
||||
return ((JSObject)obj).getMember("length");
|
||||
} else {
|
||||
throw new AssertionError("should not reach here");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isJSObjectArray(final Object obj) {
|
||||
return (obj instanceof JSObject) && ((JSObject)obj).isArray();
|
||||
}
|
||||
|
||||
private static Object getProperty(final Object holder, final Object key) {
|
||||
if (holder instanceof ScriptObject) {
|
||||
return ((ScriptObject)holder).get(key);
|
||||
} else if (holder instanceof JSObject) {
|
||||
JSObject jsObj = (JSObject)holder;
|
||||
if (key instanceof Integer) {
|
||||
return jsObj.getSlot((Integer)key);
|
||||
} else {
|
||||
return jsObj.getMember(Objects.toString(key));
|
||||
}
|
||||
} else {
|
||||
return new AssertionError("should not reach here");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
113
nashorn/test/script/basic/JDK-8157160.js
Normal file
113
nashorn/test/script/basic/JDK-8157160.js
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* JDK-8157160: JSON.stringify does not work on ScriptObjectMirror objects
|
||||
*
|
||||
* @test
|
||||
* @option -scripting
|
||||
* @run
|
||||
*/
|
||||
|
||||
var SM = Java.type("javax.script.ScriptEngineManager");
|
||||
var AJSO = Java.type("jdk.nashorn.api.scripting.AbstractJSObject");
|
||||
var Supplier = Java.type("java.util.function.Supplier");
|
||||
|
||||
var engine = new SM().getEngineByName("nashorn");
|
||||
|
||||
// JSON stringify ScriptObjectMirror instances
|
||||
print(JSON.stringify(engine.eval("({ foo : 42 })")));
|
||||
print(JSON.stringify(engine.eval("([5, 6, 76, 7])")));
|
||||
print(JSON.stringify(engine.eval(<<EOF
|
||||
({
|
||||
toJSON: function() "hello"
|
||||
})
|
||||
EOF
|
||||
)));
|
||||
|
||||
print(JSON.stringify(engine.eval(<<EOF
|
||||
obj = {
|
||||
name: 'nashorn',
|
||||
versions: [ 'es5.1', 'es6' ]
|
||||
}
|
||||
EOF
|
||||
)));
|
||||
|
||||
var dm = engine.eval("new Date()");
|
||||
print('"' + dm.toJSON() + '"' == JSON.stringify(dm));
|
||||
|
||||
// JSON stringifying an arbitrary JSObject impl.
|
||||
var jsObj = new AJSO() {
|
||||
keySet: function() {
|
||||
var keys = new java.util.HashSet();
|
||||
keys.add("x");
|
||||
keys.add("y");
|
||||
return keys;
|
||||
},
|
||||
|
||||
getMember: function(name) {
|
||||
if (name == "x") {
|
||||
return 42;
|
||||
} else if (name == "y") {
|
||||
return "hello";
|
||||
}
|
||||
}
|
||||
};
|
||||
print(JSON.stringify(jsObj));
|
||||
|
||||
// try toJSON implementation on JSObject
|
||||
var jsObj2 = new AJSO() {
|
||||
getMember: function(name) {
|
||||
if (name == 'toJSON') {
|
||||
return function() {
|
||||
return "my json representation";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
print(JSON.stringify(jsObj2));
|
||||
|
||||
var jsObj3 = new AJSO() {
|
||||
getMember: function(name) {
|
||||
if (name == 'toJSON') {
|
||||
return new Supplier() {
|
||||
"get": function() {
|
||||
return "value from toJSON function";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
print(JSON.stringify(jsObj3));
|
||||
|
||||
// replacer function from another script world
|
||||
print(JSON.stringify({
|
||||
foo: "hello"
|
||||
}, engine.eval(<<EOF
|
||||
function (key, value) {
|
||||
if (key == "foo") {
|
||||
return value.toUpperCase()
|
||||
}
|
||||
return value;
|
||||
}
|
||||
EOF)));
|
9
nashorn/test/script/basic/JDK-8157160.js.EXPECTED
Normal file
9
nashorn/test/script/basic/JDK-8157160.js.EXPECTED
Normal file
@ -0,0 +1,9 @@
|
||||
{"foo":42}
|
||||
[5,6,76,7]
|
||||
"hello"
|
||||
{"name":"nashorn","versions":["es5.1","es6"]}
|
||||
true
|
||||
{"x":42,"y":"hello"}
|
||||
"my json representation"
|
||||
"value from toJSON function"
|
||||
{"foo":"HELLO"}
|
Loading…
x
Reference in New Issue
Block a user