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
  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
 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
 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
 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
 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
 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
 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
 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 {
 320         Object test1(Object arg);
 321         Object test2(Object arg);
 322         boolean compare(Object o1, Object o2);
 323     }
 324 
 325     // @bug 8053910: ScriptObjectMirror causing havoc with Invocation interface
 326     @Test
 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
 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
 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
 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
 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 }