ScriptRuntime.java revision 1655:3ac5d360070e
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    /**
317     * Returns an iterator over property values used in the {@code for each...in} statement. Aside from built-in JS
318     * objects, it also operates on Java arrays, any {@link Iterable}, as well as on {@link Map} objects, iterating over
319     * map values.
320     * @param obj object to iterate on.
321     * @return iterator over the object's property values.
322     */
323    public static Iterator<?> toValueIterator(final Object obj) {
324        if (obj instanceof ScriptObject) {
325            return ((ScriptObject)obj).valueIterator();
326        }
327
328        if (obj != null && obj.getClass().isArray()) {
329            final Object array  = obj;
330            final int    length = Array.getLength(obj);
331
332            return new Iterator<Object>() {
333                private int index = 0;
334
335                @Override
336                public boolean hasNext() {
337                    return index < length;
338                }
339
340                @Override
341                public Object next() {
342                    if (index >= length) {
343                        throw new NoSuchElementException();
344                    }
345                    return Array.get(array, index++);
346                }
347
348                @Override
349                public void remove() {
350                    throw new UnsupportedOperationException("remove");
351                }
352            };
353        }
354
355        if (obj instanceof JSObject) {
356            return ((JSObject)obj).values().iterator();
357        }
358
359        if (obj instanceof Map) {
360            return ((Map<?,?>)obj).values().iterator();
361        }
362
363        if (obj instanceof Iterable) {
364            return ((Iterable<?>)obj).iterator();
365        }
366
367        final Object wrapped = Global.instance().wrapAsObject(obj);
368        if (wrapped instanceof ScriptObject) {
369            return ((ScriptObject)wrapped).valueIterator();
370        }
371
372        return Collections.emptyIterator();
373    }
374
375    /**
376     * Returns an iterator over property values used in the {@code for ... of} statement. The iterator uses the
377     * Iterator interface defined in version 6 of the ECMAScript specification.
378     *
379     * @param obj object to iterate on.
380     * @return iterator based on the ECMA 6 Iterator interface.
381     */
382    public static Iterator<?> toES6Iterator(final Object obj) {
383        final Global global = Global.instance();
384        final Object iterator = AbstractIterator.getIterator(Global.toObject(obj), global);
385
386        final InvokeByName nextInvoker = AbstractIterator.getNextInvoker(global);
387        final MethodHandle doneInvoker = AbstractIterator.getDoneInvoker(global);
388        final MethodHandle valueInvoker = AbstractIterator.getValueInvoker(global);
389
390        return new Iterator<Object>() {
391
392            private Object nextResult = nextResult();
393
394            private Object nextResult() {
395                try {
396                    final Object next = nextInvoker.getGetter().invokeExact(iterator);
397                    if (Bootstrap.isCallable(next)) {
398                        return nextInvoker.getInvoker().invokeExact(next, iterator, (Object) null);
399                    }
400                } catch (final RuntimeException|Error r) {
401                    throw r;
402                } catch (final Throwable t) {
403                    throw new RuntimeException(t);
404                }
405                return null;
406            }
407
408            @Override
409            public boolean hasNext() {
410                if (nextResult == null) {
411                    return false;
412                }
413                try {
414                    final Object done = doneInvoker.invokeExact(nextResult);
415                    return !JSType.toBoolean(done);
416                } catch (final RuntimeException|Error r) {
417                    throw r;
418                } catch (final Throwable t) {
419                    throw new RuntimeException(t);
420                }
421            }
422
423            @Override
424            public Object next() {
425                if (nextResult == null) {
426                    return Undefined.getUndefined();
427                }
428                try {
429                    final Object result = nextResult;
430                    nextResult = nextResult();
431                    return valueInvoker.invokeExact(result);
432                } catch (final RuntimeException|Error r) {
433                    throw r;
434                } catch (final Throwable t) {
435                    throw new RuntimeException(t);
436                }
437            }
438
439            @Override
440            public void remove() {
441                throw new UnsupportedOperationException("remove");
442            }
443        };
444    }
445
446    /**
447     * Merge a scope into its prototype's map.
448     * Merge a scope into its prototype.
449     *
450     * @param scope Scope to merge.
451     * @return prototype object after merge
452     */
453    public static ScriptObject mergeScope(final ScriptObject scope) {
454        final ScriptObject parentScope = scope.getProto();
455        parentScope.addBoundProperties(scope);
456        return parentScope;
457    }
458
459    /**
460     * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve
461     * better performance by creating a dynamic invoker using {@link Bootstrap#createDynamicCallInvoker(Class, Class...)}
462     * then using its {@link MethodHandle#invokeExact(Object...)} method instead.
463     *
464     * @param target ScriptFunction object.
465     * @param self   Receiver in call.
466     * @param args   Call arguments.
467     * @return Call result.
468     */
469    public static Object apply(final ScriptFunction target, final Object self, final Object... args) {
470        try {
471            return target.invoke(self, args);
472        } catch (final RuntimeException | Error e) {
473            throw e;
474        } catch (final Throwable t) {
475            throw new RuntimeException(t);
476        }
477    }
478
479    /**
480     * Throws a reference error for an undefined variable.
481     *
482     * @param name the variable name
483     */
484    public static void throwReferenceError(final String name) {
485        throw referenceError("not.defined", name);
486    }
487
488    /**
489     * Throws a type error for an assignment to a const.
490     *
491     * @param name the const name
492     */
493    public static void throwConstTypeError(final String name) {
494        throw typeError("assign.constant", name);
495    }
496
497    /**
498     * Call a script function as a constructor with given args.
499     *
500     * @param target ScriptFunction object.
501     * @param args   Call arguments.
502     * @return Constructor call result.
503     */
504    public static Object construct(final ScriptFunction target, final Object... args) {
505        try {
506            return target.construct(args);
507        } catch (final RuntimeException | Error e) {
508            throw e;
509        } catch (final Throwable t) {
510            throw new RuntimeException(t);
511        }
512    }
513
514    /**
515     * Generic implementation of ECMA 9.12 - SameValue algorithm
516     *
517     * @param x first value to compare
518     * @param y second value to compare
519     *
520     * @return true if both objects have the same value
521     */
522    public static boolean sameValue(final Object x, final Object y) {
523        final JSType xType = JSType.ofNoFunction(x);
524        final JSType yType = JSType.ofNoFunction(y);
525
526        if (xType != yType) {
527            return false;
528        }
529
530        if (xType == JSType.UNDEFINED || xType == JSType.NULL) {
531            return true;
532        }
533
534        if (xType == JSType.NUMBER) {
535            final double xVal = ((Number)x).doubleValue();
536            final double yVal = ((Number)y).doubleValue();
537
538            if (Double.isNaN(xVal) && Double.isNaN(yVal)) {
539                return true;
540            }
541
542            // checking for xVal == -0.0 and yVal == +0.0 or vice versa
543            if (xVal == 0.0 && Double.doubleToLongBits(xVal) != Double.doubleToLongBits(yVal)) {
544                return false;
545            }
546
547            return xVal == yVal;
548        }
549
550        if (xType == JSType.STRING || yType == JSType.BOOLEAN) {
551            return x.equals(y);
552        }
553
554        return x == y;
555    }
556
557    /**
558     * Returns AST as JSON compatible string. This is used to
559     * implement "parse" function in resources/parse.js script.
560     *
561     * @param code code to be parsed
562     * @param name name of the code source (used for location)
563     * @param includeLoc tells whether to include location information for nodes or not
564     * @return JSON string representation of AST of the supplied code
565     */
566    public static String parse(final String code, final String name, final boolean includeLoc) {
567        return JSONWriter.parse(Context.getContextTrusted(), code, name, includeLoc);
568    }
569
570    /**
571     * Test whether a char is valid JavaScript whitespace
572     * @param ch a char
573     * @return true if valid JavaScript whitespace
574     */
575    public static boolean isJSWhitespace(final char ch) {
576        return Lexer.isJSWhitespace(ch);
577    }
578
579    /**
580     * Entering a {@code with} node requires new scope. This is the implementation. When exiting the with statement,
581     * use {@link ScriptObject#getProto()} on the scope.
582     *
583     * @param scope      existing scope
584     * @param expression expression in with
585     *
586     * @return {@link WithObject} that is the new scope
587     */
588    public static ScriptObject openWith(final ScriptObject scope, final Object expression) {
589        final Global global = Context.getGlobal();
590        if (expression == UNDEFINED) {
591            throw typeError(global, "cant.apply.with.to.undefined");
592        } else if (expression == null) {
593            throw typeError(global, "cant.apply.with.to.null");
594        }
595
596        if (expression instanceof ScriptObjectMirror) {
597            final Object unwrapped = ScriptObjectMirror.unwrap(expression, global);
598            if (unwrapped instanceof ScriptObject) {
599                return new WithObject(scope, (ScriptObject)unwrapped);
600            }
601            // foreign ScriptObjectMirror
602            final ScriptObject exprObj = global.newObject();
603            NativeObject.bindAllProperties(exprObj, (ScriptObjectMirror)expression);
604            return new WithObject(scope, exprObj);
605        }
606
607        final Object wrappedExpr = JSType.toScriptObject(global, expression);
608        if (wrappedExpr instanceof ScriptObject) {
609            return new WithObject(scope, (ScriptObject)wrappedExpr);
610        }
611
612        throw typeError(global, "cant.apply.with.to.non.scriptobject");
613    }
614
615    /**
616     * ECMA 11.6.1 - The addition operator (+) - generic implementation
617     *
618     * @param x  first term
619     * @param y  second term
620     *
621     * @return result of addition
622     */
623    public static Object ADD(final Object x, final Object y) {
624        // This prefix code to handle Number special is for optimization.
625        final boolean xIsNumber = x instanceof Number;
626        final boolean yIsNumber = y instanceof Number;
627
628        if (xIsNumber && yIsNumber) {
629             return ((Number)x).doubleValue() + ((Number)y).doubleValue();
630        }
631
632        final boolean xIsUndefined = x == UNDEFINED;
633        final boolean yIsUndefined = y == UNDEFINED;
634
635        if (xIsNumber && yIsUndefined || xIsUndefined && yIsNumber || xIsUndefined && yIsUndefined) {
636            return Double.NaN;
637        }
638
639        // code below is as per the spec.
640        final Object xPrim = JSType.toPrimitive(x);
641        final Object yPrim = JSType.toPrimitive(y);
642
643        if (isString(xPrim) || isString(yPrim)) {
644            try {
645                return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim));
646            } catch (final IllegalArgumentException iae) {
647                throw rangeError(iae, "concat.string.too.big");
648            }
649        }
650
651        return JSType.toNumber(xPrim) + JSType.toNumber(yPrim);
652    }
653
654    /**
655     * Debugger hook.
656     * TODO: currently unimplemented
657     *
658     * @return undefined
659     */
660    public static Object DEBUGGER() {
661        return UNDEFINED;
662    }
663
664    /**
665     * New hook
666     *
667     * @param clazz type for the clss
668     * @param args  constructor arguments
669     *
670     * @return undefined
671     */
672    public static Object NEW(final Object clazz, final Object... args) {
673        return UNDEFINED;
674    }
675
676    /**
677     * ECMA 11.4.3 The typeof Operator - generic implementation
678     *
679     * @param object   the object from which to retrieve property to type check
680     * @param property property in object to check
681     *
682     * @return type name
683     */
684    public static Object TYPEOF(final Object object, final Object property) {
685        Object obj = object;
686
687        if (property != null) {
688            if (obj instanceof ScriptObject) {
689                obj = ((ScriptObject)obj).get(property);
690                if(Global.isLocationPropertyPlaceholder(obj)) {
691                    if(CompilerConstants.__LINE__.name().equals(property)) {
692                        obj = 0;
693                    } else {
694                        obj = "";
695                    }
696                }
697            } else if (object instanceof Undefined) {
698                obj = ((Undefined)obj).get(property);
699            } else if (object == null) {
700                throw typeError("cant.get.property", safeToString(property), "null");
701            } else if (JSType.isPrimitive(obj)) {
702                obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property);
703            } else if (obj instanceof JSObject) {
704                obj = ((JSObject)obj).getMember(property.toString());
705            } else {
706                obj = UNDEFINED;
707            }
708        }
709
710        return JSType.of(obj).typeName();
711    }
712
713    /**
714     * Throw ReferenceError when LHS of assignment or increment/decrement
715     * operator is not an assignable node (say a literal)
716     *
717     * @param lhs Evaluated LHS
718     * @param rhs Evaluated RHS
719     * @param msg Additional LHS info for error message
720     * @return undefined
721     */
722    public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) {
723        throw referenceError("cant.be.used.as.lhs", Objects.toString(msg));
724    }
725
726    /**
727     * ECMA 11.4.1 - delete operation, generic implementation
728     *
729     * @param obj       object with property to delete
730     * @param property  property to delete
731     * @param strict    are we in strict mode
732     *
733     * @return true if property was successfully found and deleted
734     */
735    public static boolean DELETE(final Object obj, final Object property, final Object strict) {
736        if (obj instanceof ScriptObject) {
737            return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict));
738        }
739
740        if (obj instanceof Undefined) {
741            return ((Undefined)obj).delete(property, false);
742        }
743
744        if (obj == null) {
745            throw typeError("cant.delete.property", safeToString(property), "null");
746        }
747
748        if (obj instanceof ScriptObjectMirror) {
749            return ((ScriptObjectMirror)obj).delete(property);
750        }
751
752        if (JSType.isPrimitive(obj)) {
753            return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict));
754        }
755
756        if (obj instanceof JSObject) {
757            ((JSObject)obj).removeMember(Objects.toString(property));
758            return true;
759        }
760
761        // if object is not reference type, vacuously delete is successful.
762        return true;
763    }
764
765    /**
766     * ECMA 11.4.1 - delete operator, implementation for slow scopes
767     *
768     * This implementation of 'delete' walks the scope chain to find the scope that contains the
769     * property to be deleted, then invokes delete on it.
770     *
771     * @param obj       top scope object
772     * @param property  property to delete
773     * @param strict    are we in strict mode
774     *
775     * @return true if property was successfully found and deleted
776     */
777    public static boolean SLOW_DELETE(final Object obj, final Object property, final Object strict) {
778        if (obj instanceof ScriptObject) {
779            ScriptObject sobj = (ScriptObject) obj;
780            final String key = property.toString();
781            while (sobj != null && sobj.isScope()) {
782                final FindProperty find = sobj.findProperty(key, false);
783                if (find != null) {
784                    return sobj.delete(key, Boolean.TRUE.equals(strict));
785                }
786                sobj = sobj.getProto();
787            }
788        }
789        return DELETE(obj, property, strict);
790    }
791
792    /**
793     * ECMA 11.4.1 - delete operator, special case
794     *
795     * This is 'delete' that always fails. We have to check strict mode and throw error.
796     * That is why this is a runtime function. Or else we could have inlined 'false'.
797     *
798     * @param property  property to delete
799     * @param strict    are we in strict mode
800     *
801     * @return false always
802     */
803    public static boolean FAIL_DELETE(final Object property, final Object strict) {
804        if (Boolean.TRUE.equals(strict)) {
805            throw syntaxError("strict.cant.delete", safeToString(property));
806        }
807        return false;
808    }
809
810    /**
811     * ECMA 11.9.1 - The equals operator (==) - generic implementation
812     *
813     * @param x first object to compare
814     * @param y second object to compare
815     *
816     * @return true if type coerced versions of objects are equal
817     */
818    public static boolean EQ(final Object x, final Object y) {
819        return equals(x, y);
820    }
821
822    /**
823     * ECMA 11.9.2 - The does-not-equal operator (==) - generic implementation
824     *
825     * @param x first object to compare
826     * @param y second object to compare
827     *
828     * @return true if type coerced versions of objects are not equal
829     */
830    public static boolean NE(final Object x, final Object y) {
831        return !EQ(x, y);
832    }
833
834    /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */
835    private static boolean equals(final Object x, final Object y) {
836        // We want to keep this method small so we skip reference equality check for numbers
837        // as NaN should return false when compared to itself (JDK-8043608).
838        if (x == y && !(x instanceof Number)) {
839            return true;
840        }
841        if (x instanceof ScriptObject && y instanceof ScriptObject) {
842            return false; // x != y
843        }
844        if (x instanceof ScriptObjectMirror || y instanceof ScriptObjectMirror) {
845            return ScriptObjectMirror.identical(x, y);
846        }
847        return equalValues(x, y);
848    }
849
850    /**
851     * Extracted portion of {@code equals()} that compares objects by value (or by reference, if no known value
852     * comparison applies).
853     * @param x one value
854     * @param y another value
855     * @return true if they're equal according to 11.9.3
856     */
857    private static boolean equalValues(final Object x, final Object y) {
858        final JSType xType = JSType.ofNoFunction(x);
859        final JSType yType = JSType.ofNoFunction(y);
860
861        if (xType == yType) {
862            return equalSameTypeValues(x, y, xType);
863        }
864
865        return equalDifferentTypeValues(x, y, xType, yType);
866    }
867
868    /**
869     * Extracted portion of {@link #equals(Object, Object)} and {@link #strictEquals(Object, Object)} that compares
870     * values belonging to the same JSType.
871     * @param x one value
872     * @param y another value
873     * @param type the common type for the values
874     * @return true if they're equal
875     */
876    private static boolean equalSameTypeValues(final Object x, final Object y, final JSType type) {
877        if (type == JSType.UNDEFINED || type == JSType.NULL) {
878            return true;
879        }
880
881        if (type == JSType.NUMBER) {
882            return ((Number)x).doubleValue() == ((Number)y).doubleValue();
883        }
884
885        if (type == JSType.STRING) {
886            // String may be represented by ConsString
887            return x.toString().equals(y.toString());
888        }
889
890        if (type == JSType.BOOLEAN) {
891            return ((Boolean)x).booleanValue() == ((Boolean)y).booleanValue();
892        }
893
894        return x == y;
895    }
896
897    /**
898     * Extracted portion of {@link #equals(Object, Object)} that compares values belonging to different JSTypes.
899     * @param x one value
900     * @param y another value
901     * @param xType the type for the value x
902     * @param yType the type for the value y
903     * @return true if they're equal
904     */
905    private static boolean equalDifferentTypeValues(final Object x, final Object y, final JSType xType, final JSType yType) {
906        if (isUndefinedAndNull(xType, yType) || isUndefinedAndNull(yType, xType)) {
907            return true;
908        } else if (isNumberAndString(xType, yType)) {
909            return equalNumberToString(x, y);
910        } else if (isNumberAndString(yType, xType)) {
911            // Can reverse order as both are primitives
912            return equalNumberToString(y, x);
913        } else if (xType == JSType.BOOLEAN) {
914            return equalBooleanToAny(x, y);
915        } else if (yType == JSType.BOOLEAN) {
916            // Can reverse order as y is primitive
917            return equalBooleanToAny(y, x);
918        } else if (isPrimitiveAndObject(xType, yType)) {
919            return equalWrappedPrimitiveToObject(x, y);
920        } else if (isPrimitiveAndObject(yType, xType)) {
921            // Can reverse order as y is primitive
922            return equalWrappedPrimitiveToObject(y, x);
923        }
924
925        return false;
926    }
927
928    private static boolean isUndefinedAndNull(final JSType xType, final JSType yType) {
929        return xType == JSType.UNDEFINED && yType == JSType.NULL;
930    }
931
932    private static boolean isNumberAndString(final JSType xType, final JSType yType) {
933        return xType == JSType.NUMBER && yType == JSType.STRING;
934    }
935
936    private static boolean isPrimitiveAndObject(final JSType xType, final JSType yType) {
937        return (xType == JSType.NUMBER || xType == JSType.STRING || xType == JSType.SYMBOL) && yType == JSType.OBJECT;
938    }
939
940    private static boolean equalNumberToString(final Object num, final Object str) {
941        // Specification says comparing a number to string should be done as "equals(num, JSType.toNumber(str))". We
942        // can short circuit it to this as we know that "num" is a number, so it'll end up being a number-number
943        // comparison.
944        return ((Number)num).doubleValue() == JSType.toNumber(str.toString());
945    }
946
947    private static boolean equalBooleanToAny(final Object bool, final Object any) {
948        return equals(JSType.toNumber((Boolean)bool), any);
949    }
950
951    private static boolean equalWrappedPrimitiveToObject(final Object numOrStr, final Object any) {
952        return equals(numOrStr, JSType.toPrimitive(any));
953    }
954
955    /**
956     * ECMA 11.9.4 - The strict equal operator (===) - generic implementation
957     *
958     * @param x first object to compare
959     * @param y second object to compare
960     *
961     * @return true if objects are equal
962     */
963    public static boolean EQ_STRICT(final Object x, final Object y) {
964        return strictEquals(x, y);
965    }
966
967    /**
968     * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation
969     *
970     * @param x first object to compare
971     * @param y second object to compare
972     *
973     * @return true if objects are not equal
974     */
975    public static boolean NE_STRICT(final Object x, final Object y) {
976        return !EQ_STRICT(x, y);
977    }
978
979    /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */
980    private static boolean strictEquals(final Object x, final Object y) {
981        // NOTE: you might be tempted to do a quick x == y comparison. Remember, though, that any Double object having
982        // NaN value is not equal to itself by value even though it is referentially.
983
984        final JSType xType = JSType.ofNoFunction(x);
985        final JSType yType = JSType.ofNoFunction(y);
986
987        if (xType != yType) {
988            return false;
989        }
990
991        return equalSameTypeValues(x, y, xType);
992    }
993
994    /**
995     * ECMA 11.8.6 - The in operator - generic implementation
996     *
997     * @param property property to check for
998     * @param obj object in which to check for property
999     *
1000     * @return true if objects are equal
1001     */
1002    public static boolean IN(final Object property, final Object obj) {
1003        final JSType rvalType = JSType.ofNoFunction(obj);
1004
1005        if (rvalType == JSType.OBJECT) {
1006            if (obj instanceof ScriptObject) {
1007                return ((ScriptObject)obj).has(property);
1008            }
1009
1010            if (obj instanceof JSObject) {
1011                return ((JSObject)obj).hasMember(Objects.toString(property));
1012            }
1013
1014            return false;
1015        }
1016
1017        throw typeError("in.with.non.object", rvalType.toString().toLowerCase(Locale.ENGLISH));
1018    }
1019
1020    /**
1021     * ECMA 11.8.6 - The strict instanceof operator - generic implementation
1022     *
1023     * @param obj first object to compare
1024     * @param clazz type to check against
1025     *
1026     * @return true if {@code obj} is an instanceof {@code clazz}
1027     */
1028    public static boolean INSTANCEOF(final Object obj, final Object clazz) {
1029        if (clazz instanceof ScriptFunction) {
1030            if (obj instanceof ScriptObject) {
1031                return ((ScriptObject)clazz).isInstance((ScriptObject)obj);
1032            }
1033            return false;
1034        }
1035
1036        if (clazz instanceof StaticClass) {
1037            return ((StaticClass)clazz).getRepresentedClass().isInstance(obj);
1038        }
1039
1040        if (clazz instanceof JSObject) {
1041            return ((JSObject)clazz).isInstance(obj);
1042        }
1043
1044        // provide for reverse hook
1045        if (obj instanceof JSObject) {
1046            return ((JSObject)obj).isInstanceOf(clazz);
1047        }
1048
1049        throw typeError("instanceof.on.non.object");
1050    }
1051
1052    /**
1053     * ECMA 11.8.1 - The less than operator ({@literal <}) - generic implementation
1054     *
1055     * @param x first object to compare
1056     * @param y second object to compare
1057     *
1058     * @return true if x is less than y
1059     */
1060    public static boolean LT(final Object x, final Object y) {
1061        final Object px = JSType.toPrimitive(x, Number.class);
1062        final Object py = JSType.toPrimitive(y, Number.class);
1063
1064        return areBothString(px, py) ? px.toString().compareTo(py.toString()) < 0 :
1065            JSType.toNumber(px) < JSType.toNumber(py);
1066    }
1067
1068    private static boolean areBothString(final Object x, final Object y) {
1069        return isString(x) && isString(y);
1070    }
1071
1072    /**
1073     * ECMA 11.8.2 - The greater than operator ({@literal >}) - generic implementation
1074     *
1075     * @param x first object to compare
1076     * @param y second object to compare
1077     *
1078     * @return true if x is greater than y
1079     */
1080    public static boolean GT(final Object x, final Object y) {
1081        final Object px = JSType.toPrimitive(x, Number.class);
1082        final Object py = JSType.toPrimitive(y, Number.class);
1083
1084        return areBothString(px, py) ? px.toString().compareTo(py.toString()) > 0 :
1085            JSType.toNumber(px) > JSType.toNumber(py);
1086    }
1087
1088    /**
1089     * ECMA 11.8.3 - The less than or equal operator ({@literal <=}) - generic implementation
1090     *
1091     * @param x first object to compare
1092     * @param y second object to compare
1093     *
1094     * @return true if x is less than or equal to y
1095     */
1096    public static boolean LE(final Object x, final Object y) {
1097        final Object px = JSType.toPrimitive(x, Number.class);
1098        final Object py = JSType.toPrimitive(y, Number.class);
1099
1100        return areBothString(px, py) ? px.toString().compareTo(py.toString()) <= 0 :
1101            JSType.toNumber(px) <= JSType.toNumber(py);
1102    }
1103
1104    /**
1105     * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation
1106     *
1107     * @param x first object to compare
1108     * @param y second object to compare
1109     *
1110     * @return true if x is greater than or equal to y
1111     */
1112    public static boolean GE(final Object x, final Object y) {
1113        final Object px = JSType.toPrimitive(x, Number.class);
1114        final Object py = JSType.toPrimitive(y, Number.class);
1115
1116        return areBothString(px, py) ? px.toString().compareTo(py.toString()) >= 0 :
1117            JSType.toNumber(px) >= JSType.toNumber(py);
1118    }
1119
1120    /**
1121     * Tag a reserved name as invalidated - used when someone writes
1122     * to a property with this name - overly conservative, but link time
1123     * is too late to apply e.g. apply-&gt;call specialization
1124     * @param name property name
1125     */
1126    public static void invalidateReservedBuiltinName(final String name) {
1127        final Context context = Context.getContextTrusted();
1128        final SwitchPoint sp = context.getBuiltinSwitchPoint(name);
1129        assert sp != null;
1130        context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint");
1131        SwitchPoint.invalidateAll(new SwitchPoint[] { sp });
1132    }
1133
1134    /**
1135     * ES6 12.2.9.3 Runtime Semantics: GetTemplateObject(templateLiteral).
1136     *
1137     * @param rawStrings array of template raw values
1138     * @param cookedStrings array of template values
1139     * @return template object
1140     */
1141    public static ScriptObject GET_TEMPLATE_OBJECT(final Object rawStrings, final Object cookedStrings) {
1142        final ScriptObject template = (ScriptObject)cookedStrings;
1143        final ScriptObject rawObj = (ScriptObject)rawStrings;
1144        assert rawObj.getArray().length() == template.getArray().length();
1145        template.addOwnProperty("raw", Property.NOT_WRITABLE | Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE, rawObj.freeze());
1146        template.freeze();
1147        return template;
1148    }
1149}
1150