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