ScriptRuntime.java revision 1513:2cebe18ffc70
1171169Smlaier/*
2171169Smlaier * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
3171169Smlaier * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4171169Smlaier *
5171169Smlaier * This code is free software; you can redistribute it and/or modify it
6171169Smlaier * under the terms of the GNU General Public License version 2 only, as
7171169Smlaier * published by the Free Software Foundation.  Oracle designates this
8171169Smlaier * particular file as subject to the "Classpath" exception as provided
9171169Smlaier * by Oracle in the LICENSE file that accompanied this code.
10171169Smlaier *
11171169Smlaier * This code is distributed in the hope that it will be useful, but WITHOUT
12171169Smlaier * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13171169Smlaier * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14171169Smlaier * version 2 for more details (a copy is included in the LICENSE file that
15171169Smlaier * accompanied this code).
16171169Smlaier *
17171169Smlaier * You should have received a copy of the GNU General Public License version
18171169Smlaier * 2 along with this work; if not, write to the Free Software Foundation,
19171169Smlaier * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20171169Smlaier *
21171169Smlaier * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22171169Smlaier * or visit www.oracle.com if you need additional information or have any
23171169Smlaier * questions.
24171169Smlaier */
25171169Smlaier
26171169Smlaierpackage jdk.nashorn.internal.runtime;
27171169Smlaier
28171169Smlaierimport static jdk.nashorn.internal.codegen.CompilerConstants.staticCall;
29171169Smlaierimport static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
30171169Smlaierimport static jdk.nashorn.internal.runtime.ECMAErrors.rangeError;
31171169Smlaierimport static jdk.nashorn.internal.runtime.ECMAErrors.referenceError;
32171169Smlaierimport static jdk.nashorn.internal.runtime.ECMAErrors.syntaxError;
33171169Smlaierimport static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
34171169Smlaierimport static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt;
35171169Smlaierimport static jdk.nashorn.internal.runtime.JSType.isString;
36171169Smlaier
37171169Smlaierimport java.lang.invoke.MethodHandle;
38171169Smlaierimport java.lang.invoke.MethodHandles;
39171169Smlaierimport java.lang.invoke.SwitchPoint;
40171169Smlaierimport java.lang.reflect.Array;
41171169Smlaierimport java.util.Collections;
42171169Smlaierimport java.util.Iterator;
43171169Smlaierimport java.util.List;
44171169Smlaierimport java.util.Locale;
45171169Smlaierimport java.util.Map;
46171169Smlaierimport java.util.NoSuchElementException;
47171169Smlaierimport java.util.Objects;
48171169Smlaier
49171169Smlaierimport jdk.internal.dynalink.beans.StaticClass;
50171169Smlaierimport jdk.nashorn.api.scripting.JSObject;
51171169Smlaierimport jdk.nashorn.api.scripting.ScriptObjectMirror;
52171169Smlaierimport jdk.nashorn.internal.codegen.ApplySpecialization;
53171169Smlaierimport jdk.nashorn.internal.codegen.CompilerConstants;
54171169Smlaierimport jdk.nashorn.internal.codegen.CompilerConstants.Call;
55171169Smlaierimport jdk.nashorn.internal.ir.debug.JSONWriter;
56171169Smlaierimport jdk.nashorn.internal.objects.Global;
57171169Smlaierimport jdk.nashorn.internal.objects.NativeObject;
58171169Smlaierimport jdk.nashorn.internal.parser.Lexer;
59171169Smlaierimport jdk.nashorn.internal.runtime.linker.Bootstrap;
60171169Smlaier
61171169Smlaier/**
62171169Smlaier * Utilities to be called by JavaScript runtime API and generated classes.
63171169Smlaier */
64171169Smlaier
65171169Smlaierpublic final class ScriptRuntime {
66171169Smlaier    private ScriptRuntime() {
67171169Smlaier    }
68171169Smlaier
69171169Smlaier    /** Singleton representing the empty array object '[]' */
70171169Smlaier    public static final Object[] EMPTY_ARRAY = new Object[0];
71171169Smlaier
72171169Smlaier    /** Unique instance of undefined. */
73171169Smlaier    public static final Undefined UNDEFINED = Undefined.getUndefined();
74171169Smlaier
75171169Smlaier    /**
76171169Smlaier     * Unique instance of undefined used to mark empty array slots.
77171169Smlaier     * Can't escape the array.
78171169Smlaier     */
79171169Smlaier    public static final Undefined EMPTY = Undefined.getEmpty();
80171169Smlaier
81171169Smlaier    /** Method handle to generic + operator, operating on objects */
82171169Smlaier    public static final Call ADD = staticCallNoLookup(ScriptRuntime.class, "ADD", Object.class, Object.class, Object.class);
83171169Smlaier
84171169Smlaier    /** Method handle to generic === operator, operating on objects */
85171169Smlaier    public static final Call EQ_STRICT = staticCallNoLookup(ScriptRuntime.class, "EQ_STRICT", boolean.class, Object.class, Object.class);
86171169Smlaier
87171169Smlaier    /** Method handle used to enter a {@code with} scope at runtime. */
88171169Smlaier    public static final Call OPEN_WITH = staticCallNoLookup(ScriptRuntime.class, "openWith", ScriptObject.class, ScriptObject.class, Object.class);
89171169Smlaier
90171169Smlaier    /**
91171169Smlaier     * Method used to place a scope's variable into the Global scope, which has to be done for the
92171169Smlaier     * properties declared at outermost script level.
93171169Smlaier     */
94171169Smlaier    public static final Call MERGE_SCOPE = staticCallNoLookup(ScriptRuntime.class, "mergeScope", ScriptObject.class, ScriptObject.class);
95171169Smlaier
96171169Smlaier    /**
97171169Smlaier     * Return an appropriate iterator for the elements in a for-in construct
98171169Smlaier     */
99171169Smlaier    public static final Call TO_PROPERTY_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toPropertyIterator", Iterator.class, Object.class);
100171169Smlaier
101171169Smlaier    /**
102171169Smlaier     * Return an appropriate iterator for the elements in a for-each construct
103171169Smlaier     */
104171169Smlaier    public static final Call TO_VALUE_ITERATOR = staticCallNoLookup(ScriptRuntime.class, "toValueIterator", Iterator.class, Object.class);
105171169Smlaier
106171169Smlaier    /**
107171169Smlaier      * Method handle for apply. Used from {@link ScriptFunction} for looking up calls to
108171169Smlaier      * call sites that are known to be megamorphic. Using an invoke dynamic here would
109171169Smlaier      * lead to the JVM deoptimizing itself to death
110171169Smlaier      */
111171169Smlaier    public static final Call APPLY = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "apply", Object.class, ScriptFunction.class, Object.class, Object[].class);
112171169Smlaier
113171169Smlaier    /**
114171169Smlaier     * Throws a reference error for an undefined variable.
115171169Smlaier     */
116171169Smlaier    public static final Call THROW_REFERENCE_ERROR = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "throwReferenceError", void.class, String.class);
117171169Smlaier
118171169Smlaier    /**
119171169Smlaier     * Throws a reference error for an undefined variable.
120171169Smlaier     */
121171169Smlaier    public static final Call THROW_CONST_TYPE_ERROR = staticCall(MethodHandles.lookup(), ScriptRuntime.class, "throwConstTypeError", void.class, String.class);
122171169Smlaier
123171169Smlaier    /**
124171169Smlaier     * Used to invalidate builtin names, e.g "Function" mapping to all properties in Function.prototype and Function.prototype itself.
125171169Smlaier     */
126171169Smlaier    public static final Call INVALIDATE_RESERVED_BUILTIN_NAME = staticCallNoLookup(ScriptRuntime.class, "invalidateReservedBuiltinName", void.class, String.class);
127171169Smlaier
128171169Smlaier    /**
129171169Smlaier     * Converts a switch tag value to a simple integer. deflt value if it can't.
130171169Smlaier     *
131171169Smlaier     * @param tag   Switch statement tag value.
132171169Smlaier     * @param deflt default to use if not convertible.
133171169Smlaier     * @return int tag value (or deflt.)
134171169Smlaier     */
135171169Smlaier    public static int switchTagAsInt(final Object tag, final int deflt) {
136171169Smlaier        if (tag instanceof Number) {
137171169Smlaier            final double d = ((Number)tag).doubleValue();
138171169Smlaier            if (isRepresentableAsInt(d)) {
139171169Smlaier                return (int)d;
140171169Smlaier            }
141171169Smlaier        }
142171169Smlaier        return deflt;
143171169Smlaier    }
144171169Smlaier
145171169Smlaier    /**
146171169Smlaier     * Converts a switch tag value to a simple integer. deflt value if it can't.
147171169Smlaier     *
148171169Smlaier     * @param tag   Switch statement tag value.
149171169Smlaier     * @param deflt default to use if not convertible.
150171169Smlaier     * @return int tag value (or deflt.)
151171169Smlaier     */
152171169Smlaier    public static int switchTagAsInt(final boolean tag, final int deflt) {
153171169Smlaier        return deflt;
154171169Smlaier    }
155171169Smlaier
156171169Smlaier    /**
157171169Smlaier     * Converts a switch tag value to a simple integer. deflt value if it can't.
158171169Smlaier     *
159171169Smlaier     * @param tag   Switch statement tag value.
160171169Smlaier     * @param deflt default to use if not convertible.
161171169Smlaier     * @return int tag value (or deflt.)
162171169Smlaier     */
163171169Smlaier    public static int switchTagAsInt(final long tag, final int deflt) {
164171169Smlaier        return isRepresentableAsInt(tag) ? (int)tag : deflt;
165171169Smlaier    }
166171169Smlaier
167171169Smlaier    /**
168171169Smlaier     * Converts a switch tag value to a simple integer. deflt value if it can't.
169171169Smlaier     *
170171169Smlaier     * @param tag   Switch statement tag value.
171171169Smlaier     * @param deflt default to use if not convertible.
172171169Smlaier     * @return int tag value (or deflt.)
173171169Smlaier     */
174171169Smlaier    public static int switchTagAsInt(final double tag, final int deflt) {
175171169Smlaier        return isRepresentableAsInt(tag) ? (int)tag : deflt;
176171169Smlaier    }
177171169Smlaier
178171169Smlaier    /**
179171169Smlaier     * This is the builtin implementation of {@code Object.prototype.toString}
180171169Smlaier     * @param self reference
181171169Smlaier     * @return string representation as object
182171169Smlaier     */
183171169Smlaier    public static String builtinObjectToString(final Object self) {
184171169Smlaier        String className;
185171169Smlaier        // Spec tells us to convert primitives by ToObject..
186171169Smlaier        // But we don't need to -- all we need is the right class name
187171169Smlaier        // of the corresponding primitive wrapper type.
188171169Smlaier
189171169Smlaier        final JSType type = JSType.ofNoFunction(self);
190171169Smlaier
191171169Smlaier        switch (type) {
192171169Smlaier        case BOOLEAN:
193171169Smlaier            className = "Boolean";
194171169Smlaier            break;
195171169Smlaier        case NUMBER:
196171169Smlaier            className = "Number";
197171169Smlaier            break;
198171169Smlaier        case STRING:
199171169Smlaier            className = "String";
200171169Smlaier            break;
201171169Smlaier        // special case of null and undefined
202171169Smlaier        case NULL:
203171169Smlaier            className = "Null";
204171169Smlaier            break;
205171169Smlaier        case UNDEFINED:
206171169Smlaier            className = "Undefined";
207171169Smlaier            break;
208171169Smlaier        case OBJECT:
209171169Smlaier            if (self instanceof ScriptObject) {
210171169Smlaier                className = ((ScriptObject)self).getClassName();
211171169Smlaier            } else if (self instanceof JSObject) {
212171169Smlaier                className = ((JSObject)self).getClassName();
213171169Smlaier            } else {
214171169Smlaier                className = self.getClass().getName();
215171169Smlaier            }
216171169Smlaier            break;
217171169Smlaier        default:
218171169Smlaier            // Nashorn extension: use Java class name
219171169Smlaier            className = self.getClass().getName();
220            break;
221        }
222
223        final StringBuilder sb = new StringBuilder();
224        sb.append("[object ");
225        sb.append(className);
226        sb.append(']');
227
228        return sb.toString();
229    }
230
231    /**
232     * This is called whenever runtime wants to throw an error and wants to provide
233     * meaningful information about an object. We don't want to call toString which
234     * ends up calling "toString" from script world which may itself throw error.
235     * When we want to throw an error, we don't additional error from script land
236     * -- which may sometimes lead to infinite recursion.
237     *
238     * @param obj Object to converted to String safely (without calling user script)
239     * @return safe String representation of the given object
240     */
241    public static String safeToString(final Object obj) {
242        return JSType.toStringImpl(obj, true);
243    }
244
245    /**
246     * Returns an iterator over property identifiers used in the {@code for...in} statement. Note that the ECMAScript
247     * 5.1 specification, chapter 12.6.4. uses the terminology "property names", which seems to imply that the property
248     * identifiers are expected to be strings, but this is not actually spelled out anywhere, and Nashorn will in some
249     * cases deviate from this. Namely, we guarantee to always return an iterator over {@link String} values for any
250     * built-in JavaScript object. We will however return an iterator over {@link Integer} objects for native Java
251     * arrays and {@link List} objects, as well as arbitrary objects representing keys of a {@link Map}. Therefore, the
252     * expression {@code typeof i} within a {@code for(i in obj)} statement can return something other than
253     * {@code string} when iterating over native Java arrays, {@code List}, and {@code Map} objects.
254     * @param obj object to iterate on.
255     * @return iterator over the object's property names.
256     */
257    public static Iterator<?> toPropertyIterator(final Object obj) {
258        if (obj instanceof ScriptObject) {
259            return ((ScriptObject)obj).propertyIterator();
260        }
261
262        if (obj != null && obj.getClass().isArray()) {
263            return new RangeIterator(Array.getLength(obj));
264        }
265
266        if (obj instanceof JSObject) {
267            return ((JSObject)obj).keySet().iterator();
268        }
269
270        if (obj instanceof List) {
271            return new RangeIterator(((List<?>)obj).size());
272        }
273
274        if (obj instanceof Map) {
275            return ((Map<?,?>)obj).keySet().iterator();
276        }
277
278        final Object wrapped = Global.instance().wrapAsObject(obj);
279        if (wrapped instanceof ScriptObject) {
280            return ((ScriptObject)wrapped).propertyIterator();
281        }
282
283        return Collections.emptyIterator();
284    }
285
286    private static final class RangeIterator implements Iterator<Integer> {
287        private final int length;
288        private int index;
289
290        RangeIterator(final int length) {
291            this.length = length;
292        }
293
294        @Override
295        public boolean hasNext() {
296            return index < length;
297        }
298
299        @Override
300        public Integer next() {
301            return index++;
302        }
303
304        @Override
305        public void remove() {
306            throw new UnsupportedOperationException("remove");
307        }
308    }
309
310    /**
311     * Returns an iterator over property values used in the {@code for each...in} statement. Aside from built-in JS
312     * objects, it also operates on Java arrays, any {@link Iterable}, as well as on {@link Map} objects, iterating over
313     * map values.
314     * @param obj object to iterate on.
315     * @return iterator over the object's property values.
316     */
317    public static Iterator<?> toValueIterator(final Object obj) {
318        if (obj instanceof ScriptObject) {
319            return ((ScriptObject)obj).valueIterator();
320        }
321
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 JSObject) {
350            return ((JSObject)obj).values().iterator();
351        }
352
353        if (obj instanceof Map) {
354            return ((Map<?,?>)obj).values().iterator();
355        }
356
357        if (obj instanceof Iterable) {
358            return ((Iterable<?>)obj).iterator();
359        }
360
361        final Object wrapped = Global.instance().wrapAsObject(obj);
362        if (wrapped instanceof ScriptObject) {
363            return ((ScriptObject)wrapped).valueIterator();
364        }
365
366        return Collections.emptyIterator();
367    }
368
369    /**
370     * Merge a scope into its prototype's map.
371     * Merge a scope into its prototype.
372     *
373     * @param scope Scope to merge.
374     * @return prototype object after merge
375     */
376    public static ScriptObject mergeScope(final ScriptObject scope) {
377        final ScriptObject parentScope = scope.getProto();
378        parentScope.addBoundProperties(scope);
379        return parentScope;
380    }
381
382    /**
383     * Call a function given self and args. If the number of the arguments is known in advance, you can likely achieve
384     * better performance by creating a dynamic invoker using {@link Bootstrap#createDynamicCallInvoker(Class, Class...)}
385     * then using its {@link MethodHandle#invokeExact(Object...)} method instead.
386     *
387     * @param target ScriptFunction object.
388     * @param self   Receiver in call.
389     * @param args   Call arguments.
390     * @return Call result.
391     */
392    public static Object apply(final ScriptFunction target, final Object self, final Object... args) {
393        try {
394            return target.invoke(self, args);
395        } catch (final RuntimeException | Error e) {
396            throw e;
397        } catch (final Throwable t) {
398            throw new RuntimeException(t);
399        }
400    }
401
402    /**
403     * Throws a reference error for an undefined variable.
404     *
405     * @param name the variable name
406     */
407    public static void throwReferenceError(final String name) {
408        throw referenceError("not.defined", name);
409    }
410
411    /**
412     * Throws a type error for an assignment to a const.
413     *
414     * @param name the const name
415     */
416    public static void throwConstTypeError(final String name) {
417        throw typeError("assign.constant", name);
418    }
419
420    /**
421     * Call a script function as a constructor with given args.
422     *
423     * @param target ScriptFunction object.
424     * @param args   Call arguments.
425     * @return Constructor call result.
426     */
427    public static Object construct(final ScriptFunction target, final Object... args) {
428        try {
429            return target.construct(args);
430        } catch (final RuntimeException | Error e) {
431            throw e;
432        } catch (final Throwable t) {
433            throw new RuntimeException(t);
434        }
435    }
436
437    /**
438     * Generic implementation of ECMA 9.12 - SameValue algorithm
439     *
440     * @param x first value to compare
441     * @param y second value to compare
442     *
443     * @return true if both objects have the same value
444     */
445    public static boolean sameValue(final Object x, final Object y) {
446        final JSType xType = JSType.ofNoFunction(x);
447        final JSType yType = JSType.ofNoFunction(y);
448
449        if (xType != yType) {
450            return false;
451        }
452
453        if (xType == JSType.UNDEFINED || xType == JSType.NULL) {
454            return true;
455        }
456
457        if (xType == JSType.NUMBER) {
458            final double xVal = ((Number)x).doubleValue();
459            final double yVal = ((Number)y).doubleValue();
460
461            if (Double.isNaN(xVal) && Double.isNaN(yVal)) {
462                return true;
463            }
464
465            // checking for xVal == -0.0 and yVal == +0.0 or vice versa
466            if (xVal == 0.0 && Double.doubleToLongBits(xVal) != Double.doubleToLongBits(yVal)) {
467                return false;
468            }
469
470            return xVal == yVal;
471        }
472
473        if (xType == JSType.STRING || yType == JSType.BOOLEAN) {
474            return x.equals(y);
475        }
476
477        return x == y;
478    }
479
480    /**
481     * Returns AST as JSON compatible string. This is used to
482     * implement "parse" function in resources/parse.js script.
483     *
484     * @param code code to be parsed
485     * @param name name of the code source (used for location)
486     * @param includeLoc tells whether to include location information for nodes or not
487     * @return JSON string representation of AST of the supplied code
488     */
489    public static String parse(final String code, final String name, final boolean includeLoc) {
490        return JSONWriter.parse(Context.getContextTrusted(), code, name, includeLoc);
491    }
492
493    /**
494     * Test whether a char is valid JavaScript whitespace
495     * @param ch a char
496     * @return true if valid JavaScript whitespace
497     */
498    public static boolean isJSWhitespace(final char ch) {
499        return Lexer.isJSWhitespace(ch);
500    }
501
502    /**
503     * Entering a {@code with} node requires new scope. This is the implementation. When exiting the with statement,
504     * use {@link ScriptObject#getProto()} on the scope.
505     *
506     * @param scope      existing scope
507     * @param expression expression in with
508     *
509     * @return {@link WithObject} that is the new scope
510     */
511    public static ScriptObject openWith(final ScriptObject scope, final Object expression) {
512        final Global global = Context.getGlobal();
513        if (expression == UNDEFINED) {
514            throw typeError(global, "cant.apply.with.to.undefined");
515        } else if (expression == null) {
516            throw typeError(global, "cant.apply.with.to.null");
517        }
518
519        if (expression instanceof ScriptObjectMirror) {
520            final Object unwrapped = ScriptObjectMirror.unwrap(expression, global);
521            if (unwrapped instanceof ScriptObject) {
522                return new WithObject(scope, (ScriptObject)unwrapped);
523            }
524            // foreign ScriptObjectMirror
525            final ScriptObject exprObj = global.newObject();
526            NativeObject.bindAllProperties(exprObj, (ScriptObjectMirror)expression);
527            return new WithObject(scope, exprObj);
528        }
529
530        final Object wrappedExpr = JSType.toScriptObject(global, expression);
531        if (wrappedExpr instanceof ScriptObject) {
532            return new WithObject(scope, (ScriptObject)wrappedExpr);
533        }
534
535        throw typeError(global, "cant.apply.with.to.non.scriptobject");
536    }
537
538    /**
539     * ECMA 11.6.1 - The addition operator (+) - generic implementation
540     *
541     * @param x  first term
542     * @param y  second term
543     *
544     * @return result of addition
545     */
546    public static Object ADD(final Object x, final Object y) {
547        // This prefix code to handle Number special is for optimization.
548        final boolean xIsNumber = x instanceof Number;
549        final boolean yIsNumber = y instanceof Number;
550
551        if (xIsNumber && yIsNumber) {
552             return ((Number)x).doubleValue() + ((Number)y).doubleValue();
553        }
554
555        final boolean xIsUndefined = x == UNDEFINED;
556        final boolean yIsUndefined = y == UNDEFINED;
557
558        if (xIsNumber && yIsUndefined || xIsUndefined && yIsNumber || xIsUndefined && yIsUndefined) {
559            return Double.NaN;
560        }
561
562        // code below is as per the spec.
563        final Object xPrim = JSType.toPrimitive(x);
564        final Object yPrim = JSType.toPrimitive(y);
565
566        if (isString(xPrim) || isString(yPrim)) {
567            try {
568                return new ConsString(JSType.toCharSequence(xPrim), JSType.toCharSequence(yPrim));
569            } catch (final IllegalArgumentException iae) {
570                throw rangeError(iae, "concat.string.too.big");
571            }
572        }
573
574        return JSType.toNumber(xPrim) + JSType.toNumber(yPrim);
575    }
576
577    /**
578     * Debugger hook.
579     * TODO: currently unimplemented
580     *
581     * @return undefined
582     */
583    public static Object DEBUGGER() {
584        return UNDEFINED;
585    }
586
587    /**
588     * New hook
589     *
590     * @param clazz type for the clss
591     * @param args  constructor arguments
592     *
593     * @return undefined
594     */
595    public static Object NEW(final Object clazz, final Object... args) {
596        return UNDEFINED;
597    }
598
599    /**
600     * ECMA 11.4.3 The typeof Operator - generic implementation
601     *
602     * @param object   the object from which to retrieve property to type check
603     * @param property property in object to check
604     *
605     * @return type name
606     */
607    public static Object TYPEOF(final Object object, final Object property) {
608        Object obj = object;
609
610        if (property != null) {
611            if (obj instanceof ScriptObject) {
612                obj = ((ScriptObject)obj).get(property);
613                if(Global.isLocationPropertyPlaceholder(obj)) {
614                    if(CompilerConstants.__LINE__.name().equals(property)) {
615                        obj = 0;
616                    } else {
617                        obj = "";
618                    }
619                }
620            } else if (object instanceof Undefined) {
621                obj = ((Undefined)obj).get(property);
622            } else if (object == null) {
623                throw typeError("cant.get.property", safeToString(property), "null");
624            } else if (JSType.isPrimitive(obj)) {
625                obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property);
626            } else if (obj instanceof JSObject) {
627                obj = ((JSObject)obj).getMember(property.toString());
628            } else {
629                obj = UNDEFINED;
630            }
631        }
632
633        return JSType.of(obj).typeName();
634    }
635
636    /**
637     * Throw ReferenceError when LHS of assignment or increment/decrement
638     * operator is not an assignable node (say a literal)
639     *
640     * @param lhs Evaluated LHS
641     * @param rhs Evaluated RHS
642     * @param msg Additional LHS info for error message
643     * @return undefined
644     */
645    public static Object REFERENCE_ERROR(final Object lhs, final Object rhs, final Object msg) {
646        throw referenceError("cant.be.used.as.lhs", Objects.toString(msg));
647    }
648
649    /**
650     * ECMA 11.4.1 - delete operation, generic implementation
651     *
652     * @param obj       object with property to delete
653     * @param property  property to delete
654     * @param strict    are we in strict mode
655     *
656     * @return true if property was successfully found and deleted
657     */
658    public static boolean DELETE(final Object obj, final Object property, final Object strict) {
659        if (obj instanceof ScriptObject) {
660            return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict));
661        }
662
663        if (obj instanceof Undefined) {
664            return ((Undefined)obj).delete(property, false);
665        }
666
667        if (obj == null) {
668            throw typeError("cant.delete.property", safeToString(property), "null");
669        }
670
671        if (obj instanceof ScriptObjectMirror) {
672            return ((ScriptObjectMirror)obj).delete(property);
673        }
674
675        if (JSType.isPrimitive(obj)) {
676            return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict));
677        }
678
679        if (obj instanceof JSObject) {
680            ((JSObject)obj).removeMember(Objects.toString(property));
681            return true;
682        }
683
684        // if object is not reference type, vacuously delete is successful.
685        return true;
686    }
687
688    /**
689     * ECMA 11.4.1 - delete operator, implementation for slow scopes
690     *
691     * This implementation of 'delete' walks the scope chain to find the scope that contains the
692     * property to be deleted, then invokes delete on it.
693     *
694     * @param obj       top scope object
695     * @param property  property to delete
696     * @param strict    are we in strict mode
697     *
698     * @return true if property was successfully found and deleted
699     */
700    public static boolean SLOW_DELETE(final Object obj, final Object property, final Object strict) {
701        if (obj instanceof ScriptObject) {
702            ScriptObject sobj = (ScriptObject) obj;
703            final String key = property.toString();
704            while (sobj != null && sobj.isScope()) {
705                final FindProperty find = sobj.findProperty(key, false);
706                if (find != null) {
707                    return sobj.delete(key, Boolean.TRUE.equals(strict));
708                }
709                sobj = sobj.getProto();
710            }
711        }
712        return DELETE(obj, property, strict);
713    }
714
715    /**
716     * ECMA 11.4.1 - delete operator, special case
717     *
718     * This is 'delete' that always fails. We have to check strict mode and throw error.
719     * That is why this is a runtime function. Or else we could have inlined 'false'.
720     *
721     * @param property  property to delete
722     * @param strict    are we in strict mode
723     *
724     * @return false always
725     */
726    public static boolean FAIL_DELETE(final Object property, final Object strict) {
727        if (Boolean.TRUE.equals(strict)) {
728            throw syntaxError("strict.cant.delete", safeToString(property));
729        }
730        return false;
731    }
732
733    /**
734     * ECMA 11.9.1 - The equals operator (==) - generic implementation
735     *
736     * @param x first object to compare
737     * @param y second object to compare
738     *
739     * @return true if type coerced versions of objects are equal
740     */
741    public static boolean EQ(final Object x, final Object y) {
742        return equals(x, y);
743    }
744
745    /**
746     * ECMA 11.9.2 - The does-not-equal operator (==) - generic implementation
747     *
748     * @param x first object to compare
749     * @param y second object to compare
750     *
751     * @return true if type coerced versions of objects are not equal
752     */
753    public static boolean NE(final Object x, final Object y) {
754        return !EQ(x, y);
755    }
756
757    /** ECMA 11.9.3 The Abstract Equality Comparison Algorithm */
758    private static boolean equals(final Object x, final Object y) {
759        if (x == y) {
760            return true;
761        }
762        if (x instanceof ScriptObject && y instanceof ScriptObject) {
763            return false; // x != y
764        }
765        if (x instanceof ScriptObjectMirror || y instanceof ScriptObjectMirror) {
766            return ScriptObjectMirror.identical(x, y);
767        }
768        return equalValues(x, y);
769    }
770
771    /**
772     * Extracted portion of {@code equals()} that compares objects by value (or by reference, if no known value
773     * comparison applies).
774     * @param x one value
775     * @param y another value
776     * @return true if they're equal according to 11.9.3
777     */
778    private static boolean equalValues(final Object x, final Object y) {
779        final JSType xType = JSType.ofNoFunction(x);
780        final JSType yType = JSType.ofNoFunction(y);
781
782        if (xType == yType) {
783            return equalSameTypeValues(x, y, xType);
784        }
785
786        return equalDifferentTypeValues(x, y, xType, yType);
787    }
788
789    /**
790     * Extracted portion of {@link #equals(Object, Object)} and {@link #strictEquals(Object, Object)} that compares
791     * values belonging to the same JSType.
792     * @param x one value
793     * @param y another value
794     * @param type the common type for the values
795     * @return true if they're equal
796     */
797    private static boolean equalSameTypeValues(final Object x, final Object y, final JSType type) {
798        if (type == JSType.UNDEFINED || type == JSType.NULL) {
799            return true;
800        }
801
802        if (type == JSType.NUMBER) {
803            return ((Number)x).doubleValue() == ((Number)y).doubleValue();
804        }
805
806        if (type == JSType.STRING) {
807            // String may be represented by ConsString
808            return x.toString().equals(y.toString());
809        }
810
811        if (type == JSType.BOOLEAN) {
812            return ((Boolean)x).booleanValue() == ((Boolean)y).booleanValue();
813        }
814
815        return x == y;
816    }
817
818    /**
819     * Extracted portion of {@link #equals(Object, Object)} that compares values belonging to different JSTypes.
820     * @param x one value
821     * @param y another value
822     * @param xType the type for the value x
823     * @param yType the type for the value y
824     * @return true if they're equal
825     */
826    private static boolean equalDifferentTypeValues(final Object x, final Object y, final JSType xType, final JSType yType) {
827        if (isUndefinedAndNull(xType, yType) || isUndefinedAndNull(yType, xType)) {
828            return true;
829        } else if (isNumberAndString(xType, yType)) {
830            return equalNumberToString(x, y);
831        } else if (isNumberAndString(yType, xType)) {
832            // Can reverse order as both are primitives
833            return equalNumberToString(y, x);
834        } else if (xType == JSType.BOOLEAN) {
835            return equalBooleanToAny(x, y);
836        } else if (yType == JSType.BOOLEAN) {
837            // Can reverse order as y is primitive
838            return equalBooleanToAny(y, x);
839        } else if (isPrimitiveAndObject(xType, yType)) {
840            return equalWrappedPrimitiveToObject(x, y);
841        } else if (isPrimitiveAndObject(yType, xType)) {
842            // Can reverse order as y is primitive
843            return equalWrappedPrimitiveToObject(y, x);
844        }
845
846        return false;
847    }
848
849    private static boolean isUndefinedAndNull(final JSType xType, final JSType yType) {
850        return xType == JSType.UNDEFINED && yType == JSType.NULL;
851    }
852
853    private static boolean isNumberAndString(final JSType xType, final JSType yType) {
854        return xType == JSType.NUMBER && yType == JSType.STRING;
855    }
856
857    private static boolean isPrimitiveAndObject(final JSType xType, final JSType yType) {
858        return (xType == JSType.NUMBER || xType == JSType.STRING || xType == JSType.SYMBOL) && yType == JSType.OBJECT;
859    }
860
861    private static boolean equalNumberToString(final Object num, final Object str) {
862        // Specification says comparing a number to string should be done as "equals(num, JSType.toNumber(str))". We
863        // can short circuit it to this as we know that "num" is a number, so it'll end up being a number-number
864        // comparison.
865        return ((Number)num).doubleValue() == JSType.toNumber(str.toString());
866    }
867
868    private static boolean equalBooleanToAny(final Object bool, final Object any) {
869        return equals(JSType.toNumber((Boolean)bool), any);
870    }
871
872    private static boolean equalWrappedPrimitiveToObject(final Object numOrStr, final Object any) {
873        return equals(numOrStr, JSType.toPrimitive(any));
874    }
875
876    /**
877     * ECMA 11.9.4 - The strict equal operator (===) - generic implementation
878     *
879     * @param x first object to compare
880     * @param y second object to compare
881     *
882     * @return true if objects are equal
883     */
884    public static boolean EQ_STRICT(final Object x, final Object y) {
885        return strictEquals(x, y);
886    }
887
888    /**
889     * ECMA 11.9.5 - The strict non equal operator (!==) - generic implementation
890     *
891     * @param x first object to compare
892     * @param y second object to compare
893     *
894     * @return true if objects are not equal
895     */
896    public static boolean NE_STRICT(final Object x, final Object y) {
897        return !EQ_STRICT(x, y);
898    }
899
900    /** ECMA 11.9.6 The Strict Equality Comparison Algorithm */
901    private static boolean strictEquals(final Object x, final Object y) {
902        // NOTE: you might be tempted to do a quick x == y comparison. Remember, though, that any Double object having
903        // NaN value is not equal to itself by value even though it is referentially.
904
905        final JSType xType = JSType.ofNoFunction(x);
906        final JSType yType = JSType.ofNoFunction(y);
907
908        if (xType != yType) {
909            return false;
910        }
911
912        return equalSameTypeValues(x, y, xType);
913    }
914
915    /**
916     * ECMA 11.8.6 - The in operator - generic implementation
917     *
918     * @param property property to check for
919     * @param obj object in which to check for property
920     *
921     * @return true if objects are equal
922     */
923    public static boolean IN(final Object property, final Object obj) {
924        final JSType rvalType = JSType.ofNoFunction(obj);
925
926        if (rvalType == JSType.OBJECT) {
927            if (obj instanceof ScriptObject) {
928                return ((ScriptObject)obj).has(property);
929            }
930
931            if (obj instanceof JSObject) {
932                return ((JSObject)obj).hasMember(Objects.toString(property));
933            }
934
935            return false;
936        }
937
938        throw typeError("in.with.non.object", rvalType.toString().toLowerCase(Locale.ENGLISH));
939    }
940
941    /**
942     * ECMA 11.8.6 - The strict instanceof operator - generic implementation
943     *
944     * @param obj first object to compare
945     * @param clazz type to check against
946     *
947     * @return true if {@code obj} is an instanceof {@code clazz}
948     */
949    public static boolean INSTANCEOF(final Object obj, final Object clazz) {
950        if (clazz instanceof ScriptFunction) {
951            if (obj instanceof ScriptObject) {
952                return ((ScriptObject)clazz).isInstance((ScriptObject)obj);
953            }
954            return false;
955        }
956
957        if (clazz instanceof StaticClass) {
958            return ((StaticClass)clazz).getRepresentedClass().isInstance(obj);
959        }
960
961        if (clazz instanceof JSObject) {
962            return ((JSObject)clazz).isInstance(obj);
963        }
964
965        // provide for reverse hook
966        if (obj instanceof JSObject) {
967            return ((JSObject)obj).isInstanceOf(clazz);
968        }
969
970        throw typeError("instanceof.on.non.object");
971    }
972
973    /**
974     * ECMA 11.8.1 - The less than operator ({@literal <}) - generic implementation
975     *
976     * @param x first object to compare
977     * @param y second object to compare
978     *
979     * @return true if x is less than y
980     */
981    public static boolean LT(final Object x, final Object y) {
982        final Object px = JSType.toPrimitive(x, Number.class);
983        final Object py = JSType.toPrimitive(y, Number.class);
984
985        return areBothString(px, py) ? px.toString().compareTo(py.toString()) < 0 :
986            JSType.toNumber(px) < JSType.toNumber(py);
987    }
988
989    private static boolean areBothString(final Object x, final Object y) {
990        return isString(x) && isString(y);
991    }
992
993    /**
994     * ECMA 11.8.2 - The greater than operator ({@literal >}) - generic implementation
995     *
996     * @param x first object to compare
997     * @param y second object to compare
998     *
999     * @return true if x is greater than y
1000     */
1001    public static boolean GT(final Object x, final Object y) {
1002        final Object px = JSType.toPrimitive(x, Number.class);
1003        final Object py = JSType.toPrimitive(y, Number.class);
1004
1005        return areBothString(px, py) ? px.toString().compareTo(py.toString()) > 0 :
1006            JSType.toNumber(px) > JSType.toNumber(py);
1007    }
1008
1009    /**
1010     * ECMA 11.8.3 - The less than or equal operator ({@literal <=}) - generic implementation
1011     *
1012     * @param x first object to compare
1013     * @param y second object to compare
1014     *
1015     * @return true if x is less than or equal to y
1016     */
1017    public static boolean LE(final Object x, final Object y) {
1018        final Object px = JSType.toPrimitive(x, Number.class);
1019        final Object py = JSType.toPrimitive(y, Number.class);
1020
1021        return areBothString(px, py) ? px.toString().compareTo(py.toString()) <= 0 :
1022            JSType.toNumber(px) <= JSType.toNumber(py);
1023    }
1024
1025    /**
1026     * ECMA 11.8.4 - The greater than or equal operator ({@literal >=}) - generic implementation
1027     *
1028     * @param x first object to compare
1029     * @param y second object to compare
1030     *
1031     * @return true if x is greater than or equal to y
1032     */
1033    public static boolean GE(final Object x, final Object y) {
1034        final Object px = JSType.toPrimitive(x, Number.class);
1035        final Object py = JSType.toPrimitive(y, Number.class);
1036
1037        return areBothString(px, py) ? px.toString().compareTo(py.toString()) >= 0 :
1038            JSType.toNumber(px) >= JSType.toNumber(py);
1039    }
1040
1041    /**
1042     * Tag a reserved name as invalidated - used when someone writes
1043     * to a property with this name - overly conservative, but link time
1044     * is too late to apply e.g. apply-&gt;call specialization
1045     * @param name property name
1046     */
1047    public static void invalidateReservedBuiltinName(final String name) {
1048        final Context context = Context.getContextTrusted();
1049        final SwitchPoint sp = context.getBuiltinSwitchPoint(name);
1050        assert sp != null;
1051        context.getLogger(ApplySpecialization.class).info("Overwrote special name '" + name +"' - invalidating switchpoint");
1052        SwitchPoint.invalidateAll(new SwitchPoint[] { sp });
1053    }
1054
1055    /**
1056     * ES6 12.2.9.3 Runtime Semantics: GetTemplateObject(templateLiteral).
1057     *
1058     * @param rawStrings array of template raw values
1059     * @param cookedStrings array of template values
1060     * @return template object
1061     */
1062    public static ScriptObject GET_TEMPLATE_OBJECT(final Object rawStrings, final Object cookedStrings) {
1063        final ScriptObject template = (ScriptObject)cookedStrings;
1064        final ScriptObject rawObj = (ScriptObject)rawStrings;
1065        assert rawObj.getArray().length() == template.getArray().length();
1066        template.addOwnProperty("raw", Property.NOT_WRITABLE | Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE, rawObj.freeze());
1067        template.freeze();
1068        return template;
1069    }
1070}
1071