1 /*
2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package jdk.nashorn.api.scripting.test;
27 
28 import static org.testng.Assert.assertEquals;
29 import static org.testng.Assert.assertFalse;
30 import static org.testng.Assert.assertTrue;
31 import static org.testng.Assert.fail;
32 
33 import java.nio.ByteBuffer;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.function.Function;
38 import javax.script.Bindings;
39 import javax.script.Invocable;
40 import javax.script.ScriptContext;
41 import javax.script.ScriptEngine;
42 import javax.script.ScriptEngineManager;
43 import javax.script.ScriptException;
44 import jdk.nashorn.api.scripting.AbstractJSObject;
45 import jdk.nashorn.api.scripting.JSObject;
46 import jdk.nashorn.api.scripting.ScriptObjectMirror;
47 import org.testng.annotations.Test;
48 
49 /**
50  * Tests to check jdk.nashorn.api.scripting.ScriptObjectMirror API.
51  *
52  * @test
53  * @run testng jdk.nashorn.api.scripting.test.ScriptObjectMirrorTest
54  */
55 @SuppressWarnings("javadoc")
56 public class ScriptObjectMirrorTest {
57 
58     @SuppressWarnings("unchecked")
59     @Test
reflectionTest()60     public void reflectionTest() throws ScriptException {
61         final ScriptEngineManager m = new ScriptEngineManager();
62         final ScriptEngine e = m.getEngineByName("nashorn");
63 
64         e.eval("var obj = { x: 344, y: 'nashorn' }");
65 
66         int count = 0;
67         Map<Object, Object> map = (Map<Object, Object>) e.get("obj");
68         assertFalse(map.isEmpty());
69         assertTrue(map.keySet().contains("x"));
70         assertTrue(map.containsKey("x"));
71         assertTrue(map.values().contains("nashorn"));
72         assertTrue(map.containsValue("nashorn"));
73         for (final Map.Entry<?, ?> ex : map.entrySet()) {
74             final Object key = ex.getKey();
75             if (key.equals("x")) {
76                 assertTrue(344 == ((Number) ex.getValue()).doubleValue());
77                 count++;
78             } else if (key.equals("y")) {
79                 assertEquals(ex.getValue(), "nashorn");
80                 count++;
81             }
82         }
83         assertEquals(2, count);
84         assertEquals(2, map.size());
85 
86         // add property
87         map.put("z", "hello");
88         assertEquals(e.eval("obj.z"), "hello");
89         assertEquals(map.get("z"), "hello");
90         assertTrue(map.keySet().contains("z"));
91         assertTrue(map.containsKey("z"));
92         assertTrue(map.values().contains("hello"));
93         assertTrue(map.containsValue("hello"));
94         assertEquals(map.size(), 3);
95 
96         final Map<Object, Object> newMap = new HashMap<>();
97         newMap.put("foo", 23.0);
98         newMap.put("bar", true);
99         map.putAll(newMap);
100 
101         assertEquals(e.eval("obj.foo"), 23.0);
102         assertEquals(e.eval("obj.bar"), true);
103 
104         // remove using map method
105         map.remove("foo");
106         assertEquals(e.eval("typeof obj.foo"), "undefined");
107 
108         count = 0;
109         e.eval("var arr = [ true, 'hello' ]");
110         map = (Map<Object, Object>) e.get("arr");
111         assertFalse(map.isEmpty());
112         assertTrue(map.containsKey("length"));
113         assertTrue(map.containsValue("hello"));
114         for (final Map.Entry<?, ?> ex : map.entrySet()) {
115             final Object key = ex.getKey();
116             if (key.equals("0")) {
117                 assertEquals(ex.getValue(), Boolean.TRUE);
118                 count++;
119             } else if (key.equals("1")) {
120                 assertEquals(ex.getValue(), "hello");
121                 count++;
122             }
123         }
124         assertEquals(count, 2);
125         assertEquals(map.size(), 2);
126 
127         // add element
128         map.put("2", "world");
129         assertEquals(map.get("2"), "world");
130         assertEquals(map.size(), 3);
131 
132         // remove all
133         map.clear();
134         assertTrue(map.isEmpty());
135         assertEquals(e.eval("typeof arr[0]"), "undefined");
136         assertEquals(e.eval("typeof arr[1]"), "undefined");
137         assertEquals(e.eval("typeof arr[2]"), "undefined");
138     }
139 
140     @Test
jsobjectTest()141     public void jsobjectTest() {
142         final ScriptEngineManager m = new ScriptEngineManager();
143         final ScriptEngine e = m.getEngineByName("nashorn");
144         try {
145             e.eval("var obj = { '1': 'world', func: function() { return this.bar; }, bar: 'hello' }");
146             final ScriptObjectMirror obj = (ScriptObjectMirror) e.get("obj");
147 
148             // try basic get on existing properties
149             if (!obj.getMember("bar").equals("hello")) {
150                 fail("obj.bar != 'hello'");
151             }
152 
153             if (!obj.getSlot(1).equals("world")) {
154                 fail("obj[1] != 'world'");
155             }
156 
157             if (!obj.callMember("func", new Object[0]).equals("hello")) {
158                 fail("obj.func() != 'hello'");
159             }
160 
161             // try setting properties
162             obj.setMember("bar", "new-bar");
163             obj.setSlot(1, "new-element-1");
164             if (!obj.getMember("bar").equals("new-bar")) {
165                 fail("obj.bar != 'new-bar'");
166             }
167 
168             if (!obj.getSlot(1).equals("new-element-1")) {
169                 fail("obj[1] != 'new-element-1'");
170             }
171 
172             // try adding properties
173             obj.setMember("prop", "prop-value");
174             obj.setSlot(12, "element-12");
175             if (!obj.getMember("prop").equals("prop-value")) {
176                 fail("obj.prop != 'prop-value'");
177             }
178 
179             if (!obj.getSlot(12).equals("element-12")) {
180                 fail("obj[12] != 'element-12'");
181             }
182 
183             // delete properties
184             obj.removeMember("prop");
185             if ("prop-value".equals(obj.getMember("prop"))) {
186                 fail("obj.prop is not deleted!");
187             }
188 
189             // Simple eval tests
190             assertEquals(obj.eval("typeof Object"), "function");
191             assertEquals(obj.eval("'nashorn'.substring(3)"), "horn");
192         } catch (final Exception exp) {
193             exp.printStackTrace();
194             fail(exp.getMessage());
195         }
196     }
197 
198     @Test
scriptObjectMirrorToStringTest()199     public void scriptObjectMirrorToStringTest() {
200         final ScriptEngineManager m = new ScriptEngineManager();
201         final ScriptEngine e = m.getEngineByName("nashorn");
202         try {
203             final Object obj = e.eval("new TypeError('wrong type')");
204             assertEquals(obj.toString(), "TypeError: wrong type", "toString returns wrong value");
205         } catch (final Throwable t) {
206             t.printStackTrace();
207             fail(t.getMessage());
208         }
209 
210         try {
211             final Object obj = e.eval("(function func() { print('hello'); })");
212             assertEquals(obj.toString(), "function func() { print('hello'); }", "toString returns wrong value");
213         } catch (final Throwable t) {
214             t.printStackTrace();
215             fail(t.getMessage());
216         }
217     }
218 
219     @Test
mirrorNewObjectGlobalFunctionTest()220     public void mirrorNewObjectGlobalFunctionTest() throws ScriptException {
221         final ScriptEngineManager m = new ScriptEngineManager();
222         final ScriptEngine e = m.getEngineByName("nashorn");
223         final ScriptEngine e2 = m.getEngineByName("nashorn");
224 
225         e.eval("function func() {}");
226         e2.put("foo", e.get("func"));
227         final ScriptObjectMirror e2global = (ScriptObjectMirror)e2.eval("this");
228         final Object newObj = ((ScriptObjectMirror)e2global.getMember("foo")).newObject();
229         assertTrue(newObj instanceof ScriptObjectMirror);
230     }
231 
232     @Test
mirrorNewObjectInstanceFunctionTest()233     public void mirrorNewObjectInstanceFunctionTest() throws ScriptException {
234         final ScriptEngineManager m = new ScriptEngineManager();
235         final ScriptEngine e = m.getEngineByName("nashorn");
236         final ScriptEngine e2 = m.getEngineByName("nashorn");
237 
238         e.eval("function func() {}");
239         e2.put("func", e.get("func"));
240         final ScriptObjectMirror e2obj = (ScriptObjectMirror)e2.eval("({ foo: func })");
241         final Object newObj = ((ScriptObjectMirror)e2obj.getMember("foo")).newObject();
242         assertTrue(newObj instanceof ScriptObjectMirror);
243     }
244 
245     @Test
indexPropertiesExternalBufferTest()246     public void indexPropertiesExternalBufferTest() throws ScriptException {
247         final ScriptEngineManager m = new ScriptEngineManager();
248         final ScriptEngine e = m.getEngineByName("nashorn");
249         final ScriptObjectMirror obj = (ScriptObjectMirror)e.eval("var obj = {}; obj");
250         final ByteBuffer buf = ByteBuffer.allocate(5);
251         int i;
252         for (i = 0; i < 5; i++) {
253             buf.put(i, (byte)(i+10));
254         }
255         obj.setIndexedPropertiesToExternalArrayData(buf);
256 
257         for (i = 0; i < 5; i++) {
258             assertEquals((byte)(i+10), ((Number)e.eval("obj[" + i + "]")).byteValue());
259         }
260 
261         e.eval("for (i = 0; i < 5; i++) obj[i] = 0");
262         for (i = 0; i < 5; i++) {
263             assertEquals((byte)0, ((Number)e.eval("obj[" + i + "]")).byteValue());
264             assertEquals((byte)0, buf.get(i));
265         }
266     }
267 
268     @Test
conversionTest()269     public void conversionTest() throws ScriptException {
270         final ScriptEngineManager m = new ScriptEngineManager();
271         final ScriptEngine e = m.getEngineByName("nashorn");
272         final ScriptObjectMirror arr = (ScriptObjectMirror)e.eval("[33, 45, 23]");
273         final int[] intArr = arr.to(int[].class);
274         assertEquals(intArr[0], 33);
275         assertEquals(intArr[1], 45);
276         assertEquals(intArr[2], 23);
277 
278         final List<?> list = arr.to(List.class);
279         assertEquals(list.get(0), 33);
280         assertEquals(list.get(1), 45);
281         assertEquals(list.get(2), 23);
282 
283         ScriptObjectMirror obj = (ScriptObjectMirror)e.eval(
284             "({ valueOf: function() { return 42 } })");
285         assertEquals(42.0, obj.to(Double.class));
286 
287         obj = (ScriptObjectMirror)e.eval(
288             "({ toString: function() { return 'foo' } })");
289         assertEquals("foo", obj.to(String.class));
290     }
291 
292     // @bug 8044000: Access to undefined property yields "null" instead of "undefined"
293     @Test
mapScriptObjectMirrorCallsiteTest()294     public void mapScriptObjectMirrorCallsiteTest() throws ScriptException {
295         final ScriptEngineManager m = new ScriptEngineManager();
296         final ScriptEngine engine = m.getEngineByName("nashorn");
297         final String TEST_SCRIPT = "typeof obj.foo";
298 
299         final Bindings global = engine.getContext().getBindings(ScriptContext.ENGINE_SCOPE);
300         engine.eval("var obj = java.util.Collections.emptyMap()");
301         // this will drive callsite "obj.foo" of TEST_SCRIPT
302         // to use "obj instanceof Map" as it's guard
303         engine.eval(TEST_SCRIPT, global);
304         // redefine 'obj' to be a script object
305         engine.eval("obj = {}");
306 
307         final Bindings newGlobal = engine.createBindings();
308         // transfer 'obj' from default global to new global
309         // new global will get a ScriptObjectMirror wrapping 'obj'
310         newGlobal.put("obj", global.get("obj"));
311 
312         // Every ScriptObjectMirror is a Map! If callsite "obj.foo"
313         // does not see the new 'obj' is a ScriptObjectMirror, it'll
314         // continue to use Map's get("obj.foo") instead of ScriptObjectMirror's
315         // getMember("obj.foo") - thereby getting null instead of undefined
316         assertEquals("undefined", engine.eval(TEST_SCRIPT, newGlobal));
317     }
318 
319     public interface MirrorCheckExample {
test1(Object arg)320         Object test1(Object arg);
test2(Object arg)321         Object test2(Object arg);
compare(Object o1, Object o2)322         boolean compare(Object o1, Object o2);
323     }
324 
325     // @bug 8053910: ScriptObjectMirror causing havoc with Invocation interface
326     @Test
checkMirrorToObject()327     public void checkMirrorToObject() throws Exception {
328         final ScriptEngineManager engineManager = new ScriptEngineManager();
329         final ScriptEngine engine = engineManager.getEngineByName("nashorn");
330         final Invocable invocable = (Invocable)engine;
331 
332         engine.eval("function test1(arg) { return { arg: arg }; }");
333         engine.eval("function test2(arg) { return arg; }");
334         engine.eval("function compare(arg1, arg2) { return arg1 == arg2; }");
335 
336         final Map<String, Object> map = new HashMap<>();
337         map.put("option", true);
338 
339         final MirrorCheckExample example = invocable.getInterface(MirrorCheckExample.class);
340 
341         final Object value1 = invocable.invokeFunction("test1", map);
342         final Object value2 = example.test1(map);
343         final Object value3 = invocable.invokeFunction("test2", value2);
344         final Object value4 = example.test2(value2);
345 
346         // check that Object type argument receives a ScriptObjectMirror
347         // when ScriptObject is passed
348         assertEquals(ScriptObjectMirror.class, value1.getClass());
349         assertEquals(ScriptObjectMirror.class, value2.getClass());
350         assertEquals(ScriptObjectMirror.class, value3.getClass());
351         assertEquals(ScriptObjectMirror.class, value4.getClass());
352         assertTrue((boolean)invocable.invokeFunction("compare", value1, value1));
353         assertTrue(example.compare(value1, value1));
354         assertTrue((boolean)invocable.invokeFunction("compare", value3, value4));
355         assertTrue(example.compare(value3, value4));
356     }
357 
358     // @bug 8053910: ScriptObjectMirror causing havoc with Invocation interface
359     @Test
mirrorUnwrapInterfaceMethod()360     public void mirrorUnwrapInterfaceMethod() throws Exception {
361         final ScriptEngineManager engineManager = new ScriptEngineManager();
362         final ScriptEngine engine = engineManager.getEngineByName("nashorn");
363         final Invocable invocable = (Invocable)engine;
364         engine.eval("function apply(obj) { " +
365             " return obj instanceof Packages.jdk.nashorn.api.scripting.ScriptObjectMirror; " +
366             "}");
367         @SuppressWarnings("unchecked")
368         final Function<Object,Object> func = invocable.getInterface(Function.class);
369         assertFalse((boolean)func.apply(engine.eval("({ x: 2 })")));
370     }
371 
372     // @bug 8055687: Wrong "this" passed to JSObject.eval call
373     @Test
checkThisForJSObjectEval()374     public void checkThisForJSObjectEval() throws Exception {
375         final ScriptEngineManager engineManager = new ScriptEngineManager();
376         final ScriptEngine e = engineManager.getEngineByName("nashorn");
377         final JSObject jsobj = (JSObject)e.eval("({foo: 23, bar: 'hello' })");
378         assertEquals(((Number)jsobj.eval("this.foo")).intValue(), 23);
379         assertEquals(jsobj.eval("this.bar"), "hello");
380         assertEquals(jsobj.eval("String(this)"), "[object Object]");
381         final Object global = e.eval("this");
382         assertFalse(global.equals(jsobj.eval("this")));
383     }
384 
385     @Test
topLevelAnonFuncStatement()386     public void topLevelAnonFuncStatement() throws Exception {
387         final ScriptEngineManager engineManager = new ScriptEngineManager();
388         final ScriptEngine e = engineManager.getEngineByName("nashorn");
389         final JSObject func = (JSObject)e.eval("function(x) { return x + ' world' }");
390         assertTrue(func.isFunction());
391         assertEquals(func.call(e.eval("this"), "hello"), "hello world");
392     }
393 
394     // @bug 8170565: JSObject call() is passed undefined for the argument 'thiz'
395     @Test
jsObjectThisTest()396     public void jsObjectThisTest() throws Exception {
397         final ScriptEngineManager engineManager = new ScriptEngineManager();
398         final ScriptEngine e = engineManager.getEngineByName("nashorn");
399         e.put("func", new AbstractJSObject() {
400             @Override
401             public boolean isFunction() { return true; }
402 
403             @Override
404             public Object call(Object thiz, Object...args) {
405                 return thiz;
406             }
407         });
408 
409         assertTrue((boolean)e.eval("func() === this"));
410 
411         // check that there is no blind undefined->Global translation!
412         assertTrue((boolean)e.eval("typeof(Function.prototype.call.call(func, undefined)) == 'undefined'"));
413 
414         // make sure that strict functions don't get translated this for scope calls!
415         e.put("sfunc", new AbstractJSObject() {
416             @Override
417             public boolean isFunction() { return true; }
418 
419             @Override
420             public boolean isStrictFunction() { return true; }
421 
422             @Override
423             public Object call(Object thiz, Object...args) {
424                 return thiz;
425             }
426         });
427 
428         assertTrue((boolean)e.eval("typeof sfunc() == 'undefined'"));
429     }
430 }
431