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.internal.runtime;
27
28 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall;
29 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
30 import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError;
31 import static jdk.nashorn.internal.runtime.ECMAErrors.syntaxError;
32 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
33 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt;
34
35 import java.lang.invoke.MethodHandle;
36 import java.lang.invoke.MethodHandles;
37 import java.lang.reflect.Array;
38 import java.util.Collections;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.Locale;
42 import java.util.Map;
43 import java.util.NoSuchElementException;
44 import java.util.Objects;
45 import jdk.internal.dynalink.beans.StaticClass;
46 import jdk.nashorn.api.scripting.JSObject;
47 import jdk.nashorn.api.scripting.ScriptObjectMirror;
48 import jdk.nashorn.internal.codegen.CompilerConstants.Call;
49 import jdk.nashorn.internal.ir.debug.JSONWriter;
50 import jdk.nashorn.internal.objects.Global;
51 import jdk.nashorn.internal.parser.Lexer;
52 import jdk.nashorn.internal.runtime.linker.Bootstrap;
53
54
55 /**
56 * Utilities to be called by JavaScript runtime API and generated classes.
57 */
58
59 public final class ScriptRuntime {
60 private ScriptRuntime() {
61 }
62
63 /** Singleton representing the empty array object '[]' */
64 public static final Object[] EMPTY_ARRAY = new Object[0];
65
66 /** Unique instance of undefined. */
67 public static final Undefined UNDEFINED = Undefined.getUndefined();
68
69 /**
70 * Unique instance of undefined used to mark empty array slots.
71 * Can't escape the array.
72 */
73 public static final Undefined EMPTY = Undefined.getEmpty();
74
75 /** Method handle to generic + operator, operating on objects */
76 public static final Call ADD = staticCallNoLookup(ScriptRuntime.class, "ADD", Object.class, Object.class, Object.class);
77
78 /** Method handle to generic === operator, operating on objects */
79 public static final Call EQ_STRICT = staticCallNoLookup(ScriptRuntime.class, "EQ_STRICT", boolean.class, Object.class, Object.class);
80
81 /** Method handle used to enter a {@code with} scope at runtime. */
82 public static final Call OPEN_WITH = staticCallNoLookup(ScriptRuntime.class, "openWith", ScriptObject.class, ScriptObject.class, Object.class);
83
84 /** Method handle used to exit a {@code with} scope at runtime. */
85 public static final Call CLOSE_WITH = staticCallNoLookup(ScriptRuntime.class, "closeWith", ScriptObject.class, ScriptObject.class);
86
87 /**
88 * Method used to place a scope's variable into the Global scope, which has to be done for the
89 * properties declared at outermost script level.
90 */
91 public static final Call MERGE_SCOPE = staticCallNoLookup(ScriptRuntime.class, "mergeScope", ScriptObject.class, ScriptObject.class);
92
93 /**
94 * Return an appropriate iterator for the elements in a for-in construct
95 */
96 public static final Call TO_PROPERTY_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toPropertyIterator", Iterator.class, Object.class);
97
98 /**
99 * Return an appropriate iterator for the elements in a for-each construct
100 */
101 public static final Call TO_VALUE_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toValueIterator", Iterator.class, Object.class);
102
103 /**
104 * Method handle for apply. Used from {@link ScriptFunction} for looking up calls to
105 * call sites that are known to be megamorphic. Using an invoke dynamic here would
106 * lead to the JVM deoptimizing itself to death
107 */
108 public static final Call APPLY = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "apply", Object.class, ScriptFunction.class, Object.class, Object[].class);
109
110 /**
111 * Converts a switch tag value to a simple integer. deflt value if it can't.
112 *
113 * @param tag Switch statement tag value.
114 * @param deflt default to use if not convertible.
115 * @return int tag value (or deflt.)
116 */
117 public static int switchTagAsInt(final Object tag, final int deflt) {
118 if (tag instanceof Number) {
119 final double d = ((Number)tag).doubleValue();
120 if (isRepresentableAsInt(d)) {
121 return (int)d;
122 }
123 }
124
125 return deflt;
126 }
127
128 /**
129 * Converts a switch tag value to a simple integer. deflt value if it can't.
130 *
131 * @param tag Switch statement tag value.
132 * @param deflt default to use if not convertible.
133 * @return int tag value (or deflt.)
134 */
135 public static int switchTagAsInt(final boolean tag, final int deflt) {
136 return deflt;
137 }
138
139 /**
140 * Converts a switch tag value to a simple integer. deflt value if it can't.
141 *
142 * @param tag Switch statement tag value.
143 * @param deflt default to use if not convertible.
144 * @return int tag value (or deflt.)
145 */
146 public static int switchTagAsInt(final long tag, final int deflt) {
147 return isRepresentableAsInt(tag) ? (int)tag : deflt;
148 }
149
150 /**
151 * Converts a switch tag value to a simple integer. deflt value if it can't.
152 *
153 * @param tag Switch statement tag value.
154 * @param deflt default to use if not convertible.
155 * @return int tag value (or deflt.)
156 */
157 public static int switchTagAsInt(final double tag, final int deflt) {
158 return isRepresentableAsInt(tag) ? (int)tag : deflt;
159 }
160
161 /**
162 * This is the builtin implementation of {@code Object.prototype.toString}
163 * @param self reference
164 * @return string representation as object
165 */
166 public static String builtinObjectToString(final Object self) {
167 String className;
168 // Spec tells us to convert primitives by ToObject..
169 // But we don't need to -- all we need is the right class name
170 // of the corresponding primitive wrapper type.
171
172 final JSType type = JSType.of(self);
173
174 switch (type) {
175 case BOOLEAN:
176 className = "Boolean";
177 break;
178 case NUMBER:
179 className = "Number";
180 break;
181 case STRING:
182 className = "String";
183 break;
184 // special case of null and undefined
185 case NULL:
186 className = "Null";
187 break;
188 case UNDEFINED:
189 className = "Undefined";
190 break;
191 case OBJECT:
192 case FUNCTION:
193 if (self instanceof ScriptObject) {
194 className = ((ScriptObject)self).getClassName();
195 } else if (self instanceof JSObject) {
196 className = ((JSObject)self).getClassName();
197 } else {
198 className = self.getClass().getName();
199 }
200 break;
201 default:
202 // Nashorn extension: use Java class name
203 className = self.getClass().getName();
204 break;
205 }
206
207 final StringBuilder sb = new StringBuilder();
208 sb.append("[object ");
209 sb.append(className);
210 sb.append(']');
211
212 return sb.toString();
213 }
214
215 /**
216 * This is called whenever runtime wants to throw an error and wants to provide
217 * meaningful information about an object. We don't want to call toString which
218 * ends up calling "toString" from script world which may itself throw error.
219 * When we want to throw an error, we don't additional error from script land
220 * -- which may sometimes lead to infinite recursion.
221 *
222 * @param obj Object to converted to String safely (without calling user script)
223 * @return safe String representation of the given object
224 */
225 public static String safeToString(final Object obj) {
226 return JSType.toStringImpl(obj, true);
227 }
228
229 /**
230 * Returns an iterator over property identifiers used in the {@code for...in} statement. Note that the ECMAScript
231 * 5.1 specification, chapter 12.6.4. uses the terminology "property names", which seems to imply that the property
232 * identifiers are expected to be strings, but this is not actually spelled out anywhere, and Nashorn will in some
233 * cases deviate from this. Namely, we guarantee to always return an iterator over {@link String} values for any
234 * built-in JavaScript object. We will however return an iterator over {@link Integer} objects for native Java
235 * arrays and {@link List} objects, as well as arbitrary objects representing keys of a {@link Map}. Therefore, the
236 * expression {@code typeof i} within a {@code for(i in obj)} statement can return something other than
237 * {@code string} when iterating over native Java arrays, {@code List}, and {@code Map} objects.
238 * @param obj object to iterate on.
239 * @return iterator over the object's property names.
240 */
241 public static Iterator<?> toPropertyIterator(final Object obj) {
242 if (obj instanceof ScriptObject) {
243 return ((ScriptObject)obj).propertyIterator();
244 }
245
246 if (obj != null && obj.getClass().isArray()) {
247 return new RangeIterator(Array.getLength(obj));
248 }
249
250 if (obj instanceof JSObject) {
251 return ((JSObject)obj).keySet().iterator();
252 }
253
254 if (obj instanceof List) {
255 return new RangeIterator(((List<?>)obj).size());
256 }
257
258 if (obj instanceof Map) {
259 return ((Map<?,?>)obj).keySet().iterator();
260 }
261
262 final Object wrapped = Global.instance().wrapAsObject(obj);
263 if (wrapped instanceof ScriptObject) {
264 return ((ScriptObject)wrapped).propertyIterator();
265 }
266
267 return Collections.emptyIterator();
268 }
269
270 private static final class RangeIterator implements Iterator<Integer> {
271 private final int length;
272 private int index;
273
274 RangeIterator(int length) {
275 this.length = length;
276 }
277
278 @Override
279 public boolean hasNext() {
280 return index < length;
281 }
282
283 @Override
284 public Integer next() {
285 return index++;
286 }
287
288 @Override
289 public void remove() {
290 throw new UnsupportedOperationException();
291 }
292 }
293
294 /**
295 * Returns an iterator over property values used in the {@code for each...in} statement. Aside from built-in JS
296 * objects, it also operates on Java arrays, any {@link Iterable}, as well as on {@link Map} objects, iterating over
297 * map values.
298 * @param obj object to iterate on.
299 * @return iterator over the object's property values.
300 */
301 public static Iterator<?> toValueIterator(final Object obj) {
302 if (obj instanceof ScriptObject) {
303 return ((ScriptObject)obj).valueIterator();
304 }
305
306 if (obj != null && obj.getClass().isArray()) {
307 final Object array = obj;
308 final int length = Array.getLength(obj);
309
310 return new Iterator<Object>() {
311 private int index = 0;
312
313 @Override
314 public boolean hasNext() {
315 return index < length;
316 }
317
318 @Override
319 public Object next() {
320 if (index >= length) {
321 throw new NoSuchElementException();
322 }
323 return Array.get(array, index++);
324 }
325
326 @Override
327 public void remove() {
328 throw new UnsupportedOperationException();
329 }
330 };
331 }
332
333 if (obj instanceof JSObject) {
334 return ((JSObject)obj).values().iterator();
335 }
336
337 if (obj instanceof Map) {
338 return ((Map<?,?>)obj).values().iterator();
339 }
340
341 if (obj instanceof Iterable) {
342 return ((Iterable<?>)obj).iterator();
343 }
344
345 final Object wrapped = Global.instance().wrapAsObject(obj);
346 if (wrapped instanceof ScriptObject) {
347 return ((ScriptObject)wrapped).valueIterator();
348 }
349
350 return Collections.emptyIterator();
351 }
352
353 /**
354 * Merge a scope into its prototype's map.
355 * Merge a scope into its prototype.
356 *
357 * @param scope Scope to merge.
358 * @return prototype object after merge
359 */
360 public static ScriptObject mergeScope(final ScriptObject scope) {
361 final ScriptObject global = scope.getProto();
362 global.addBoundProperties(scope);
363 return global;
364 }
365
366 /**
367 * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve
368 * better performance by {@link Bootstrap#createDynamicInvoker(String, Class, Class...) creating a dynamic invoker}
369 * for operation {@code "dyn:call"}, then using its {@link MethodHandle#invokeExact(Object...)} method instead.
370 *
371 * @param target ScriptFunction object.
372 * @param self Receiver in call.
373 * @param args Call arguments.
374 * @return Call result.
375 */
376 public static Object apply(final ScriptFunction target, final Object self, final Object... args) {
377 try {
378 return target.invoke(self, args);
379 } catch (final RuntimeException | Error e) {
380 throw e;
381 } catch (final Throwable t) {
382 throw new RuntimeException(t);
383 }
384 }
385
386 /**
387 * Call a script function as a constructor with given args.
388 *
389 * @param target ScriptFunction object.
390 * @param args Call arguments.
391 * @return Constructor call result.
392 */
393 public static Object construct(final ScriptFunction target, final Object... args) {
394 try {
395 return target.construct(args);
396 } catch (final RuntimeException | Error e) {
397 throw e;
398 } catch (final Throwable t) {
399 throw new RuntimeException(t);
400 }
401 }
402
403 /**
404 * Generic implementation of ECMA 9.12 - SameValue algorithm
405 *
406 * @param x first value to compare
407 * @param y second value to compare
408 *
409 * @return true if both objects have the same value
410 */
411 public static boolean sameValue(final Object x, final Object y) {
412 final JSType xType = JSType.of(x);
413 final JSType yType = JSType.of(y);
414
415 if (xType != yType) {
416 return false;
417 }
418
419 if (xType == JSType.UNDEFINED || xType == JSType.NULL) {
420 return true;
421 }
422
423 if (xType == JSType.NUMBER) {
424 final double xVal = ((Number)x).doubleValue();
425 final double yVal = ((Number)y).doubleValue();
426
427 if (Double.isNaN(xVal) && Double.isNaN(yVal)) {
428 return true;
429 }
430
431 // checking for xVal == -0.0 and yVal == +0.0 or vice versa
432 if (xVal == 0.0 && (Double.doubleToLongBits(xVal) != Double.doubleToLongBits(yVal))) {
433 return false;
434 }
435
436 return xVal == yVal;
437 }
438
439 if (xType == JSType.STRING || yType == JSType.BOOLEAN) {
440 return x.equals(y);
441 }
442
443 return (x == y);
444 }
445
446 /**
447 * Returns AST as JSON compatible string. This is used to
448 * implement "parse" function in resources/parse.js script.
449 *
450 * @param code code to be parsed
451 * @param name name of the code source (used for location)
452 * @param includeLoc tells whether to include location information for nodes or not
453 * @return JSON string representation of AST of the supplied code
454 */
455 public static String parse(final String code, final String name, final boolean includeLoc) {
456 return JSONWriter.parse(Context.getContextTrusted().getEnv(), code, name, includeLoc);
457 }
458
459 /**
460 * Test whether a char is valid JavaScript whitespace
461 * @param ch a char
462 * @return true if valid JavaScript whitespace
463 */
464 public static boolean isJSWhitespace(final char ch) {
465 return Lexer.isJSWhitespace(ch);
466 }
467
468 /**
469 * Entering a {@code with} node requires new scope. This is the implementation
470 *
471 * @param scope existing scope
472 * @param expression expression in with
473 *
474 * @return {@link WithObject} that is the new scope
475 */
476 public static ScriptObject openWith(final ScriptObject scope, final Object expression) {
477 final Global global = Context.getGlobal();
478 if (expression == UNDEFINED) {
479 throw typeError(global, "cant.apply.with.to.undefined");
480 } else if (expression == null) {
481 throw typeError(global, "cant.apply.with.to.null");
482 }
483
484 final Object wrappedExpr = JSType.toScriptObject(global, expression);
485 if (wrappedExpr instanceof ScriptObject) {
486 return new WithObject(scope, (ScriptObject)wrappedExpr);
487 }
488
489 throw typeError(global, "cant.apply.with.to.non.scriptobject");
490 }
491
492 /**
493 * Exiting a {@code with} node requires restoring scope. This is the implementation
494 *
495 * @param scope existing scope
496 *
497 * @return restored scope
498 */
499 public static ScriptObject closeWith(final ScriptObject scope) {
500 if (scope instanceof WithObject) {
501 return ((WithObject)scope).getParentScope();
502 }
503 return scope;
504 }
505
506 /**
507 * ECMA 11.6.1 - The addition operator (+) - generic implementation
508 * Compiler specializes using {@link jdk.nashorn.internal.codegen.RuntimeCallSite}
509 * if any type information is available for any of the operands
510 *
511 * @param x first term
512 * @param y second term
513 *
514 * @return result of addition
515 */
516 public static Object ADD(final Object x, final Object y) {
517 // This prefix code to handle Number special is for optimization.
518 final boolean xIsNumber = x instanceof Number;
519 final boolean yIsNumber = y instanceof Number;
520
521 if (xIsNumber && yIsNumber) {
522 return ((Number)x).doubleValue() + ((Number)y).doubleValue();
523 }
524
525 final boolean xIsUndefined = x == UNDEFINED;
526 final boolean yIsUndefined = y == UNDEFINED;
527
528 if ((xIsNumber && yIsUndefined) || (xIsUndefined && yIsNumber) || (xIsUndefined && yIsUndefined)) {
529 return Double.NaN;
530 }
531
532 // code below is as per the spec.
533 final Object xPrim = JSType.toPrimitive(x);
534 final Object yPrim = JSType.toPrimitive(y);
535
536 if (xPrim instanceof String || yPrim instanceof String
537 || xPrim instanceof ConsString || yPrim instanceof ConsString) {
538 return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim));
539 }
540
541 return JSType.toNumber(xPrim) + JSType.toNumber(yPrim);
542 }
543
544 /**
545 * Debugger hook.
546 * TODO: currently unimplemented
547 *
548 * @return undefined
549 */
550 public static Object DEBUGGER() {
551 return UNDEFINED;
552 }
553
554 /**
555 * New hook
556 *
557 * @param clazz type for the clss
558 * @param args constructor arguments
559 *
560 * @return undefined
561 */
562 public static Object NEW(final Object clazz, final Object... args) {
563 return UNDEFINED;
564 }
565
566 /**
567 * ECMA 11.4.3 The typeof Operator - generic implementation
568 *
569 * @param object the object from which to retrieve property to type check
570 * @param property property in object to check
571 *
572 * @return type name
573 */
574 public static Object TYPEOF(final Object object, final Object property) {
575 Object obj = object;
576
577 if (property != null) {
578 if (obj instanceof ScriptObject) {
579 obj = ((ScriptObject)obj).get(property);
580 } else if (object instanceof Undefined) {
581 obj = ((Undefined)obj).get(property);
582 } else if (object == null) {
583 throw typeError("cant.get.property", safeToString(property), "null");
584 } else if (JSType.isPrimitive(obj)) {
585 obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property);
586 } else if (obj instanceof JSObject) {
587 obj = ((JSObject)obj).getMember(property.toString());
588 } else {
589 obj = UNDEFINED;
590 }
591 }
592
593 return JSType.of(obj).typeName();
594 }
595
596 /**
597 * Throw ReferenceError when LHS of assignment or increment/decrement
598 * operator is not an assignable node (say a literal)
599 *
600 * @param lhs Evaluated LHS
601 * @param rhs Evaluated RHS
602 * @param msg Additional LHS info for error message
603 * @return undefined
604 */
605 public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) {
606 throw referenceError("cant.be.used.as.lhs", Objects.toString(msg));
607 }
608
609 /**
610 * ECMA 11.4.1 - delete operation, generic implementation
611 *
612 * @param obj object with property to delete
613 * @param property property to delete
614 * @param strict are we in strict mode
615 *
616 * @return true if property was successfully found and deleted
617 */
618 public static boolean DELETE(final Object obj, final Object property, final Object strict) {
619 if (obj instanceof ScriptObject) {
620 return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict));
621 }
622
623 if (obj instanceof Undefined) {
624 return ((Undefined)obj).delete(property, false);
625 }
626
627 if (obj == null) {
628 throw typeError("cant.delete.property", safeToString(property), "null");
629 }
630
631 if (obj instanceof ScriptObjectMirror) {
632 return ((ScriptObjectMirror)obj).delete(property);
633 }
634
635 if (JSType.isPrimitive(obj)) {
636 return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict));
637 }
638
639 if (obj instanceof JSObject) {
640 ((JSObject)obj).removeMember(Objects.toString(property));
641 return true;
642 }
643
644 // if object is not reference type, vacuously delete is successful.
645 return true;
646 }
647
648 /**
649 * ECMA 11.4.1 - delete operator, special case
650 *
651 * This is 'delete' that always fails. We have to check strict mode and throw error.
652 * That is why this is a runtime function. Or else we could have inlined 'false'.
653 *
654 * @param property property to delete
655 * @param strict are we in strict mode
656 *
657 * @return false always
658 */
659 public static boolean FAIL_DELETE(final Object property, final Object strict) {
660 if (Boolean.TRUE.equals(strict)) {
661 throw syntaxError("strict.cant.delete", safeToString(property));
662 }
663 return false;
664 }
665
666 /**
667 * ECMA 11.9.1 - The equals operator (==) - generic implementation
668 *
669 * @param x first object to compare
670 * @param y second object to compare
671 *
672 * @return true if type coerced versions of objects are equal
673 */
674 public static boolean EQ(final Object x, final Object y) {
675 return equals(x, y);
676 }
677
678 /**
679 * ECMA 11.9.2 - The does-not-equal operator (==) - generic implementation
680 *
681 * @param x first object to compare
682 * @param y second object to compare
683 *
684 * @return true if type coerced versions of objects are not equal
685 */
686 public static boolean NE(final Object x, final Object y) {
687 return !EQ(x, y);
688 }
689
690 /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */
691 private static boolean equals(final Object x, final Object y) {
692 final JSType xType = JSType.of(x);
693 final JSType yType = JSType.of(y);
694
695 if (xType == yType) {
696
697 if (xType == JSType.UNDEFINED || xType == JSType.NULL) {
698 return true;
699 }
700
701 if (xType == JSType.NUMBER) {
702 final double xVal = ((Number)x).doubleValue();
703 final double yVal = ((Number)y).doubleValue();
704 if (Double.isNaN(xVal) || Double.isNaN(yVal)) {
705 return false;
706 }
707
708 return xVal == yVal;
709 }
710
711 if (xType == JSType.STRING) {
712 // String may be represented by ConsString
713 return x.toString().equals(y.toString());
714 }
715
716 if (xType == JSType.BOOLEAN) {
717 // Boolean comparison
718 return x.equals(y);
719 }
720
721 return x == y;
722 }
723
724 if ((xType == JSType.UNDEFINED && yType == JSType.NULL) ||
725 (xType == JSType.NULL && yType == JSType.UNDEFINED)) {
726 return true;
727 }
728
729 if (xType == JSType.NUMBER && yType == JSType.STRING) {
730 return EQ(x, JSType.toNumber(y));
731 }
732
733 if (xType == JSType.STRING && yType == JSType.NUMBER) {
734 return EQ(JSType.toNumber(x), y);
735 }
736
737 if (xType == JSType.BOOLEAN) {
738 return EQ(JSType.toNumber(x), y);
739 }
740
741 if (yType == JSType.BOOLEAN) {
742 return EQ(x, JSType.toNumber(y));
743 }
744
745 if ((xType == JSType.STRING || xType == JSType.NUMBER) &&
746 (y instanceof ScriptObject)) {
747 return EQ(x, JSType.toPrimitive(y));
748 }
749
750 if ((x instanceof ScriptObject) &&
751 (yType == JSType.STRING || yType == JSType.NUMBER)) {
752 return EQ(JSType.toPrimitive(x), y);
753 }
754
755 return false;
756 }
757
758 /**
759 * ECMA 11.9.4 - The strict equal operator (===) - generic implementation
760 *
761 * @param x first object to compare
762 * @param y second object to compare
763 *
764 * @return true if objects are equal
765 */
766 public static boolean EQ_STRICT(final Object x, final Object y) {
767 return strictEquals(x, y);
768 }
769
770 /**
771 * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation
772 *
773 * @param x first object to compare
774 * @param y second object to compare
775 *
776 * @return true if objects are not equal
777 */
778 public static boolean NE_STRICT(final Object x, final Object y) {
779 return !EQ_STRICT(x, y);
780 }
781
782 /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */
783 private static boolean strictEquals(final Object x, final Object y) {
784 final JSType xType = JSType.of(x);
785 final JSType yType = JSType.of(y);
786
787 if (xType != yType) {
788 return false;
789 }
790
791 if (xType == JSType.UNDEFINED || xType == JSType.NULL) {
792 return true;
793 }
794
795 if (xType == JSType.NUMBER) {
796 final double xVal = ((Number)x).doubleValue();
797 final double yVal = ((Number)y).doubleValue();
798
799 if (Double.isNaN(xVal) || Double.isNaN(yVal)) {
800 return false;
801 }
802
803 return xVal == yVal;
804 }
805
806 if (xType == JSType.STRING) {
807 // String may be represented by ConsString
808 return x.toString().equals(y.toString());
809 }
810
811 if (xType == JSType.BOOLEAN) {
812 return x.equals(y);
813 }
814
815 // finally, the object identity comparison
816 return x == y;
817 }
818
819 /**
820 * ECMA 11.8.6 - The in operator - generic implementation
821 *
822 * @param property property to check for
823 * @param obj object in which to check for property
824 *
825 * @return true if objects are equal
826 */
827 public static boolean IN(final Object property, final Object obj) {
828 final JSType rvalType = JSType.of(obj);
829
830 if (rvalType == JSType.OBJECT || rvalType == JSType.FUNCTION) {
831 if (obj instanceof ScriptObject) {
832 return ((ScriptObject)obj).has(property);
833 }
834
835 if (obj instanceof JSObject) {
836 return ((JSObject)obj).hasMember(Objects.toString(property));
837 }
838
839 return false;
840 }
841
842 throw typeError("in.with.non.object", rvalType.toString().toLowerCase(Locale.ENGLISH));
843 }
844
845 /**
846 * ECMA 11.8.6 - The strict instanceof operator - generic implementation
847 *
848 * @param obj first object to compare
849 * @param clazz type to check against
850 *
851 * @return true if {@code obj} is an instanceof {@code clazz}
852 */
853 public static boolean INSTANCEOF(final Object obj, final Object clazz) {
854 if (clazz instanceof ScriptFunction) {
855 if (obj instanceof ScriptObject) {
856 return ((ScriptObject)clazz).isInstance((ScriptObject)obj);
857 }
858 return false;
859 }
860
861 if (clazz instanceof StaticClass) {
862 return ((StaticClass)clazz).getRepresentedClass().isInstance(obj);
863 }
864
865 if (clazz instanceof JSObject) {
866 return ((JSObject)clazz).isInstance(obj);
867 }
868
869 // provide for reverse hook
870 if (obj instanceof JSObject) {
871 return ((JSObject)obj).isInstanceOf(clazz);
872 }
873
874 throw typeError("instanceof.on.non.object");
875 }
876
877 /**
878 * ECMA 11.8.1 - The less than operator ({@literal <}) - generic implementation
879 *
880 * @param x first object to compare
881 * @param y second object to compare
882 *
883 * @return true if x is less than y
884 */
885 public static boolean LT(final Object x, final Object y) {
886 final Object value = lessThan(x, y, true);
887 return (value == UNDEFINED) ? false : (Boolean)value;
888 }
889
890 /**
891 * ECMA 11.8.2 - The greater than operator ({@literal >}) - generic implementation
892 *
893 * @param x first object to compare
894 * @param y second object to compare
895 *
896 * @return true if x is greater than y
897 */
898 public static boolean GT(final Object x, final Object y) {
899 final Object value = lessThan(y, x, false);
900 return (value == UNDEFINED) ? false : (Boolean)value;
901 }
902
903 /**
904 * ECMA 11.8.3 - The less than or equal operator ({@literal <=}) - generic implementation
905 *
906 * @param x first object to compare
907 * @param y second object to compare
908 *
909 * @return true if x is less than or equal to y
910 */
911 public static boolean LE(final Object x, final Object y) {
912 final Object value = lessThan(y, x, false);
913 return (!(Boolean.TRUE.equals(value) || value == UNDEFINED));
914 }
915
916 /**
917 * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation
918 *
919 * @param x first object to compare
920 * @param y second object to compare
921 *
922 * @return true if x is greater than or equal to y
923 */
924 public static boolean GE(final Object x, final Object y) {
925 final Object value = lessThan(x, y, true);
926 return (!(Boolean.TRUE.equals(value) || value == UNDEFINED));
927 }
928
929 /** ECMA 11.8.5 The Abstract Relational Comparison Algorithm */
930 private static Object lessThan(final Object x, final Object y, final boolean leftFirst) {
931 Object px, py;
932
933 //support e.g. x < y should throw exception correctly if x or y are not numeric
934 if (leftFirst) {
935 px = JSType.toPrimitive(x, Number.class);
936 py = JSType.toPrimitive(y, Number.class);
937 } else {
938 py = JSType.toPrimitive(y, Number.class);
939 px = JSType.toPrimitive(x, Number.class);
940 }
941
942 if (JSType.of(px) == JSType.STRING && JSType.of(py) == JSType.STRING) {
943 // May be String or ConsString
944 return (px.toString()).compareTo(py.toString()) < 0;
945 }
946
947 final double nx = JSType.toNumber(px);
948 final double ny = JSType.toNumber(py);
949
950 if (Double.isNaN(nx) || Double.isNaN(ny)) {
951 return UNDEFINED;
952 }
953
954 if (nx == ny) {
955 return false;
956 }
957
958 if (nx > 0 && ny > 0 && Double.isInfinite(nx) && Double.isInfinite(ny)) {
959 return false;
960 }
961
962 if (nx < 0 && ny < 0 && Double.isInfinite(nx) && Double.isInfinite(ny)) {
963 return false;
964 }
965
966 return nx < ny;
967 }
968
969 }
--- EOF ---