forked from mozilla/rhino
-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathScriptRuntime.java
More file actions
6293 lines (5690 loc) · 237 KB
/
ScriptRuntime.java
File metadata and controls
6293 lines (5690 loc) · 237 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.ServiceLoader;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.dtoa.DoubleFormatter;
import org.mozilla.javascript.lc.type.TypeInfo;
import org.mozilla.javascript.lc.type.impl.factory.ClassValueCacheFactory;
import org.mozilla.javascript.lc.type.impl.factory.LegacyCacheFactory;
import org.mozilla.javascript.typedarrays.NativeArrayBuffer;
import org.mozilla.javascript.typedarrays.NativeBigInt64Array;
import org.mozilla.javascript.typedarrays.NativeBigUint64Array;
import org.mozilla.javascript.typedarrays.NativeDataView;
import org.mozilla.javascript.typedarrays.NativeFloat16Array;
import org.mozilla.javascript.typedarrays.NativeFloat32Array;
import org.mozilla.javascript.typedarrays.NativeFloat64Array;
import org.mozilla.javascript.typedarrays.NativeInt16Array;
import org.mozilla.javascript.typedarrays.NativeInt32Array;
import org.mozilla.javascript.typedarrays.NativeInt8Array;
import org.mozilla.javascript.typedarrays.NativeUint16Array;
import org.mozilla.javascript.typedarrays.NativeUint32Array;
import org.mozilla.javascript.typedarrays.NativeUint8Array;
import org.mozilla.javascript.typedarrays.NativeUint8ClampedArray;
import org.mozilla.javascript.v8dtoa.DoubleConversion;
import org.mozilla.javascript.xml.XMLLib;
import org.mozilla.javascript.xml.XMLLoader;
import org.mozilla.javascript.xml.XMLObject;
/**
* This is the class that implements the runtime.
*
* @author Norris Boyd
*/
public class ScriptRuntime {
/** No instances should be created. */
protected ScriptRuntime() {}
/**
* Returns representation of the [[ThrowTypeError]] object. See ECMA 5 spec, 13.2.3
*
* @return a {@link BaseFunction}
* @deprecated {@link #typeErrorThrower(Context)}
*/
@Deprecated
public static BaseFunction typeErrorThrower() {
return typeErrorThrower(Context.getCurrentContext());
}
/** Returns representation of the [[ThrowTypeError]] object. See ECMA 5 spec, 13.2.3 */
public static BaseFunction typeErrorThrower(Context cx) {
if (cx.typeErrorThrower == null) {
BaseFunction thrower = new ThrowTypeError(cx.topCallScope);
cx.typeErrorThrower = thrower;
}
return cx.typeErrorThrower;
}
static final class ThrowTypeError extends BaseFunction {
private static final long serialVersionUID = -5891740962154902286L;
ThrowTypeError(Scriptable scope) {
setPrototype(ScriptableObject.getFunctionPrototype(scope));
setAttributes("length", DONTENUM | PERMANENT | READONLY);
setAttributes("name", DONTENUM | PERMANENT | READONLY);
// delete arity and arguments (without further checking)
getMap().compute(this, "arity", 0, ThrowTypeError::removeWithoutChecking);
getMap().compute(this, "arguments", 0, ThrowTypeError::removeWithoutChecking);
preventExtensions();
}
private static <T extends PropHolder<T>> Slot<T> removeWithoutChecking(
Object key,
int index,
Slot slot,
CompoundOperationMap<T> compoundOp,
SlotMapOwner<T> owner) {
return null;
}
@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
throwNotAllowed();
return null;
}
public static void throwNotAllowed() {
throw typeErrorById("msg.op.not.allowed");
}
}
public static Object concat(Object lhs, Object rhs) {
String rhsString = ScriptRuntime.toString(rhs);
String lhsString = ScriptRuntime.toString(lhs);
return new ConsString(lhsString, rhsString);
}
static class NoSuchMethodShim implements Callable {
String methodName;
Callable noSuchMethodMethod;
NoSuchMethodShim(Callable noSuchMethodMethod, String methodName) {
this.noSuchMethodMethod = noSuchMethodMethod;
this.methodName = methodName;
}
/**
* Perform the call.
*
* @param cx the current Context for this thread
* @param scope the scope to use to resolve properties.
* @param thisObj the JavaScript {@code this} object
* @param args the array of arguments
* @return the result of the call
*/
@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Object[] nestedArgs = new Object[2];
nestedArgs[0] = methodName;
nestedArgs[1] = newArrayLiteral(args, null, cx, scope);
return noSuchMethodMethod.call(cx, scope, thisObj, nestedArgs);
}
}
/*
* There's such a huge space (and some time) waste for the Foo.class
* syntax: the compiler sticks in a test of a static field in the
* enclosing class for null and the code for creating the class value.
* It has to do this since the reference has to get pushed off until
* execution time (i.e. can't force an early load), but for the
* 'standard' classes - especially those in java.lang, we can trust
* that they won't cause problems by being loaded early.
*/
public static final Class<?> BooleanClass = Kit.classOrNull("java.lang.Boolean"),
ByteClass = Kit.classOrNull("java.lang.Byte"),
CharacterClass = Kit.classOrNull("java.lang.Character"),
ClassClass = Kit.classOrNull("java.lang.Class"),
DoubleClass = Kit.classOrNull("java.lang.Double"),
FloatClass = Kit.classOrNull("java.lang.Float"),
IntegerClass = Kit.classOrNull("java.lang.Integer"),
LongClass = Kit.classOrNull("java.lang.Long"),
NumberClass = Kit.classOrNull("java.lang.Number"),
ObjectClass = Kit.classOrNull("java.lang.Object"),
ShortClass = Kit.classOrNull("java.lang.Short"),
StringClass = Kit.classOrNull("java.lang.String"),
DateClass = Kit.classOrNull("java.util.Date"),
BigIntegerClass = Kit.classOrNull("java.math.BigInteger");
public static final Class<?> ContextClass = Kit.classOrNull("org.mozilla.javascript.Context"),
ContextFactoryClass = Kit.classOrNull("org.mozilla.javascript.ContextFactory"),
FunctionClass = Kit.classOrNull("org.mozilla.javascript.Function"),
ScriptableObjectClass = Kit.classOrNull("org.mozilla.javascript.ScriptableObject");
public static final Class<Scriptable> ScriptableClass = Scriptable.class;
private static final Object LIBRARY_SCOPE_KEY = "LIBRARY_SCOPE";
public static boolean isRhinoRuntimeType(Class<?> cl) {
if (cl.isPrimitive()) {
return (cl != Character.TYPE);
}
return (cl == StringClass
|| cl == BooleanClass
|| NumberClass.isAssignableFrom(cl)
|| ScriptableClass.isAssignableFrom(cl));
}
public static TopLevel initSafeStandardObjects(Context cx, TopLevel scope, boolean sealed) {
if (scope == null) {
scope = new TopLevel();
}
scope.put("global", scope, scope.getGlobalThis());
scope.clearCache();
scope.associateValue(LIBRARY_SCOPE_KEY, scope);
new ClassCache().associate(scope);
var typeFactory =
(androidApi >= 34 || androidApi < 0)
? new ClassValueCacheFactory.Concurrent()
: new LegacyCacheFactory.Concurrent();
typeFactory.associate(scope);
LambdaConstructor function = BaseFunction.init(cx, scope, sealed);
JSFunction obj = NativeObject.init(cx, scope, sealed);
ScriptableObject objectPrototype = (ScriptableObject) obj.getPrototypeProperty();
ScriptableObject functionPrototype = (ScriptableObject) function.getPrototypeProperty();
objectPrototype.setPrototype(null);
functionPrototype.setPrototype(objectPrototype);
function.setPrototype(functionPrototype);
obj.setPrototype(functionPrototype);
// Set the prototype of the object passed in if need be
if (scope.getPrototype() == null) scope.setPrototype(objectPrototype);
// must precede NativeGlobal since it's needed therein
NativeError.init(cx, scope, sealed);
NativeGlobal.init(cx, scope, sealed);
NativeArray.init(cx, scope, sealed);
if (!cx.isInterpretedMode()) {
// When optimizing, attempt to fulfill all requests for new Array(N)
// with a higher threshold before switching to a sparse
// representation
NativeArray.setMaximumInitialCapacity(200000);
}
NativeString.init(scope, sealed);
NativeBoolean.init(cx, scope, sealed);
NativeNumber.init(scope, sealed);
NativeDate.init(cx, scope, sealed);
new LazilyLoadedCtor(scope, "Math", sealed, true, NativeMath::init);
new LazilyLoadedCtor(scope, "JSON", sealed, true, NativeJSON::init);
NativeWith.init(scope, sealed);
NativeCall.init(scope, sealed);
NativeScript.init(cx, scope, sealed);
NativeIterator.init(cx, scope, sealed); // Also initializes NativeGenerator & ES6Generator
NativeArrayIterator.init(scope, sealed);
NativeStringIterator.init(scope, sealed);
registerRegExp(cx, scope, sealed);
NativeJavaObject.init(scope, sealed);
NativeJavaMap.init(scope, sealed);
// define lazy-loaded properties using their class name
// Depends on the old reflection-based lazy loading mechanism
// to property initialize the prototype.
new LazilyLoadedCtor(
scope, "Continuation", "org.mozilla.javascript.NativeContinuation", sealed, true);
if (cx.hasFeature(Context.FEATURE_E4X)) {
if (xmlLoaderImpl != null) {
xmlLoaderImpl.load(scope, sealed);
}
}
if (((cx.getLanguageVersion() >= Context.VERSION_1_8)
&& cx.hasFeature(Context.FEATURE_V8_EXTENSIONS))
|| (cx.getLanguageVersion() >= Context.VERSION_ES6)) {
new LazilyLoadedCtor(scope, "ArrayBuffer", sealed, true, NativeArrayBuffer::init);
new LazilyLoadedCtor(scope, "Int8Array", sealed, true, NativeInt8Array::init);
new LazilyLoadedCtor(scope, "Uint8Array", sealed, true, NativeUint8Array::init);
new LazilyLoadedCtor(
scope, "Uint8ClampedArray", sealed, true, NativeUint8ClampedArray::init);
new LazilyLoadedCtor(scope, "Int16Array", sealed, true, NativeInt16Array::init);
new LazilyLoadedCtor(scope, "Uint16Array", sealed, true, NativeUint16Array::init);
new LazilyLoadedCtor(scope, "Int32Array", sealed, true, NativeInt32Array::init);
new LazilyLoadedCtor(scope, "Uint32Array", sealed, true, NativeUint32Array::init);
new LazilyLoadedCtor(scope, "BigInt64Array", sealed, true, NativeBigInt64Array::init);
new LazilyLoadedCtor(scope, "BigUint64Array", sealed, true, NativeBigUint64Array::init);
new LazilyLoadedCtor(scope, "Float16Array", sealed, true, NativeFloat16Array::init);
new LazilyLoadedCtor(scope, "Float32Array", sealed, true, NativeFloat32Array::init);
new LazilyLoadedCtor(scope, "Float64Array", sealed, true, NativeFloat64Array::init);
new LazilyLoadedCtor(scope, "DataView", sealed, true, NativeDataView::init);
}
if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
NativeSymbol.init(cx, scope, sealed);
NativeCollectionIterator.init(scope, NativeSet.ITERATOR_TAG, sealed);
NativeCollectionIterator.init(scope, NativeMap.ITERATOR_TAG, sealed);
new LazilyLoadedCtor(scope, "Map", sealed, true, NativeMap::init);
new LazilyLoadedCtor(scope, "Promise", sealed, true, NativePromise::init);
new LazilyLoadedCtor(scope, "Set", sealed, true, NativeSet::init);
new LazilyLoadedCtor(scope, "WeakMap", sealed, true, NativeWeakMap::init);
new LazilyLoadedCtor(scope, "WeakSet", sealed, true, NativeWeakSet::init);
new LazilyLoadedCtor(scope, "BigInt", sealed, true, NativeBigInt::init);
new LazilyLoadedCtor(scope, "Proxy", sealed, true, NativeProxy::init);
new LazilyLoadedCtor(scope, "Reflect", sealed, true, NativeReflect::init);
}
scope.cacheBuiltins(sealed);
return scope;
}
private static void registerRegExp(Context cx, TopLevel scope, boolean sealed) {
RegExpProxy regExpProxy = getRegExpProxy(cx);
if (regExpProxy != null) {
regExpProxy.register(scope, sealed);
}
}
public static TopLevel initStandardObjects(Context cx, TopLevel scope, boolean sealed) {
TopLevel s = initSafeStandardObjects(cx, scope, sealed);
// These depend on the legacy initialization behavior of the lazy loading mechanism
new LazilyLoadedCtor(
s, "Packages", "org.mozilla.javascript.NativeJavaTopPackage", sealed, true);
new LazilyLoadedCtor(
s, "getClass", "org.mozilla.javascript.NativeJavaTopPackage", sealed, true);
new LazilyLoadedCtor(s, "JavaAdapter", "org.mozilla.javascript.JavaAdapter", sealed, true);
new LazilyLoadedCtor(
s, "JavaImporter", "org.mozilla.javascript.ImporterTopLevel", sealed, true);
for (String packageName : getTopPackageNames()) {
new LazilyLoadedCtor(
s, packageName, "org.mozilla.javascript.NativeJavaTopPackage", sealed, true);
}
return s;
}
static String[] getTopPackageNames() {
// Include "android" top package if running on Android
return androidApi > 0
? new String[] {"java", "javax", "org", "com", "edu", "net", "android"}
: new String[] {"java", "javax", "org", "com", "edu", "net"};
}
public static ScriptableObject getLibraryScopeOrNull(Scriptable scope) {
ScriptableObject libScope;
libScope = (ScriptableObject) ScriptableObject.getTopScopeValue(scope, LIBRARY_SCOPE_KEY);
return libScope;
}
// It is public so NativeRegExp can access it.
public static boolean isJSLineTerminator(int c) {
// Optimization for faster check for eol character:
// they do not have 0xDFD0 bits set
if ((c & 0xDFD0) != 0) {
return false;
}
return c == '\n' || c == '\r' || c == 0x2028 || c == 0x2029;
}
public static boolean isJSWhitespaceOrLineTerminator(int c) {
return (isStrWhiteSpaceChar(c) || isJSLineTerminator(c));
}
/**
* Indicates if the character is a Str whitespace char according to ECMA spec: StrWhiteSpaceChar
* ::: <TAB> <SP> <NBSP> <FF> <VT> <CR> <LF> <LS> <PS> <USP> <BOM>
*/
static boolean isStrWhiteSpaceChar(int c) {
switch (c) {
case ' ': // <SP>
case '\n': // <LF>
case '\r': // <CR>
case '\t': // <TAB>
case '\u00A0': // <NBSP>
case '\u000C': // <FF>
case '\u000B': // <VT>
case '\u2028': // <LS>
case '\u2029': // <PS>
case '\uFEFF': // <BOM>
return true;
default:
return Character.getType(c) == Character.SPACE_SEPARATOR;
}
}
public static Boolean wrapBoolean(boolean b) {
return Boolean.valueOf(b);
}
public static Integer wrapInt(int i) {
return Integer.valueOf(i);
}
public static Number wrapNumber(double x) {
if (Double.isNaN(x)) {
return ScriptRuntime.NaNobj;
}
return Double.valueOf(x);
}
/**
* Convert the value to a boolean.
*
* <p>See ECMA 9.2.
*/
public static boolean toBoolean(Object val) {
for (; ; ) {
if (val instanceof Boolean) return ((Boolean) val).booleanValue();
if (val == null || Undefined.isUndefined(val)) return false;
if (val instanceof CharSequence) return ((CharSequence) val).length() != 0;
if (val instanceof BigInteger) {
return !BigInteger.ZERO.equals(val);
}
if (val instanceof Number) {
double d = ((Number) val).doubleValue();
return (!Double.isNaN(d) && d != 0.0);
}
if (val instanceof Scriptable) {
if (val instanceof ScriptableObject
&& ((ScriptableObject) val).avoidObjectDetection()) {
return false;
}
if (Context.getContext().isVersionECMA1()) {
// pure ECMA
return true;
}
// ECMA extension
val = ((Scriptable) val).getDefaultValue(BooleanClass);
if ((val instanceof Scriptable) && !isSymbol(val))
throw errorWithClassName("msg.primitive.expected", val);
continue;
}
warnAboutNonJSObject(val);
return true;
}
}
/**
* Convert the value to a number.
*
* <p>See ECMA 9.3.
*/
public static double toNumber(Object val) {
for (; ; ) {
if (val instanceof BigInteger) {
throw typeErrorById("msg.cant.convert.to.number", "BigInt");
}
if (val instanceof Number) return ((Number) val).doubleValue();
if (val == null) return +0.0;
if (Undefined.isUndefined(val)) return NaN;
if (val instanceof String) return toNumber((String) val);
if (val instanceof CharSequence) return toNumber(val.toString());
if (val instanceof Boolean) return ((Boolean) val).booleanValue() ? 1 : +0.0;
if (isSymbol(val)) throw typeErrorById("msg.not.a.number");
if (val instanceof Scriptable) {
// Assert: val is an Object
val = toPrimitive(val, NumberClass);
// Assert: val is a primitive
} else {
warnAboutNonJSObject(val);
return Double.NaN;
}
}
}
public static double toNumber(Object[] args, int index) {
return (index < args.length) ? toNumber(args[index]) : NaN;
}
public static final double NaN = Double.NaN;
public static final Double NaNobj = Double.valueOf(NaN);
// Preserve backward-compatibility with historical value of this.
public static final double negativeZero = Double.longBitsToDouble(0x8000000000000000L);
public static final Integer zeroObj = Integer.valueOf(0);
public static final Double negativeZeroObj = Double.valueOf(-0.0);
static double stringPrefixToNumber(String s, int start, int radix) {
return stringToNumber(s, start, s.length() - 1, radix, true);
}
static double stringToNumber(String s, int start, int end, int radix) {
return stringToNumber(s, start, end, radix, false);
}
/*
* Helper function for toNumber, parseInt, and TokenStream.getToken.
*/
private static double stringToNumber(
String source, int sourceStart, int sourceEnd, int radix, boolean isPrefix) {
char digitMax = '9';
char lowerCaseBound = 'a';
char upperCaseBound = 'A';
if (radix < 10) {
digitMax = (char) ('0' + radix - 1);
}
if (radix > 10) {
lowerCaseBound = (char) ('a' + radix - 10);
upperCaseBound = (char) ('A' + radix - 10);
}
int end;
double sum = 0.0;
for (end = sourceStart; end <= sourceEnd; end++) {
char c = source.charAt(end);
int newDigit;
if ('0' <= c && c <= digitMax) newDigit = c - '0';
else if ('a' <= c && c < lowerCaseBound) newDigit = c - 'a' + 10;
else if ('A' <= c && c < upperCaseBound) newDigit = c - 'A' + 10;
else if (!isPrefix) return NaN; // isn't a prefix but found unexpected char
else break; // unexpected char
sum = sum * radix + newDigit;
}
if (sourceStart == end) { // stopped right at the beginning
return NaN;
}
if (sum > NativeNumber.MAX_SAFE_INTEGER) {
if (radix == 10) {
/* If we're accumulating a decimal number and the number
* is >= 2^53, then the result from the repeated multiply-add
* above may be inaccurate. Call Java to get the correct
* answer.
*/
try {
return Double.parseDouble(source.substring(sourceStart, end));
} catch (NumberFormatException nfe) {
return NaN;
}
} else if (radix == 2 || radix == 4 || radix == 8 || radix == 16 || radix == 32) {
/* The number may also be inaccurate for one of these bases.
* This happens if the addition in value*radix + digit causes
* a round-down to an even least significant mantissa bit
* when the first dropped bit is a one. If any of the
* following digits in the number (which haven't been added
* in yet) are nonzero then the correct action would have
* been to round up instead of down. An example of this
* occurs when reading the number 0x1000000000000081, which
* rounds to 0x1000000000000000 instead of 0x1000000000000100.
*/
int bitShiftInChar = 1;
int digit = 0;
final int SKIP_LEADING_ZEROS = 0;
final int FIRST_EXACT_53_BITS = 1;
final int AFTER_BIT_53 = 2;
final int ZEROS_AFTER_54 = 3;
final int MIXED_AFTER_54 = 4;
int state = SKIP_LEADING_ZEROS;
int exactBitsLimit = 53;
double factor = 0.0;
boolean bit53 = false;
// bit54 is the 54th bit (the first dropped from the mantissa)
boolean bit54 = false;
int pos = sourceStart;
for (; ; ) {
if (bitShiftInChar == 1) {
if (pos == end) break;
digit = source.charAt(pos++);
if ('0' <= digit && digit <= '9') digit -= '0';
else if ('a' <= digit && digit <= 'z') digit -= 'a' - 10;
else digit -= 'A' - 10;
bitShiftInChar = radix;
}
bitShiftInChar >>= 1;
boolean bit = (digit & bitShiftInChar) != 0;
switch (state) {
case SKIP_LEADING_ZEROS:
if (bit) {
--exactBitsLimit;
sum = 1.0;
state = FIRST_EXACT_53_BITS;
}
break;
case FIRST_EXACT_53_BITS:
sum *= 2.0;
if (bit) sum += 1.0;
--exactBitsLimit;
if (exactBitsLimit == 0) {
bit53 = bit;
state = AFTER_BIT_53;
}
break;
case AFTER_BIT_53:
bit54 = bit;
factor = 2.0;
state = ZEROS_AFTER_54;
break;
case ZEROS_AFTER_54:
if (bit) {
state = MIXED_AFTER_54;
}
// fallthrough
case MIXED_AFTER_54:
factor *= 2;
break;
}
}
switch (state) {
case SKIP_LEADING_ZEROS:
sum = 0.0;
break;
case FIRST_EXACT_53_BITS:
case AFTER_BIT_53:
// do nothing
break;
case ZEROS_AFTER_54:
// x1.1 -> x1 + 1 (round up)
// x0.1 -> x0 (round down)
if (bit54 && bit53) sum += 1.0;
sum *= factor;
break;
case MIXED_AFTER_54:
// x.100...1.. -> x + 1 (round up)
// x.0anything -> x (round down)
if (bit54) sum += 1.0;
sum *= factor;
break;
}
}
/* We don't worry about inaccurate numbers for any other base. */
}
return sum;
}
/**
* ToNumber applied to the String type
*
* <p>See the #sec-tonumber-applied-to-the-string-type section of ECMA
*/
public static double toNumber(String s) {
final int len = s.length();
// Skip whitespace at the start
int start = 0;
char startChar;
for (; ; ) {
if (start == len) {
// empty or contains only whitespace
return +0.0;
}
startChar = s.charAt(start);
if (!ScriptRuntime.isStrWhiteSpaceChar(startChar)) {
// found first non-whitespace character
break;
}
start++;
}
// Skip whitespace at the end
int end = len - 1;
char endChar;
while (ScriptRuntime.isStrWhiteSpaceChar(endChar = s.charAt(end))) {
end--;
}
// Do not break scripts relying on old non-compliant conversion
// (see bug #368)
// 1. makes ToNumber parse only a valid prefix in hex literals (similar to 'parseInt()')
// ToNumber('0x10 something') => 16
// 2. allows plus and minus signs for hexadecimal numbers
// ToNumber('-0x10') => -16
// 3. disables support for binary ('0b10') and octal ('0o13') literals
// ToNumber('0b1') => NaN
// ToNumber('0o5') => NaN
final Context cx = Context.getCurrentContext();
final boolean oldParsingMode = cx == null || cx.getLanguageVersion() < Context.VERSION_ES6;
// Handle non-base10 numbers
if (startChar == '0') {
if (start + 2 <= end) {
final char radixC = s.charAt(start + 1);
int radix = -1;
if (radixC == 'x' || radixC == 'X') {
radix = 16;
} else if (!oldParsingMode && (radixC == 'o' || radixC == 'O')) {
radix = 8;
} else if (!oldParsingMode && (radixC == 'b' || radixC == 'B')) {
radix = 2;
}
if (radix != -1) {
if (oldParsingMode) {
return stringPrefixToNumber(s, start + 2, radix);
}
return stringToNumber(s, start + 2, end, radix);
}
}
} else if (oldParsingMode && (startChar == '+' || startChar == '-')) {
// If in old parsing mode, check for a signed hexadecimal number
if (start + 3 <= end && s.charAt(start + 1) == '0') {
final char radixC = s.charAt(start + 2);
if (radixC == 'x' || radixC == 'X') {
double val = stringPrefixToNumber(s, start + 3, 16);
return startChar == '-' ? -val : val;
}
}
}
if (endChar == 'y') {
// check for "Infinity"
if (startChar == '+' || startChar == '-') {
start++;
}
if (start + 7 == end && s.regionMatches(start, "Infinity", 0, 8)) {
return startChar == '-' ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
}
return NaN;
}
// A base10, non-infinity number:
// just try a normal floating point conversion
String sub = s.substring(start, end + 1);
// Quick test to check string contains only valid characters because
// Double.parseDouble() can be slow and accept input we want to reject
for (int i = sub.length() - 1; i >= 0; i--) {
char c = sub.charAt(i);
if (('0' <= c && c <= '9') || c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-')
continue;
return NaN;
}
try {
return Double.parseDouble(sub);
} catch (NumberFormatException ex) {
return NaN;
}
}
/** Convert the value to a BigInt. */
public static BigInteger toBigInt(Object val) {
val = toPrimitive(val, NumberClass);
if (val instanceof BigInteger) {
return (BigInteger) val;
}
if (val instanceof BigDecimal) {
return ((BigDecimal) val).toBigInteger();
}
if (val instanceof Number) {
if (val instanceof Long) {
return BigInteger.valueOf(((Long) val));
} else {
double d = ((Number) val).doubleValue();
if (Double.isNaN(d) || Double.isInfinite(d)) {
throw rangeErrorById("msg.cant.convert.to.bigint.isnt.integer", toString(val));
}
BigDecimal bd = new BigDecimal(d, MathContext.UNLIMITED);
try {
return bd.toBigIntegerExact();
} catch (ArithmeticException e) {
throw rangeErrorById("msg.cant.convert.to.bigint.isnt.integer", toString(val));
}
}
}
if (val == null || Undefined.isUndefined(val)) {
throw typeErrorById("msg.cant.convert.to.bigint", toString(val));
}
if (val instanceof String) {
return toBigInt((String) val);
}
if (val instanceof CharSequence) {
return toBigInt(val.toString());
}
if (val instanceof Boolean) {
return ((Boolean) val).booleanValue() ? BigInteger.ONE : BigInteger.ZERO;
}
if (isSymbol(val)) {
throw typeErrorById("msg.cant.convert.to.bigint", toString(val));
}
throw errorWithClassName("msg.primitive.expected", val);
}
/** ToBigInt applied to the String type */
public static BigInteger toBigInt(String s) {
final int len = s.length();
// Skip whitespace at the start
int start = 0;
char startChar;
for (; ; ) {
if (start == len) {
// empty or contains only whitespace
return BigInteger.ZERO;
}
startChar = s.charAt(start);
if (!ScriptRuntime.isStrWhiteSpaceChar(startChar)) {
// found first non-whitespace character
break;
}
start++;
}
// Skip whitespace at the end
int end = len - 1;
while (ScriptRuntime.isStrWhiteSpaceChar(s.charAt(end))) {
end--;
}
// Handle non-base10 numbers
if (startChar == '0') {
if (start + 2 <= end) {
final char radixC = s.charAt(start + 1);
int radix = -1;
if (radixC == 'x' || radixC == 'X') {
radix = 16;
} else if (radixC == 'o' || radixC == 'O') {
radix = 8;
} else if (radixC == 'b' || radixC == 'B') {
radix = 2;
}
if (radix != -1) {
try {
return new BigInteger(s.substring(start + 2, end + 1), radix);
} catch (NumberFormatException ex) {
throw syntaxErrorById("msg.bigint.bad.form");
}
}
}
}
// A base10, non-infinity bigint:
// just try a normal biginteger conversion
String sub = s.substring(start, end + 1);
for (int i = sub.length() - 1; i >= 0; i--) {
char c = sub.charAt(i);
if (i == 0 && (c == '+' || c == '-')) {
continue;
}
if ('0' <= c && c <= '9') {
continue;
}
throw syntaxErrorById("msg.bigint.bad.form");
}
try {
return new BigInteger(sub);
} catch (NumberFormatException ex) {
throw syntaxErrorById("msg.bigint.bad.form");
}
}
/**
* Convert the value to a Numeric (Number or BigInt).
*
* <p>toNumber does not allow java.math.BigInteger. toNumeric allows java.math.BigInteger.
*
* <p>See ECMA 7.1.3 (v11.0).
*/
public static Number toNumeric(Object val) {
val = toPrimitive(val, NumberClass);
if (val instanceof Number) {
return (Number) val;
}
return toNumber(val);
}
public static int toIndex(Object val) {
if (Undefined.isUndefined(val)) {
return 0;
}
double integerIndex = toInteger(val);
if (integerIndex < 0) {
throw rangeErrorById("msg.out.of.range.index", integerIndex);
}
// ToLength
double index = Math.min(integerIndex, NativeNumber.MAX_SAFE_INTEGER);
if (integerIndex != index) {
throw rangeErrorById("msg.out.of.range.index", integerIndex);
}
return (int) index;
}
/**
* Helper function for builtin objects that use the varargs form. ECMA function formal arguments
* are undefined if not supplied; this function pads the argument array out to the expected
* length, if necessary.
*/
public static Object[] padArguments(Object[] args, int count) {
if (count < args.length) return args;
Object[] result = new Object[count];
System.arraycopy(args, 0, result, 0, args.length);
if (args.length < count) {
Arrays.fill(result, args.length, count, Undefined.instance);
}
return result;
}
/**
* Helper function for builtin objects that use the varargs form. ECMA function formal arguments
* are undefined if not supplied; this function pads the argument array out to the expected
* length, if necessary. Also the rest parameter array construction is done here.
*/
public static Object[] padAndRestArguments(
Context cx, Scriptable scope, Object[] args, int argCount) {
Object[] result = new Object[argCount];
int paramCount = argCount - 1;
if (args.length < paramCount) {
System.arraycopy(args, 0, result, 0, args.length);
Arrays.fill(result, args.length, paramCount, Undefined.instance);
} else {
System.arraycopy(args, 0, result, 0, paramCount);
}
Object[] restValues;
if (args.length > paramCount) {
restValues = new Object[args.length - paramCount];
System.arraycopy(args, paramCount, restValues, 0, restValues.length);
} else {
restValues = ScriptRuntime.emptyArgs;
}
result[paramCount] = cx.newArray(scope, restValues);
return result;
}
/**
* Helper for object rest properties in destructuring. Creates a shallow copy of the source
* object excluding the specified keys.
*
* @param cx the current Context
* @param scope the current scope
* @param source the source object
* @param excludeKeys array of property names to exclude
* @return a new object with all properties except the excluded ones
*/
public static Scriptable doObjectRest(
Context cx, Scriptable scope, Object source, Object[] excludeKeys) {
Scriptable sourceObj = toObject(cx, scope, source);
Scriptable result = cx.newObject(scope);
java.util.Set<String> excludeStrings = new java.util.HashSet<>();
java.util.Set<Integer> excludeIntegers = new java.util.HashSet<>();
java.util.Set<Symbol> excludeSymbols = new java.util.HashSet<>();
for (Object key : excludeKeys) {
if (key == null) {
continue;
}
if (key instanceof Symbol) {
excludeSymbols.add((Symbol) key);
} else if (key instanceof Integer) {
excludeIntegers.add((Integer) key);
} else {
excludeStrings.add(toString(key));
}
}
if (sourceObj instanceof ScriptableObject) {
ScriptableObject so = (ScriptableObject) sourceObj;
try (var map = so.startCompoundOp(false)) {
// Get all enumerable properties (regular + symbols) in one call
Object[] allIds = so.getIds(map, false, true);
for (Object id : allIds) {
// Check exclusion and copy property
if (!shouldExcludeProperty(
id, excludeStrings, excludeIntegers, excludeSymbols)) {
copyProperty(sourceObj, result, id, cx, scope);
}
}
}
} else {
Object[] ids = sourceObj.getIds();
for (Object id : ids) {
if (!shouldExcludeProperty(id, excludeStrings, excludeIntegers, excludeSymbols)) {
copyProperty(sourceObj, result, id, cx, scope);
}
}
}
return result;
}
/** Check if a property should be excluded based on its ID */
private static boolean shouldExcludeProperty(
Object id,
java.util.Set<String> excludeStrings,
java.util.Set<Integer> excludeIntegers,
java.util.Set<Symbol> excludeSymbols) {
if (id instanceof String) {
return excludeStrings.contains((String) id);
} else if (id instanceof Integer) {
return excludeIntegers.contains((Integer) id) || excludeStrings.contains(id.toString());
} else if (id instanceof Symbol) {
return excludeSymbols.contains((Symbol) id);
}
return false;
}
/** Copy a single property from source to result */
private static void copyProperty(
Scriptable source, Scriptable result, Object id, Context cx, Scriptable scope) {
Object value;
if (id instanceof Integer) {
int index = (Integer) id;
value = getObjectIndex(source, index, cx);
if (value != Scriptable.NOT_FOUND) {
result.put(index, result, value);
}
} else if (id instanceof String) {
String propName = (String) id;
value = getObjectProp(source, propName, cx, scope);
if (value != Scriptable.NOT_FOUND) {
result.put(propName, result, value);
}