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  */
  25 
  26 package jdk.nashorn.internal.objects;
  27 
  28 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  29 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt;
  30 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
  31 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndexNoThrow;
  32 import static jdk.nashorn.internal.runtime.linker.Lookup.MH;
  33 
  34 import java.lang.invoke.MethodHandle;
  35 import java.lang.invoke.MethodHandles;
  36 import java.text.Collator;
  37 import java.util.ArrayList;
  38 import java.util.Arrays;
  39 import java.util.List;
  40 import java.util.regex.Pattern;
  41 import jdk.nashorn.internal.objects.annotations.Attribute;
  42 import jdk.nashorn.internal.objects.annotations.Constructor;
  43 import jdk.nashorn.internal.objects.annotations.Function;
  44 import jdk.nashorn.internal.objects.annotations.Getter;
  45 import jdk.nashorn.internal.objects.annotations.ScriptClass;
  46 import jdk.nashorn.internal.objects.annotations.SpecializedConstructor;
  47 import jdk.nashorn.internal.objects.annotations.SpecializedFunction;
  48 import jdk.nashorn.internal.objects.annotations.Where;
  49 import jdk.nashorn.internal.parser.Lexer;
  50 import jdk.nashorn.internal.runtime.ConsString;
  51 import jdk.nashorn.internal.runtime.JSType;
  52 import jdk.nashorn.internal.runtime.ScriptFunction;
  53 import jdk.nashorn.internal.runtime.ScriptObject;
  54 import jdk.nashorn.internal.runtime.ScriptRuntime;
  55 import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
  56 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
  57 import jdk.nashorn.internal.runtime.linker.NashornGuards;
  58 import jdk.nashorn.internal.runtime.linker.PrimitiveLookup;
  59 import org.dynalang.dynalink.linker.GuardedInvocation;
  60 
  61 
  62 /**
  63  * ECMA 15.5 String Objects.
  64  */
  65 @ScriptClass("String")
  66 public final class NativeString extends ScriptObject {
  67 
  68     private final CharSequence value;
  69 
  70     private static final MethodHandle WRAPFILTER = findWrapFilter();
  71 
  72     NativeString(final CharSequence value) {
  73         this(value, Global.instance().getStringPrototype());
  74     }
  75 
  76     private NativeString(final CharSequence value, final ScriptObject proto) {
  77         assert value instanceof String || value instanceof ConsString;
  78         this.value = value;
  79         this.setProto(proto);
  80     }
  81 
  82     @Override
  83     public String safeToString() {
  84         return "[String " + toString() + "]";
  85     }
  86 
  87     @Override
  88     public String toString() {
  89         return getStringValue();
  90     }
  91 
  92     @Override
  93     public boolean equals(final Object other) {
  94         if (other instanceof NativeString) {
  95             return getStringValue().equals(((NativeString) other).getStringValue());
  96         }
  97 
  98         return false;
  99     }
 100 
 101     @Override
 102     public int hashCode() {
 103         return getStringValue().hashCode();
 104     }
 105 
 106     private String getStringValue() {
 107         return value instanceof String ? (String) value : value.toString();
 108     }
 109 
 110     private CharSequence getValue() {
 111         return value;
 112     }
 113 
 114     @Override
 115     public String getClassName() {
 116         return "String";
 117     }
 118 
 119     @Override
 120     public Object getLength() {
 121         return value.length();
 122     }
 123 
 124     // String characters can be accessed with array-like indexing..
 125     @Override
 126     public Object get(final Object key) {
 127         final int index = getArrayIndexNoThrow(key);
 128         if (index >= 0 && index < value.length()) {
 129             return String.valueOf(value.charAt(index));
 130         }
 131         return super.get(key);
 132     }
 133 
 134     @Override
 135     public Object get(final double key) {
 136         if (isRepresentableAsInt(key)) {
 137             return get((int)key);
 138         }
 139         return super.get(key);
 140     }
 141 
 142     @Override
 143     public Object get(final long key) {
 144         if (key >= 0 && key < value.length()) {
 145             return String.valueOf(value.charAt((int)key));
 146         }
 147         return super.get(key);
 148     }
 149 
 150     @Override
 151     public Object get(final int key) {
 152         if (key >= 0 && key < value.length()) {
 153             return String.valueOf(value.charAt(key));
 154         }
 155         return super.get(key);
 156     }
 157 
 158     @Override
 159     public int getInt(final Object key) {
 160         return JSType.toInt32(get(key));
 161     }
 162 
 163     @Override
 164     public int getInt(final double key) {
 165         return JSType.toInt32(get(key));
 166     }
 167 
 168     @Override
 169     public int getInt(final long key) {
 170         return JSType.toInt32(get(key));
 171     }
 172 
 173     @Override
 174     public int getInt(final int key) {
 175         return JSType.toInt32(get(key));
 176     }
 177 
 178     @Override
 179     public long getLong(final Object key) {
 180         return JSType.toUint32(get(key));
 181     }
 182 
 183     @Override
 184     public long getLong(final double key) {
 185         return JSType.toUint32(get(key));
 186     }
 187 
 188     @Override
 189     public long getLong(final long key) {
 190         return JSType.toUint32(get(key));
 191     }
 192 
 193     @Override
 194     public long getLong(final int key) {
 195         return JSType.toUint32(get(key));
 196     }
 197 
 198     @Override
 199     public double getDouble(final Object key) {
 200         return JSType.toNumber(get(key));
 201     }
 202 
 203     @Override
 204     public double getDouble(final double key) {
 205         return JSType.toNumber(get(key));
 206     }
 207 
 208     @Override
 209     public double getDouble(final long key) {
 210         return JSType.toNumber(get(key));
 211     }
 212 
 213     @Override
 214     public double getDouble(final int key) {
 215         return JSType.toNumber(get(key));
 216     }
 217 
 218     @Override
 219     public boolean has(final Object key) {
 220         final int index = getArrayIndexNoThrow(key);
 221         return isValid(index) || super.has(key);
 222     }
 223 
 224     @Override
 225     public boolean has(final int key) {
 226         return isValid(key) || super.has(key);
 227     }
 228 
 229     @Override
 230     public boolean has(final long key) {
 231         final int index = getArrayIndexNoThrow(key);
 232         return isValid(index) || super.has(key);
 233     }
 234 
 235     @Override
 236     public boolean has(final double key) {
 237         final int index = getArrayIndexNoThrow(key);
 238         return isValid(index) || super.has(key);
 239     }
 240 
 241     @Override
 242     public boolean hasOwnProperty(final Object key) {
 243         final int index = getArrayIndexNoThrow(key);
 244         return isValid(index) || super.hasOwnProperty(key);
 245     }
 246 
 247     @Override
 248     public boolean hasOwnProperty(final int key) {
 249         return isValid(key) || super.hasOwnProperty(key);
 250     }
 251 
 252     @Override
 253     public boolean hasOwnProperty(final long key) {
 254         final int index = getArrayIndexNoThrow(key);
 255         return isValid(index) || super.hasOwnProperty(key);
 256     }
 257 
 258     @Override
 259     public boolean hasOwnProperty(final double key) {
 260         final int index = getArrayIndexNoThrow(key);
 261         return isValid(index) || super.hasOwnProperty(key);
 262     }
 263 
 264     @Override
 265     public boolean delete(final int key, final boolean strict) {
 266         return checkDeleteIndex(key, strict)? false : super.delete(key, strict);
 267     }
 268 
 269     @Override
 270     public boolean delete(final long key, final boolean strict) {
 271         final int index = getArrayIndexNoThrow(key);
 272         return checkDeleteIndex(index, strict)? false : super.delete(key, strict);
 273     }
 274 
 275     @Override
 276     public boolean delete(final double key, final boolean strict) {
 277         final int index = getArrayIndexNoThrow(key);
 278         return checkDeleteIndex(index, strict)? false : super.delete(key, strict);
 279     }
 280 
 281     @Override
 282     public boolean delete(final Object key, final boolean strict) {
 283         final int index = getArrayIndexNoThrow(key);
 284         return checkDeleteIndex(index, strict)? false : super.delete(key, strict);
 285     }
 286 
 287     private boolean checkDeleteIndex(final int index, final boolean strict) {
 288         if (isValid(index)) {
 289             if (strict) {
 290                 typeError(Global.instance(), "cant.delete.property", Integer.toString(index), ScriptRuntime.safeToString(this));
 291             }
 292             return true;
 293         }
 294 
 295         return false;
 296     }
 297 
 298     @Override
 299     public Object getOwnPropertyDescriptor(final String key) {
 300         final int index = ArrayIndex.getArrayIndexNoThrow(key);
 301         if (index >= 0 && index < value.length()) {
 302             final Global global = Global.instance();
 303             return global.newDataDescriptor(String.valueOf(value.charAt(index)), false, true, false);
 304         }
 305 
 306         return super.getOwnPropertyDescriptor(key);
 307     }
 308 
 309     /**
 310      * return a List of own keys associated with the object.
 311      * @param all True if to include non-enumerable keys.
 312      * @return Array of keys.
 313      */
 314     @Override
 315     public String[] getOwnKeys(final boolean all) {
 316         final List<Object> keys = new ArrayList<>();
 317 
 318         // add string index keys
 319         for (int i = 0; i < value.length(); i++) {
 320             keys.add(JSType.toString(i));
 321         }
 322 
 323         // add super class properties
 324         keys.addAll(Arrays.asList(super.getOwnKeys(all)));
 325         return keys.toArray(new String[keys.size()]);
 326     }
 327 
 328     /**
 329      * ECMA 15.5.3 String.length
 330      * @param self self reference
 331      * @return     value of length property for string
 332      */
 333     @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE)
 334     public static Object length(final Object self) {
 335         return getCharSequence(self).length();
 336     }
 337 
 338     /**
 339      * ECMA 15.5.3.2 String.fromCharCode ( [ char0 [ , char1 [ , ... ] ] ] )
 340      * @param self  self reference
 341      * @param args  array of arguments to be interpreted as char
 342      * @return string with arguments translated to charcodes
 343      */
 344     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1, where = Where.CONSTRUCTOR)
 345     public static Object fromCharCode(final Object self, final Object... args) {
 346         final char[] buf = new char[args.length];
 347         int index = 0;
 348         for (final Object arg : args) {
 349             buf[index++] = (char)JSType.toUint16(arg);
 350         }
 351         return new String(buf);
 352     }
 353 
 354     /**
 355      * ECMA 15.5.3.2 - specialization for one char
 356      * @param self  self reference
 357      * @param value one argument to be interpreted as char
 358      * @return string with one charcode
 359      */
 360     @SpecializedFunction
 361     public static Object fromCharCode(final Object self, final Object value) {
 362         try {
 363             return "" + (char)JSType.toUint16(((Number)value).doubleValue());
 364         } catch (final ClassCastException e) {
 365             return fromCharCode(self, new Object[] { value });
 366         }
 367     }
 368 
 369     /**
 370      * ECMA 15.5.3.2 - specialization for one char of int type
 371      * @param self  self reference
 372      * @param value one argument to be interpreted as char
 373      * @return string with one charcode
 374      */
 375     @SpecializedFunction
 376     public static Object fromCharCode(final Object self, final int value) {
 377         return "" + (char)(value & 0xffff);
 378     }
 379 
 380     /**
 381      * ECMA 15.5.3.2 - specialization for one char of long type
 382      * @param self  self reference
 383      * @param value one argument to be interpreted as char
 384      * @return string with one charcode
 385      */
 386     @SpecializedFunction
 387     public static Object fromCharCode(final Object self, final long value) {
 388         return "" + (char)((int)value & 0xffff);
 389     }
 390 
 391     /**
 392      * ECMA 15.5.3.2 - specialization for one char of double type
 393      * @param self  self reference
 394      * @param value one argument to be interpreted as char
 395      * @return string with one charcode
 396      */
 397     @SpecializedFunction
 398     public static Object fromCharCode(final Object self, final double value) {
 399         return "" + (char)JSType.toUint16(value);
 400     }
 401 
 402     /**
 403      * ECMA 15.5.4.2 String.prototype.toString ( )
 404      * @param self self reference
 405      * @return self as string
 406      */
 407     @Function(attributes = Attribute.NOT_ENUMERABLE)
 408     public static Object toString(final Object self) {
 409         return getString(self);
 410     }
 411 
 412     /**
 413      * ECMA 15.5.4.3 String.prototype.valueOf ( )
 414      * @param self self reference
 415      * @return self as string
 416      */
 417     @Function(attributes = Attribute.NOT_ENUMERABLE)
 418     public static Object valueOf(final Object self) {
 419         return getString(self);
 420     }
 421 
 422     /**
 423      * ECMA 15.5.4.4 String.prototype.charAt (pos)
 424      * @param self self reference
 425      * @param pos  position in string
 426      * @return string representing the char at the given position
 427      */
 428     @Function(attributes = Attribute.NOT_ENUMERABLE)
 429     public static Object charAt(final Object self, final Object pos) {
 430         try {
 431             return String.valueOf(((String)self).charAt(((Number)pos).intValue()));
 432         } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) {
 433             Global.checkObjectCoercible(self);
 434             final String str = JSType.toString(self);
 435             final int    at  = JSType.toInteger(pos);
 436             if (at < 0 || at >= str.length()) {
 437                 return "";
 438             }
 439             return String.valueOf(str.charAt(at));
 440         }
 441     }
 442 
 443     /**
 444      * ECMA 15.5.4.5 String.prototype.charCodeAt (pos)
 445      * @param self self reference
 446      * @param pos  position in string
 447      * @return number representing charcode at position
 448      */
 449     @Function(attributes = Attribute.NOT_ENUMERABLE)
 450     public static Object charCodeAt(final Object self, final Object pos) {
 451         try {
 452             return (int)((String)self).charAt(((Number)pos).intValue());
 453         } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) {
 454             Global.checkObjectCoercible(self);
 455             final String str = JSType.toString(self);
 456             final int at     = JSType.toInteger(pos);
 457             if (at < 0 || at >= str.length()) {
 458                 return Double.NaN;
 459             }
 460 
 461             return JSType.toObject(str.charAt(at));
 462         }
 463     }
 464 
 465     /**
 466      * ECMA 15.5.4.6 String.prototype.concat ( [ string1 [ , string2 [ , ... ] ] ] )
 467      * @param self self reference
 468      * @param args list of string to concatenate
 469      * @return concatenated string
 470      */
 471     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
 472     public static Object concat(final Object self, final Object... args) {
 473         Global.checkObjectCoercible(self);
 474         final StringBuilder sb = new StringBuilder(JSType.toString(self));
 475         if (args != null) {
 476             for (final Object obj : args) {
 477                 sb.append(JSType.toString(obj));
 478             }
 479         }
 480         return sb.toString();
 481     }
 482 
 483     /**
 484      * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position)
 485      * @param self   self reference
 486      * @param search string to search for
 487      * @param pos    position to start search
 488      * @return position of first match or -1
 489      */
 490     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
 491     public static Object indexOf(final Object self, final Object search, final Object pos) {
 492         try {
 493             return ((String)self).indexOf((String)search, ((Number)pos).intValue()); //assuming that the conversions really mean "toInteger" and not "toInt32" this is ok.
 494         } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) {
 495             Global.checkObjectCoercible(self);
 496             return JSType.toString(self).indexOf(JSType.toString(search), JSType.toInteger(pos));
 497         }
 498     }
 499 
 500     /**
 501      * ECMA 15.5.4.8 String.prototype.lastIndexOf (searchString, position)
 502      * @param self   self reference
 503      * @param search string to search for
 504      * @param pos    position to start search
 505      * @return last position of match or -1
 506      */
 507     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
 508     public static Object lastIndexOf(final Object self, final Object search, final Object pos) {
 509         Global.checkObjectCoercible(self);
 510 
 511         final String str       = JSType.toString(self);
 512         final String searchStr = JSType.toString(search);
 513 
 514         int from;
 515 
 516         if (pos == UNDEFINED) {
 517             from = str.length();
 518         } else {
 519             final double numPos = JSType.toNumber(pos);
 520             from = !Double.isNaN(numPos) ? (int)numPos : (int)Double.POSITIVE_INFINITY;
 521         }
 522 
 523         return str.lastIndexOf(searchStr, from);
 524     }
 525 
 526     /**
 527      * ECMA 15.5.4.9 String.prototype.localeCompare (that)
 528      * @param self self reference
 529      * @param that comparison object
 530      * @return result of locale sensitive comparison operation between {@code self} and {@code that}
 531      */
 532     @Function(attributes = Attribute.NOT_ENUMERABLE)
 533     public static Object localeCompare(final Object self, final Object that) {
 534         Global.checkObjectCoercible(self);
 535 
 536         final String   str      = JSType.toString(self);
 537         final Collator collator = Collator.getInstance(Global.getThisContext().getLocale());
 538 
 539         collator.setStrength(Collator.IDENTICAL);
 540         collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
 541 
 542         return (double)collator.compare(str, JSType.toString(that));
 543     }
 544 
 545     /**
 546      * ECMA 15.5.4.10 String.prototype.match (regexp)
 547      * @param self   self reference
 548      * @param regexp regexp expression
 549      * @return array of regexp matches
 550      */
 551     @Function(attributes = Attribute.NOT_ENUMERABLE)
 552     public static Object match(final Object self, final Object regexp) {
 553         Global.checkObjectCoercible(self);
 554 
 555         final String str = JSType.toString(self);
 556 
 557         NativeRegExp nativeRegExp;
 558         if (regexp == UNDEFINED) {
 559             nativeRegExp = new NativeRegExp("");
 560         } else {
 561             nativeRegExp = Global.toRegExp(regexp);
 562         }
 563 
 564         if (!nativeRegExp.getGlobal()) {
 565             return nativeRegExp.exec(str);
 566         }
 567 
 568         nativeRegExp.setLastIndex(0);
 569 
 570         int previousLastIndex = 0;
 571         final List<Object> matches = new ArrayList<>();
 572 
 573         Object result;
 574         while ((result = nativeRegExp.exec(str)) != null) {
 575             final int thisIndex = nativeRegExp.getLastIndex();
 576             if (thisIndex == previousLastIndex) {
 577                 nativeRegExp.setLastIndex(thisIndex + 1);
 578                 previousLastIndex = thisIndex + 1;
 579             } else {
 580                 previousLastIndex = thisIndex;
 581             }
 582             matches.add(((ScriptObject)result).get(0));
 583         }
 584 
 585         if (matches.isEmpty()) {
 586             return null;
 587         }
 588 
 589         return new NativeArray(matches.toArray());
 590     }
 591 
 592     /**
 593      * ECMA 15.5.4.11 String.prototype.replace (searchValue, replaceValue)
 594      * @param self        self reference
 595      * @param string      item to replace
 596      * @param replacement item to replace it with
 597      * @return string after replacement
 598      */
 599     @Function(attributes = Attribute.NOT_ENUMERABLE)
 600     public static Object replace(final Object self, final Object string, final Object replacement) {
 601         Global.checkObjectCoercible(self);
 602 
 603         final String str = JSType.toString(self);
 604 
 605         final NativeRegExp nativeRegExp;
 606         if (string instanceof NativeRegExp) {
 607             nativeRegExp = (NativeRegExp) string;
 608         } else {
 609             nativeRegExp = new NativeRegExp(Pattern.compile(JSType.toString(string), Pattern.LITERAL));
 610         }
 611 
 612         if (replacement instanceof ScriptFunction) {
 613             return nativeRegExp.replace(str, "", (ScriptFunction)replacement);
 614         }
 615 
 616         return nativeRegExp.replace(str, JSType.toString(replacement), null);
 617     }
 618 
 619     /**
 620      * ECMA 15.5.4.12 String.prototype.search (regexp)
 621      *
 622      * @param self    self reference
 623      * @param string  string to search for
 624      * @return offset where match occurred
 625      */
 626     @Function(attributes = Attribute.NOT_ENUMERABLE)
 627     public static Object search(final Object self, final Object string) {
 628         Global.checkObjectCoercible(self);
 629 
 630         final String       str          = JSType.toString(self);
 631         final NativeRegExp nativeRegExp = Global.toRegExp(string == UNDEFINED ? "" : string);
 632 
 633         return nativeRegExp.search(str);
 634     }
 635 
 636     /**
 637      * ECMA 15.5.4.13 String.prototype.slice (start, end)
 638      *
 639      * @param self  self reference
 640      * @param start start position for slice
 641      * @param end   end position for slice
 642      * @return sliced out substring
 643      */
 644     @Function(attributes = Attribute.NOT_ENUMERABLE)
 645     public static Object slice(final Object self, final Object start, final Object end) {
 646         Global.checkObjectCoercible(self);
 647 
 648         final String str      = JSType.toString(self);
 649         final int    len      = str.length();
 650         final int    intStart = JSType.toInteger(start);
 651         final int    intEnd   = (end == UNDEFINED) ? len : JSType.toInteger(end);
 652 
 653         int from;
 654 
 655         if (intStart < 0) {
 656             from = Math.max(len + intStart, 0);
 657         } else {
 658             from = Math.min(intStart, len);
 659         }
 660 
 661         int to;
 662 
 663         if (intEnd < 0) {
 664             to = Math.max(len + intEnd,0);
 665         } else {
 666             to = Math.min(intEnd, len);
 667         }
 668 
 669         return str.substring(Math.min(from,  to), to);
 670     }
 671 
 672     /**
 673      * ECMA 15.5.4.14 String.prototype.split (separator, limit)
 674      *
 675      * @param self      self reference
 676      * @param separator separator for split
 677      * @param limit     limit for splits
 678      * @return array object in which splits have been placed
 679      */
 680     @Function(attributes = Attribute.NOT_ENUMERABLE)
 681     public static Object split(final Object self, final Object separator, final Object limit) {
 682         Global.checkObjectCoercible(self);
 683 
 684         final String str = JSType.toString(self);
 685 
 686         if (separator == UNDEFINED) {
 687             return new NativeArray(new Object[]{str});
 688         }
 689 
 690         final long lim = (limit == UNDEFINED) ? JSType.MAX_UINT : JSType.toUint32(limit);
 691 
 692         if (separator instanceof NativeRegExp) {
 693             return ((NativeRegExp) separator).split(str, lim);
 694         }
 695 
 696         // when separator is a string, it has to be treated as a
 697         // literal search string to be used for splitting.
 698         return new NativeRegExp(Pattern.compile(JSType.toString(separator), Pattern.LITERAL)).split(str, lim);
 699     }
 700 
 701     /**
 702      * ECMA B.2.3 String.prototype.substr (start, length)
 703      *
 704      * @param self   self reference
 705      * @param start  start position
 706      * @param length length of section
 707      * @return substring given start and length of section
 708      */
 709     @Function(attributes = Attribute.NOT_ENUMERABLE)
 710     public static Object substr(final Object self, final Object start, final Object length) {
 711         final String str       = JSType.toString(self);
 712         final int    strLength = str.length();
 713 
 714         int intStart = JSType.toInteger(start);
 715         if (intStart < 0) {
 716             intStart = Math.max(intStart + strLength, 0);
 717         }
 718 
 719         final int intLen = Math.min(Math.max((length == UNDEFINED) ? Integer.MAX_VALUE : JSType.toInteger(length), 0), strLength - intStart);
 720 
 721         return intLen <= 0 ? "" : str.substring(intStart, intStart + intLen);
 722     }
 723 
 724     /**
 725      * ECMA 15.5.4.15 String.prototype.substring (start, end)
 726      *
 727      * @param self  self reference
 728      * @param start start position of substring
 729      * @param end   end position of substring
 730      * @return substring given start and end indexes
 731      */
 732     @Function(attributes = Attribute.NOT_ENUMERABLE)
 733     public static Object substring(final Object self, final Object start, final Object end) {
 734         Global.checkObjectCoercible(self);
 735 
 736         final String str        = JSType.toString(self);
 737         final int    len        = str.length();
 738         final int    intStart   = JSType.toInteger(start);
 739         final int    intEnd     = (end == UNDEFINED) ? len : JSType.toInteger(end);
 740         final int    finalStart = Math.min((intStart < 0) ? 0 : intStart, len);
 741         final int    finalEnd   = Math.min((intEnd < 0) ? 0 : intEnd, len);
 742 
 743         int from, to;
 744 
 745         if (finalStart < finalEnd) {
 746             from = finalStart;
 747             to = finalEnd;
 748         } else {
 749             from = finalEnd;
 750             to = finalStart;
 751         }
 752         return str.substring(from, to);
 753     }
 754 
 755     /**
 756      * ECMA 15.5.4.16 String.prototype.toLowerCase ( )
 757      * @param self self reference
 758      * @return string to lower case
 759      */
 760     @Function(attributes = Attribute.NOT_ENUMERABLE)
 761     public static Object toLowerCase(final Object self) {
 762         Global.checkObjectCoercible(self);
 763         return JSType.toString(self).toLowerCase();
 764     }
 765 
 766     /**
 767      * ECMA 15.5.4.17 String.prototype.toLocaleLowerCase ( )
 768      * @param self self reference
 769      * @return string to locale sensitive lower case
 770      */
 771     @Function(attributes = Attribute.NOT_ENUMERABLE)
 772     public static Object toLocaleLowerCase(final Object self) {
 773         Global.checkObjectCoercible(self);
 774         return JSType.toString(self).toLowerCase(Global.getThisContext().getLocale());
 775     }
 776 
 777     /**
 778      * ECMA 15.5.4.18 String.prototype.toUpperCase ( )
 779      * @param self self reference
 780      * @return string to upper case
 781      */
 782     @Function(attributes = Attribute.NOT_ENUMERABLE)
 783     public static Object toUpperCase(final Object self) {
 784         Global.checkObjectCoercible(self);
 785         return JSType.toString(self).toUpperCase();
 786     }
 787 
 788     /**
 789      * ECMA 15.5.4.19 String.prototype.toLocaleUpperCase ( )
 790      * @param self self reference
 791      * @return string to locale sensitive upper case
 792      */
 793     @Function(attributes = Attribute.NOT_ENUMERABLE)
 794     public static Object toLocaleUpperCase(final Object self) {
 795         Global.checkObjectCoercible(self);
 796         return JSType.toString(self).toUpperCase(Global.getThisContext().getLocale());
 797     }
 798 
 799     /**
 800      * ECMA 15.5.4.20 String.prototype.trim ( )
 801      * @param self self reference
 802      * @return string trimmed from whitespace
 803      */
 804     @Function(attributes = Attribute.NOT_ENUMERABLE)
 805     public static Object trim(final Object self) {
 806         Global.checkObjectCoercible(self);
 807 
 808         final String str = JSType.toString(self);
 809 
 810         int start = 0;
 811         int end   = str.length() - 1;
 812 
 813         while (start <= end && Lexer.isJSWhitespace(str.charAt(start))) {
 814             start++;
 815         }
 816         while (end > start && Lexer.isJSWhitespace(str.charAt(end))) {
 817             end--;
 818         }
 819 
 820         return str.substring(start, end + 1);
 821     }
 822 
 823     private static Object newObj(final Object self, final CharSequence str) {
 824         if (self instanceof ScriptObject) {
 825             return new NativeString(str, ((ScriptObject)self).getProto());
 826         }
 827         return new NativeString(str, Global.instance().getStringPrototype());
 828     }
 829 
 830     /**
 831      * ECMA 15.5.2.1 new String ( [ value ] )
 832      *
 833      * Constructor
 834      *
 835      * @param newObj is this constructor invoked with the new operator
 836      * @param self   self reference
 837      * @param args   arguments (a value)
 838      *
 839      * @return new NativeString, empty string if no args, extraneous args ignored
 840      */
 841     @Constructor(arity = 1)
 842     public static Object constructor(final boolean newObj, final Object self, final Object... args) {
 843         final CharSequence str = (args.length > 0) ? JSType.toCharSequence(args[0]) : "";
 844         return newObj ? newObj(self, str) : str.toString();
 845     }
 846 
 847     /**
 848      * ECMA 15.5.2.1 new String ( [ value ] ) - special version with no args
 849      *
 850      * Constructor
 851      *
 852      * @param newObj is this constructor invoked with the new operator
 853      * @param self   self reference
 854      *
 855      * @return new NativeString ("")
 856      */
 857     @SpecializedConstructor
 858     public static Object constructor(final boolean newObj, final Object self) {
 859         return newObj ? newObj(self, "") : "";
 860     }
 861 
 862     //TODO - why is there no String with one String arg constructor?
 863 
 864     /**
 865      * ECMA 15.5.2.1 new String ( [ value ] ) - special version with exactly one {@code int} arg
 866      *
 867      * Constructor
 868      *
 869      * @param newObj is this constructor invoked with the new operator
 870      * @param self   self reference
 871      * @param arg    the arg
 872      *
 873      * @return new NativeString containing the string representation of the arg
 874      */
 875     @SpecializedConstructor
 876     public static Object constructor(final boolean newObj, final Object self, final int arg) {
 877         final CharSequence str = JSType.toCharSequence(arg);
 878         return newObj ? newObj(self, str) : str;
 879     }
 880 
 881     /**
 882      * Lookup the appropriate method for an invoke dynamic call.
 883      *
 884      * @param desc the call site descriptor
 885      * @param receiver receiver of call
 886      * @return Link to be invoked at call site.
 887      */
 888     public static GuardedInvocation lookupPrimitive(final NashornCallSiteDescriptor desc, final Object receiver) {
 889         final MethodHandle guard = NashornGuards.getInstanceOf2Guard(String.class, ConsString.class);
 890         return PrimitiveLookup.lookupPrimitive(desc, guard, new NativeString((CharSequence)receiver), WRAPFILTER);
 891     }
 892 
 893     @SuppressWarnings("unused")
 894     private static NativeString wrapFilter(final Object receiver) {
 895         return new NativeString((CharSequence)receiver);
 896     }
 897 
 898     private static CharSequence getCharSequence(final Object self) {
 899         if (self instanceof String || self instanceof ConsString) {
 900             return (CharSequence)self;
 901         } else if (self instanceof NativeString) {
 902             return ((NativeString)self).getValue();
 903         } else if (self != null && self == Global.instance().getStringPrototype()) {
 904             return "";
 905         } else {
 906             typeError(Global.instance(), "not.a.string", ScriptRuntime.safeToString(self));
 907             return null;
 908         }
 909     }
 910 
 911     private static String getString(final Object self) {
 912         if (self instanceof String) {
 913             return (String)self;
 914         } else if (self instanceof ConsString) {
 915             return self.toString();
 916         } else if (self instanceof NativeString) {
 917             return ((NativeString)self).getStringValue();
 918         } else if (self != null && self == Global.instance().getStringPrototype()) {
 919             return "";
 920         } else {
 921             typeError(Global.instance(), "not.a.string", ScriptRuntime.safeToString(self));
 922             return null;
 923         }
 924     }
 925 
 926     private boolean isValid(final int key) {
 927         return key >= 0 && key < value.length();
 928     }
 929 
 930     private static MethodHandle findWrapFilter() {
 931         try {
 932             return MethodHandles.lookup().findStatic(NativeString.class, "wrapFilter", MH.type(NativeString.class, Object.class));
 933         } catch (final NoSuchMethodException | IllegalAccessException e) {
 934             throw new AssertionError(e);
 935         }
 936     }
 937 }