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