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