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