Thu, 07 Nov 2013 17:26:46 +0530
8027828: ClassCastException when converting return value of a Java method to boolean
Reviewed-by: jlaskey, attila
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 */
26 package jdk.nashorn.api.scripting;
28 import java.security.AccessControlContext;
29 import java.security.AccessController;
30 import java.security.Permissions;
31 import java.security.PrivilegedAction;
32 import java.security.ProtectionDomain;
33 import java.util.AbstractMap;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.Iterator;
38 import java.util.LinkedHashSet;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.concurrent.Callable;
43 import javax.script.Bindings;
44 import jdk.nashorn.internal.runtime.ConsString;
45 import jdk.nashorn.internal.runtime.Context;
46 import jdk.nashorn.internal.runtime.GlobalObject;
47 import jdk.nashorn.internal.runtime.JSType;
48 import jdk.nashorn.internal.runtime.ScriptFunction;
49 import jdk.nashorn.internal.runtime.ScriptObject;
50 import jdk.nashorn.internal.runtime.ScriptRuntime;
52 /**
53 * Mirror object that wraps a given Nashorn Script object.
54 */
55 public final class ScriptObjectMirror extends AbstractJSObject implements Bindings {
56 private static AccessControlContext getContextAccCtxt() {
57 final Permissions perms = new Permissions();
58 perms.add(new RuntimePermission(Context.NASHORN_GET_CONTEXT));
59 return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
60 }
62 private static final AccessControlContext GET_CONTEXT_ACC_CTXT = getContextAccCtxt();
64 private final ScriptObject sobj;
65 private final ScriptObject global;
66 private final boolean strict;
68 @Override
69 public boolean equals(final Object other) {
70 if (other instanceof ScriptObjectMirror) {
71 return sobj.equals(((ScriptObjectMirror)other).sobj);
72 }
74 return false;
75 }
77 @Override
78 public int hashCode() {
79 return sobj.hashCode();
80 }
82 @Override
83 public String toString() {
84 return inGlobal(new Callable<String>() {
85 @Override
86 public String call() {
87 return ScriptRuntime.safeToString(sobj);
88 }
89 });
90 }
92 // JSObject methods
94 @Override
95 public Object call(final Object thiz, final Object... args) {
96 final ScriptObject oldGlobal = Context.getGlobal();
97 final boolean globalChanged = (oldGlobal != global);
99 try {
100 if (globalChanged) {
101 Context.setGlobal(global);
102 }
104 if (sobj instanceof ScriptFunction) {
105 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
106 final Object self = globalChanged? wrap(thiz, oldGlobal) : thiz;
107 return wrap(ScriptRuntime.apply((ScriptFunction)sobj, unwrap(self, global), unwrapArray(modArgs, global)), global);
108 }
110 throw new RuntimeException("not a function: " + toString());
111 } catch (final RuntimeException | Error e) {
112 throw e;
113 } catch (final Throwable t) {
114 throw new RuntimeException(t);
115 } finally {
116 if (globalChanged) {
117 Context.setGlobal(oldGlobal);
118 }
119 }
120 }
122 @Override
123 public Object newObject(final Object... args) {
124 final ScriptObject oldGlobal = Context.getGlobal();
125 final boolean globalChanged = (oldGlobal != global);
127 try {
128 if (globalChanged) {
129 Context.setGlobal(global);
130 }
132 if (sobj instanceof ScriptFunction) {
133 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
134 return wrap(ScriptRuntime.construct((ScriptFunction)sobj, unwrapArray(modArgs, global)), global);
135 }
137 throw new RuntimeException("not a constructor: " + toString());
138 } catch (final RuntimeException | Error e) {
139 throw e;
140 } catch (final Throwable t) {
141 throw new RuntimeException(t);
142 } finally {
143 if (globalChanged) {
144 Context.setGlobal(oldGlobal);
145 }
146 }
147 }
149 @Override
150 public Object eval(final String s) {
151 return inGlobal(new Callable<Object>() {
152 @Override
153 public Object call() {
154 final Context context = AccessController.doPrivileged(
155 new PrivilegedAction<Context>() {
156 @Override
157 public Context run() {
158 return Context.getContext();
159 }
160 }, GET_CONTEXT_ACC_CTXT);
161 return wrap(context.eval(global, s, null, null, false), global);
162 }
163 });
164 }
166 public Object callMember(final String functionName, final Object... args) {
167 functionName.getClass(); // null check
168 final ScriptObject oldGlobal = Context.getGlobal();
169 final boolean globalChanged = (oldGlobal != global);
171 try {
172 if (globalChanged) {
173 Context.setGlobal(global);
174 }
176 final Object val = sobj.get(functionName);
177 if (val instanceof ScriptFunction) {
178 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
179 return wrap(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)), global);
180 } else if (val instanceof JSObject && ((JSObject)val).isFunction()) {
181 return ((JSObject)val).call(sobj, args);
182 }
184 throw new NoSuchMethodException("No such function " + functionName);
185 } catch (final RuntimeException | Error e) {
186 throw e;
187 } catch (final Throwable t) {
188 throw new RuntimeException(t);
189 } finally {
190 if (globalChanged) {
191 Context.setGlobal(oldGlobal);
192 }
193 }
194 }
196 @Override
197 public Object getMember(final String name) {
198 name.getClass();
199 return inGlobal(new Callable<Object>() {
200 @Override public Object call() {
201 return wrap(sobj.get(name), global);
202 }
203 });
204 }
206 @Override
207 public Object getSlot(final int index) {
208 return inGlobal(new Callable<Object>() {
209 @Override public Object call() {
210 return wrap(sobj.get(index), global);
211 }
212 });
213 }
215 @Override
216 public boolean hasMember(final String name) {
217 name.getClass();
218 return inGlobal(new Callable<Boolean>() {
219 @Override public Boolean call() {
220 return sobj.has(name);
221 }
222 });
223 }
225 @Override
226 public boolean hasSlot(final int slot) {
227 return inGlobal(new Callable<Boolean>() {
228 @Override public Boolean call() {
229 return sobj.has(slot);
230 }
231 });
232 }
234 @Override
235 public void removeMember(final String name) {
236 name.getClass();
237 remove(name);
238 }
240 @Override
241 public void setMember(final String name, final Object value) {
242 name.getClass();
243 put(name, value);
244 }
246 @Override
247 public void setSlot(final int index, final Object value) {
248 inGlobal(new Callable<Void>() {
249 @Override public Void call() {
250 sobj.set(index, unwrap(value, global), strict);
251 return null;
252 }
253 });
254 }
256 @Override
257 public boolean isInstance(final Object obj) {
258 if (! (obj instanceof ScriptObjectMirror)) {
259 return false;
260 }
262 final ScriptObjectMirror instance = (ScriptObjectMirror)obj;
263 // if not belongs to my global scope, return false
264 if (global != instance.global) {
265 return false;
266 }
268 return inGlobal(new Callable<Boolean>() {
269 @Override public Boolean call() {
270 return sobj.isInstance(instance.sobj);
271 }
272 });
273 }
275 @Override
276 public String getClassName() {
277 return sobj.getClassName();
278 }
280 @Override
281 public boolean isFunction() {
282 return sobj instanceof ScriptFunction;
283 }
285 @Override
286 public boolean isStrictFunction() {
287 return isFunction() && ((ScriptFunction)sobj).isStrict();
288 }
290 @Override
291 public boolean isArray() {
292 return sobj.isArray();
293 }
295 // javax.script.Bindings methods
297 @Override
298 public void clear() {
299 inGlobal(new Callable<Object>() {
300 @Override public Object call() {
301 sobj.clear(strict);
302 return null;
303 }
304 });
305 }
307 @Override
308 public boolean containsKey(final Object key) {
309 return inGlobal(new Callable<Boolean>() {
310 @Override public Boolean call() {
311 return sobj.containsKey(unwrap(key, global));
312 }
313 });
314 }
316 @Override
317 public boolean containsValue(final Object value) {
318 return inGlobal(new Callable<Boolean>() {
319 @Override public Boolean call() {
320 return sobj.containsValue(unwrap(value, global));
321 }
322 });
323 }
325 @Override
326 public Set<Map.Entry<String, Object>> entrySet() {
327 return inGlobal(new Callable<Set<Map.Entry<String, Object>>>() {
328 @Override public Set<Map.Entry<String, Object>> call() {
329 final Iterator<String> iter = sobj.propertyIterator();
330 final Set<Map.Entry<String, Object>> entries = new LinkedHashSet<>();
332 while (iter.hasNext()) {
333 final String key = iter.next();
334 final Object value = translateUndefined(wrap(sobj.get(key), global));
335 entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value));
336 }
338 return Collections.unmodifiableSet(entries);
339 }
340 });
341 }
343 @Override
344 public Object get(final Object key) {
345 return inGlobal(new Callable<Object>() {
346 @Override public Object call() {
347 return translateUndefined(wrap(sobj.get(key), global));
348 }
349 });
350 }
352 @Override
353 public boolean isEmpty() {
354 return inGlobal(new Callable<Boolean>() {
355 @Override public Boolean call() {
356 return sobj.isEmpty();
357 }
358 });
359 }
361 @Override
362 public Set<String> keySet() {
363 return inGlobal(new Callable<Set<String>>() {
364 @Override public Set<String> call() {
365 final Iterator<String> iter = sobj.propertyIterator();
366 final Set<String> keySet = new LinkedHashSet<>();
368 while (iter.hasNext()) {
369 keySet.add(iter.next());
370 }
372 return Collections.unmodifiableSet(keySet);
373 }
374 });
375 }
377 @Override
378 public Object put(final String key, final Object value) {
379 final ScriptObject oldGlobal = Context.getGlobal();
380 final boolean globalChanged = (oldGlobal != global);
381 return inGlobal(new Callable<Object>() {
382 @Override public Object call() {
383 final Object modValue = globalChanged? wrap(value, oldGlobal) : value;
384 return translateUndefined(wrap(sobj.put(key, unwrap(modValue, global), strict), global));
385 }
386 });
387 }
389 @Override
390 public void putAll(final Map<? extends String, ? extends Object> map) {
391 final ScriptObject oldGlobal = Context.getGlobal();
392 final boolean globalChanged = (oldGlobal != global);
393 inGlobal(new Callable<Object>() {
394 @Override public Object call() {
395 for (final Map.Entry<? extends String, ? extends Object> entry : map.entrySet()) {
396 final Object value = entry.getValue();
397 final Object modValue = globalChanged? wrap(value, oldGlobal) : value;
398 sobj.set(entry.getKey(), unwrap(modValue, global), strict);
399 }
400 return null;
401 }
402 });
403 }
405 @Override
406 public Object remove(final Object key) {
407 return inGlobal(new Callable<Object>() {
408 @Override public Object call() {
409 return wrap(sobj.remove(unwrap(key, global), strict), global);
410 }
411 });
412 }
414 /**
415 * Delete a property from this object.
416 *
417 * @param key the property to be deleted
418 *
419 * @return if the delete was successful or not
420 */
421 public boolean delete(final Object key) {
422 return inGlobal(new Callable<Boolean>() {
423 @Override public Boolean call() {
424 return sobj.delete(unwrap(key, global), strict);
425 }
426 });
427 }
429 @Override
430 public int size() {
431 return inGlobal(new Callable<Integer>() {
432 @Override public Integer call() {
433 return sobj.size();
434 }
435 });
436 }
438 @Override
439 public Collection<Object> values() {
440 return inGlobal(new Callable<Collection<Object>>() {
441 @Override public Collection<Object> call() {
442 final List<Object> values = new ArrayList<>(size());
443 final Iterator<Object> iter = sobj.valueIterator();
445 while (iter.hasNext()) {
446 values.add(translateUndefined(wrap(iter.next(), global)));
447 }
449 return Collections.unmodifiableList(values);
450 }
451 });
452 }
454 // Support for ECMAScript Object API on mirrors
456 /**
457 * Return the __proto__ of this object.
458 * @return __proto__ object.
459 */
460 public Object getProto() {
461 return inGlobal(new Callable<Object>() {
462 @Override public Object call() {
463 return wrap(sobj.getProto(), global);
464 }
465 });
466 }
468 /**
469 * Set the __proto__ of this object.
470 * @param proto new proto for this object
471 */
472 public void setProto(final Object proto) {
473 inGlobal(new Callable<Void>() {
474 @Override public Void call() {
475 sobj.setProtoCheck(unwrap(proto, global));
476 return null;
477 }
478 });
479 }
481 /**
482 * ECMA 8.12.1 [[GetOwnProperty]] (P)
483 *
484 * @param key property key
485 *
486 * @return Returns the Property Descriptor of the named own property of this
487 * object, or undefined if absent.
488 */
489 public Object getOwnPropertyDescriptor(final String key) {
490 return inGlobal(new Callable<Object>() {
491 @Override public Object call() {
492 return wrap(sobj.getOwnPropertyDescriptor(key), global);
493 }
494 });
495 }
497 /**
498 * return an array of own property keys associated with the object.
499 *
500 * @param all True if to include non-enumerable keys.
501 * @return Array of keys.
502 */
503 public String[] getOwnKeys(final boolean all) {
504 return inGlobal(new Callable<String[]>() {
505 @Override public String[] call() {
506 return sobj.getOwnKeys(all);
507 }
508 });
509 }
511 /**
512 * Flag this script object as non extensible
513 *
514 * @return the object after being made non extensible
515 */
516 public ScriptObjectMirror preventExtensions() {
517 return inGlobal(new Callable<ScriptObjectMirror>() {
518 @Override public ScriptObjectMirror call() {
519 sobj.preventExtensions();
520 return ScriptObjectMirror.this;
521 }
522 });
523 }
525 /**
526 * Check if this script object is extensible
527 * @return true if extensible
528 */
529 public boolean isExtensible() {
530 return inGlobal(new Callable<Boolean>() {
531 @Override public Boolean call() {
532 return sobj.isExtensible();
533 }
534 });
535 }
537 /**
538 * ECMAScript 15.2.3.8 - seal implementation
539 * @return the sealed script object
540 */
541 public ScriptObjectMirror seal() {
542 return inGlobal(new Callable<ScriptObjectMirror>() {
543 @Override public ScriptObjectMirror call() {
544 sobj.seal();
545 return ScriptObjectMirror.this;
546 }
547 });
548 }
550 /**
551 * Check whether this script object is sealed
552 * @return true if sealed
553 */
554 public boolean isSealed() {
555 return inGlobal(new Callable<Boolean>() {
556 @Override public Boolean call() {
557 return sobj.isSealed();
558 }
559 });
560 }
562 /**
563 * ECMA 15.2.39 - freeze implementation. Freeze this script object
564 * @return the frozen script object
565 */
566 public ScriptObjectMirror freeze() {
567 return inGlobal(new Callable<ScriptObjectMirror>() {
568 @Override public ScriptObjectMirror call() {
569 sobj.freeze();
570 return ScriptObjectMirror.this;
571 }
572 });
573 }
575 /**
576 * Check whether this script object is frozen
577 * @return true if frozen
578 */
579 public boolean isFrozen() {
580 return inGlobal(new Callable<Boolean>() {
581 @Override public Boolean call() {
582 return sobj.isFrozen();
583 }
584 });
585 }
587 /**
588 * Utility to check if given object is ECMAScript undefined value
589 *
590 * @param obj object to check
591 * @return true if 'obj' is ECMAScript undefined value
592 */
593 public static boolean isUndefined(final Object obj) {
594 return obj == ScriptRuntime.UNDEFINED;
595 }
597 /**
598 * Utilitity to convert this script object to the given type.
599 *
600 * @param type destination type to convert to
601 * @return converted object
602 */
603 public <T> T to(final Class<T> type) {
604 return inGlobal(new Callable<T>() {
605 @Override
606 public T call() {
607 return type.cast(ScriptUtils.convert(sobj, type));
608 }
609 });
610 }
612 /**
613 * Make a script object mirror on given object if needed. Also converts ConsString instances to Strings.
614 *
615 * @param obj object to be wrapped/converted
616 * @param homeGlobal global to which this object belongs. Not used for ConsStrings.
617 * @return wrapped/converted object
618 */
619 public static Object wrap(final Object obj, final Object homeGlobal) {
620 if(obj instanceof ScriptObject) {
621 return homeGlobal instanceof ScriptObject ? new ScriptObjectMirror((ScriptObject)obj, (ScriptObject)homeGlobal) : obj;
622 }
623 if(obj instanceof ConsString) {
624 return obj.toString();
625 }
626 return obj;
627 }
629 /**
630 * Unwrap a script object mirror if needed.
631 *
632 * @param obj object to be unwrapped
633 * @param homeGlobal global to which this object belongs
634 * @return unwrapped object
635 */
636 public static Object unwrap(final Object obj, final Object homeGlobal) {
637 if (obj instanceof ScriptObjectMirror) {
638 final ScriptObjectMirror mirror = (ScriptObjectMirror)obj;
639 return (mirror.global == homeGlobal)? mirror.sobj : obj;
640 }
642 return obj;
643 }
645 /**
646 * Wrap an array of object to script object mirrors if needed.
647 *
648 * @param args array to be unwrapped
649 * @param homeGlobal global to which this object belongs
650 * @return wrapped array
651 */
652 public static Object[] wrapArray(final Object[] args, final Object homeGlobal) {
653 if (args == null || args.length == 0) {
654 return args;
655 }
657 final Object[] newArgs = new Object[args.length];
658 int index = 0;
659 for (final Object obj : args) {
660 newArgs[index] = wrap(obj, homeGlobal);
661 index++;
662 }
663 return newArgs;
664 }
666 /**
667 * Unwrap an array of script object mirrors if needed.
668 *
669 * @param args array to be unwrapped
670 * @param homeGlobal global to which this object belongs
671 * @return unwrapped array
672 */
673 public static Object[] unwrapArray(final Object[] args, final Object homeGlobal) {
674 if (args == null || args.length == 0) {
675 return args;
676 }
678 final Object[] newArgs = new Object[args.length];
679 int index = 0;
680 for (final Object obj : args) {
681 newArgs[index] = unwrap(obj, homeGlobal);
682 index++;
683 }
684 return newArgs;
685 }
687 // package-privates below this.
689 ScriptObjectMirror(final ScriptObject sobj, final ScriptObject global) {
690 assert sobj != null : "ScriptObjectMirror on null!";
691 assert global instanceof GlobalObject : "global is not a GlobalObject";
693 this.sobj = sobj;
694 this.global = global;
695 this.strict = ((GlobalObject)global).isStrictContext();
696 }
698 // accessors for script engine
699 ScriptObject getScriptObject() {
700 return sobj;
701 }
703 ScriptObject getHomeGlobal() {
704 return global;
705 }
707 static Object translateUndefined(Object obj) {
708 return (obj == ScriptRuntime.UNDEFINED)? null : obj;
709 }
711 // internals only below this.
712 private <V> V inGlobal(final Callable<V> callable) {
713 final ScriptObject oldGlobal = Context.getGlobal();
714 final boolean globalChanged = (oldGlobal != global);
715 if (globalChanged) {
716 Context.setGlobal(global);
717 }
718 try {
719 return callable.call();
720 } catch (final RuntimeException e) {
721 throw e;
722 } catch (final Exception e) {
723 throw new AssertionError("Cannot happen", e);
724 } finally {
725 if (globalChanged) {
726 Context.setGlobal(oldGlobal);
727 }
728 }
729 }
731 @Override
732 public double toNumber() {
733 return inGlobal(new Callable<Double>() {
734 @Override public Double call() {
735 return JSType.toNumber(sobj);
736 }
737 });
738 }
739 }