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