1 /* 2 * Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.lang; 27 28 import java.io.UnsupportedEncodingException; 29 import java.lang.ref.SoftReference; 30 import java.nio.ByteBuffer; 31 import java.nio.CharBuffer; 32 import java.nio.charset.Charset; 33 import java.nio.charset.CharsetDecoder; 34 import java.nio.charset.CharsetEncoder; 35 import java.nio.charset.CharacterCodingException; 36 import java.nio.charset.CoderResult; 37 import java.nio.charset.CodingErrorAction; 38 import java.nio.charset.IllegalCharsetNameException; 39 import java.nio.charset.UnsupportedCharsetException; 40 import java.util.Arrays; 41 import jdk.internal.HotSpotIntrinsicCandidate; 42 import sun.nio.cs.HistoricallyNamedCharset; 43 import sun.nio.cs.ArrayDecoder; 44 import sun.nio.cs.ArrayEncoder; 45 46 import static java.lang.String.LATIN1; 47 import static java.lang.String.UTF16; 48 import static java.lang.String.COMPACT_STRINGS; 49 50 /** 51 * Utility class for string encoding and decoding. 52 */ 53 54 class StringCoding { 55 56 private StringCoding() { } 57 58 /** The cached coders for each thread */ 59 private static final ThreadLocal<SoftReference<StringDecoder>> decoder = 60 new ThreadLocal<>(); 61 private static final ThreadLocal<SoftReference<StringEncoder>> encoder = 62 new ThreadLocal<>(); 63 64 private static final Charset ISO_8859_1 = Charset.forName("iso-8859-1"); 65 private static final Charset US_ASCII = Charset.forName("us-ascii"); 66 private static final Charset UTF_8 = Charset.forName("utf-8"); 67 68 private static boolean warnUnsupportedCharset = true; 69 70 private static <T> T deref(ThreadLocal<SoftReference<T>> tl) { 71 SoftReference<T> sr = tl.get(); 72 if (sr == null) 73 return null; 74 return sr.get(); 75 } 76 77 private static <T> void set(ThreadLocal<SoftReference<T>> tl, T ob) { 78 tl.set(new SoftReference<>(ob)); 79 } 80 81 // Trim the given byte array to the given length 82 // 83 private static byte[] safeTrim(byte[] ba, int len, boolean isTrusted) { 84 if (len == ba.length && (isTrusted || System.getSecurityManager() == null)) 85 return ba; 86 else 87 return Arrays.copyOf(ba, len); 88 } 89 90 private static int scale(int len, float expansionFactor) { 91 // We need to perform double, not float, arithmetic; otherwise 92 // we lose low order bits when len is larger than 2**24. 93 return (int)(len * (double)expansionFactor); 94 } 95 96 private static Charset lookupCharset(String csn) { 97 if (Charset.isSupported(csn)) { 98 try { 99 return Charset.forName(csn); 100 } catch (UnsupportedCharsetException x) { 101 throw new Error(x); 102 } 103 } 104 return null; 105 } 106 107 private static void warnUnsupportedCharset(String csn) { 108 if (warnUnsupportedCharset) { 109 // Use err(String) rather than the Logging API or System.err 110 // since this method may be called during VM initialization 111 // before either is available. 112 err("WARNING: Default charset " + csn + 113 " not supported, using ISO-8859-1 instead\n"); 114 warnUnsupportedCharset = false; 115 } 116 } 117 118 static class Result { 119 byte[] value; 120 byte coder; 121 122 Result with() { 123 coder = COMPACT_STRINGS ? LATIN1 : UTF16; 124 value = new byte[0]; 125 return this; 126 } 127 128 Result with(char[] val, int off, int len) { 129 if (String.COMPACT_STRINGS) { 130 byte[] bs = StringUTF16.compress(val, off, len); 131 if (bs != null) { 132 value = bs; 133 coder = LATIN1; 134 return this; 135 } 136 } 137 coder = UTF16; 138 value = StringUTF16.toBytes(val, off, len); 139 return this; 140 } 141 142 Result with(byte[] val, byte coder) { 143 this.coder = coder; 144 value = val; 145 return this; 146 } 147 } 148 149 @HotSpotIntrinsicCandidate 150 public static boolean hasNegatives(byte[] ba, int off, int len) { 151 for (int i = off; i < off + len; i++) { 152 if (ba[i] < 0) { 153 return true; 154 } 155 } 156 return false; 157 } 158 159 // -- Decoding -- 160 static class StringDecoder { 161 private final String requestedCharsetName; 162 private final Charset cs; 163 private final boolean isASCIICompatible; 164 private final CharsetDecoder cd; 165 protected final Result result; 166 167 StringDecoder(Charset cs, String rcn) { 168 this.requestedCharsetName = rcn; 169 this.cs = cs; 170 this.cd = cs.newDecoder() 171 .onMalformedInput(CodingErrorAction.REPLACE) 172 .onUnmappableCharacter(CodingErrorAction.REPLACE); 173 this.result = new Result(); 174 this.isASCIICompatible = (cd instanceof ArrayDecoder) && 175 ((ArrayDecoder)cd).isASCIICompatible(); 176 } 177 178 String charsetName() { 179 if (cs instanceof HistoricallyNamedCharset) 180 return ((HistoricallyNamedCharset)cs).historicalName(); 181 return cs.name(); 182 } 183 184 final String requestedCharsetName() { 185 return requestedCharsetName; 186 } 187 188 Result decode(byte[] ba, int off, int len) { 189 if (len == 0) { 190 return result.with(); 191 } 192 // fastpath for ascii compatible 193 if (isASCIICompatible && !hasNegatives(ba, off, len)) { 194 if (COMPACT_STRINGS) { 195 return result.with(Arrays.copyOfRange(ba, off, off + len), 196 LATIN1); 197 } else { 198 return result.with(StringLatin1.inflate(ba, off, len), UTF16); 199 } 200 } 201 int en = scale(len, cd.maxCharsPerByte()); 202 char[] ca = new char[en]; 203 if (cd instanceof ArrayDecoder) { 204 int clen = ((ArrayDecoder)cd).decode(ba, off, len, ca); 205 return result.with(ca, 0, clen); 206 } 207 cd.reset(); 208 ByteBuffer bb = ByteBuffer.wrap(ba, off, len); 209 CharBuffer cb = CharBuffer.wrap(ca); 210 try { 211 CoderResult cr = cd.decode(bb, cb, true); 212 if (!cr.isUnderflow()) 213 cr.throwException(); 214 cr = cd.flush(cb); 215 if (!cr.isUnderflow()) 216 cr.throwException(); 217 } catch (CharacterCodingException x) { 218 // Substitution is always enabled, 219 // so this shouldn't happen 220 throw new Error(x); 221 } 222 return result.with(ca, 0, cb.position()); 223 } 224 } 225 226 private static class StringDecoder8859_1 extends StringDecoder { 227 StringDecoder8859_1(Charset cs, String rcn) { 228 super(cs, rcn); 229 } 230 Result decode(byte[] ba, int off, int len) { 231 if (COMPACT_STRINGS) { 232 return result.with(Arrays.copyOfRange(ba, off, off + len), LATIN1); 233 } else { 234 return result.with(StringLatin1.inflate(ba, off, len), UTF16); 235 } 236 } 237 } 238 239 static Result decode(String charsetName, byte[] ba, int off, int len) 240 throws UnsupportedEncodingException 241 { 242 StringDecoder sd = deref(decoder); 243 String csn = (charsetName == null) ? "ISO-8859-1" : charsetName; 244 if ((sd == null) || !(csn.equals(sd.requestedCharsetName()) 245 || csn.equals(sd.charsetName()))) { 246 sd = null; 247 try { 248 Charset cs = lookupCharset(csn); 249 if (cs != null) { 250 if (cs == UTF_8) { 251 sd = new StringDecoderUTF8(cs, csn); 252 } else if (cs == ISO_8859_1) { 253 sd = new StringDecoder8859_1(cs, csn); 254 } else { 255 sd = new StringDecoder(cs, csn); 256 } 257 } 258 } catch (IllegalCharsetNameException x) {} 259 if (sd == null) 260 throw new UnsupportedEncodingException(csn); 261 set(decoder, sd); 262 } 263 return sd.decode(ba, off, len); 264 } 265 266 static Result decode(Charset cs, byte[] ba, int off, int len) { 267 // (1)We never cache the "external" cs, the only benefit of creating 268 // an additional StringDe/Encoder object to wrap it is to share the 269 // de/encode() method. These SD/E objects are short-lived, the young-gen 270 // gc should be able to take care of them well. But the best approach 271 // is still not to generate them if not really necessary. 272 // (2)The defensive copy of the input byte/char[] has a big performance 273 // impact, as well as the outgoing result byte/char[]. Need to do the 274 // optimization check of (sm==null && classLoader0==null) for both. 275 // (3)getClass().getClassLoader0() is expensive 276 // (4)There might be a timing gap in isTrusted setting. getClassLoader0() 277 // is only checked (and then isTrusted gets set) when (SM==null). It is 278 // possible that the SM==null for now but then SM is NOT null later 279 // when safeTrim() is invoked...the "safe" way to do is to redundant 280 // check (... && (isTrusted || SM == null || getClassLoader0())) in trim 281 // but it then can be argued that the SM is null when the operation 282 // is started... 283 if (cs == UTF_8) { 284 return StringDecoderUTF8.decode(ba, off, len, new Result()); 285 } 286 CharsetDecoder cd = cs.newDecoder(); 287 // ascii fastpath 288 if (cs == ISO_8859_1 || ((cd instanceof ArrayDecoder) && 289 ((ArrayDecoder)cd).isASCIICompatible() && 290 !hasNegatives(ba, off, len))) { 291 if (COMPACT_STRINGS) { 292 return new Result().with(Arrays.copyOfRange(ba, off, off + len), 293 LATIN1); 294 } else { 295 return new Result().with(StringLatin1.inflate(ba, off, len), UTF16); 296 } 297 } 298 int en = scale(len, cd.maxCharsPerByte()); 299 if (len == 0) { 300 return new Result().with(); 301 } 302 if (System.getSecurityManager() != null && 303 cs.getClass().getClassLoader0() != null) { 304 ba = Arrays.copyOfRange(ba, off, off + len); 305 off = 0; 306 } 307 cd.onMalformedInput(CodingErrorAction.REPLACE) 308 .onUnmappableCharacter(CodingErrorAction.REPLACE) 309 .reset(); 310 311 char[] ca = new char[en]; 312 if (cd instanceof ArrayDecoder) { 313 int clen = ((ArrayDecoder)cd).decode(ba, off, len, ca); 314 return new Result().with(ca, 0, clen); 315 } 316 ByteBuffer bb = ByteBuffer.wrap(ba, off, len); 317 CharBuffer cb = CharBuffer.wrap(ca); 318 try { 319 CoderResult cr = cd.decode(bb, cb, true); 320 if (!cr.isUnderflow()) 321 cr.throwException(); 322 cr = cd.flush(cb); 323 if (!cr.isUnderflow()) 324 cr.throwException(); 325 } catch (CharacterCodingException x) { 326 // Substitution is always enabled, 327 // so this shouldn't happen 328 throw new Error(x); 329 } 330 return new Result().with(ca, 0, cb.position()); 331 } 332 333 static Result decode(byte[] ba, int off, int len) { 334 String csn = Charset.defaultCharset().name(); 335 try { 336 // use charset name decode() variant which provides caching. 337 return decode(csn, ba, off, len); 338 } catch (UnsupportedEncodingException x) { 339 warnUnsupportedCharset(csn); 340 } 341 try { 342 return decode("ISO-8859-1", ba, off, len); 343 } catch (UnsupportedEncodingException x) { 344 // If this code is hit during VM initialization, err(String) is 345 // the only way we will be able to get any kind of error message. 346 err("ISO-8859-1 charset not available: " + x.toString() + "\n"); 347 // If we can not find ISO-8859-1 (a required encoding) then things 348 // are seriously wrong with the installation. 349 System.exit(1); 350 return null; 351 } 352 } 353 354 // -- Encoding -- 355 private static class StringEncoder { 356 private Charset cs; 357 private CharsetEncoder ce; 358 private final boolean isASCIICompatible; 359 private final String requestedCharsetName; 360 private final boolean isTrusted; 361 362 private StringEncoder(Charset cs, String rcn) { 363 this.requestedCharsetName = rcn; 364 this.cs = cs; 365 this.ce = cs.newEncoder() 366 .onMalformedInput(CodingErrorAction.REPLACE) 367 .onUnmappableCharacter(CodingErrorAction.REPLACE); 368 this.isTrusted = (cs.getClass().getClassLoader0() == null); 369 this.isASCIICompatible = (ce instanceof ArrayEncoder) && 370 ((ArrayEncoder)ce).isASCIICompatible(); 371 } 372 373 String charsetName() { 374 if (cs instanceof HistoricallyNamedCharset) 375 return ((HistoricallyNamedCharset)cs).historicalName(); 376 return cs.name(); 377 } 378 379 final String requestedCharsetName() { 380 return requestedCharsetName; 381 } 382 383 byte[] encode(byte coder, byte[] val) { 384 // fastpath for ascii compatible 385 if (coder == LATIN1 && isASCIICompatible && 386 !hasNegatives(val, 0, val.length)) { 387 return Arrays.copyOf(val, val.length); 388 } 389 int len = val.length >> coder; // assume LATIN1=0/UTF16=1; 390 int en = scale(len, ce.maxBytesPerChar()); 391 byte[] ba = new byte[en]; 392 if (len == 0) { 393 return ba; 394 } 395 if (ce instanceof ArrayEncoder) { 396 if (!isTrusted) { 397 val = Arrays.copyOf(val, val.length); 398 } 399 int blen = (coder == LATIN1 ) ? ((ArrayEncoder)ce).encodeFromLatin1(val, 0, len, ba) 400 : ((ArrayEncoder)ce).encodeFromUTF16(val, 0, len, ba); 401 if (blen != -1) { 402 return safeTrim(ba, blen, isTrusted); 403 } 404 } 405 char[] ca = (coder == LATIN1 ) ? StringLatin1.toChars(val) 406 : StringUTF16.toChars(val); 407 ce.reset(); 408 ByteBuffer bb = ByteBuffer.wrap(ba); 409 CharBuffer cb = CharBuffer.wrap(ca, 0, len); 410 try { 411 CoderResult cr = ce.encode(cb, bb, true); 412 if (!cr.isUnderflow()) 413 cr.throwException(); 414 cr = ce.flush(bb); 415 if (!cr.isUnderflow()) 416 cr.throwException(); 417 } catch (CharacterCodingException x) { 418 // Substitution is always enabled, 419 // so this shouldn't happen 420 throw new Error(x); 421 } 422 return safeTrim(ba, bb.position(), isTrusted); 423 } 424 } 425 426 @HotSpotIntrinsicCandidate 427 private static int implEncodeISOArray(byte[] sa, int sp, 428 byte[] da, int dp, int len) { 429 int i = 0; 430 for (; i < len; i++) { 431 char c = StringUTF16.Trusted.getChar(sa, sp++); 432 if (c > '\u00FF') 433 break; 434 da[dp++] = (byte)c; 435 } 436 return i; 437 } 438 439 static byte[] encode8859_1(byte coder, byte[] val) { 440 if (coder == LATIN1) { 441 return Arrays.copyOf(val, val.length); 442 } 443 int len = val.length >> 1; 444 byte[] dst = new byte[len]; 445 int dp = 0; 446 int sp = 0; 447 int sl = len; 448 while (sp < sl) { 449 int ret = implEncodeISOArray(val, sp, dst, dp, len); 450 sp = sp + ret; 451 dp = dp + ret; 452 if (ret != len) { 453 char c = StringUTF16.Trusted.getChar(val, sp++); 454 if (Character.isHighSurrogate(c) && sp < sl && 455 Character.isLowSurrogate(StringUTF16.Trusted.getChar(val, sp))) { 456 sp++; 457 } 458 dst[dp++] = '?'; 459 len = sl - sp; 460 } 461 } 462 if (dp == dst.length) { 463 return dst; 464 } 465 return Arrays.copyOf(dst, dp); 466 } 467 468 static byte[] encodeASCII(byte coder, byte[] val) { 469 if (coder == LATIN1) { 470 byte[] dst = new byte[val.length]; 471 for (int i = 0; i < val.length; i++) { 472 if (val[i] < 0) { 473 dst[i] = '?'; 474 } else { 475 dst[i] = val[i]; 476 } 477 } 478 return dst; 479 } 480 int len = val.length >> 1; 481 byte[] dst = new byte[len]; 482 int dp = 0; 483 for (int i = 0; i < len; i++) { 484 char c = StringUTF16.Trusted.getChar(val, i); 485 if (c < 0x80) { 486 dst[dp++] = (byte)c; 487 continue; 488 } 489 if (Character.isHighSurrogate(c) && i + 1 < len && 490 Character.isLowSurrogate(StringUTF16.Trusted.getChar(val, i + 1))) { 491 i++; 492 } 493 dst[dp++] = '?'; 494 } 495 if (len == dp) { 496 return dst; 497 } 498 return Arrays.copyOf(dst, dp); 499 } 500 501 static byte[] encodeUTF8(byte coder, byte[] val) { 502 int dp = 0; 503 byte[] dst; 504 if (coder == LATIN1) { 505 dst = new byte[val.length << 1]; 506 for (int sp = 0; sp < val.length; sp++) { 507 byte c = val[sp]; 508 if (c < 0) { 509 dst[dp++] = (byte)(0xc0 | ((c & 0xff) >> 6)); 510 dst[dp++] = (byte)(0x80 | (c & 0x3f)); 511 } else { 512 dst[dp++] = c; 513 } 514 } 515 } else { 516 int sp = 0; 517 int sl = val.length >> 1; 518 dst = new byte[sl * 3]; 519 char c; 520 while (sp < sl && (c = StringUTF16.Trusted.getChar(val, sp)) < '\u0080') { 521 // ascii fast loop; 522 dst[dp++] = (byte)c; 523 sp++; 524 } 525 while (sp < sl) { 526 c = StringUTF16.Trusted.getChar(val, sp++); 527 if (c < 0x80) { 528 dst[dp++] = (byte)c; 529 } else if (c < 0x800) { 530 dst[dp++] = (byte)(0xc0 | (c >> 6)); 531 dst[dp++] = (byte)(0x80 | (c & 0x3f)); 532 } else if (Character.isSurrogate(c)) { 533 int uc = -1; 534 char c2; 535 if (Character.isHighSurrogate(c) && sp < sl && 536 Character.isLowSurrogate(c2 = StringUTF16.Trusted.getChar(val, sp))) { 537 uc = Character.toCodePoint(c, c2); 538 } 539 if (uc < 0) { 540 dst[dp++] = '?'; 541 } else { 542 dst[dp++] = (byte)(0xf0 | ((uc >> 18))); 543 dst[dp++] = (byte)(0x80 | ((uc >> 12) & 0x3f)); 544 dst[dp++] = (byte)(0x80 | ((uc >> 6) & 0x3f)); 545 dst[dp++] = (byte)(0x80 | (uc & 0x3f)); 546 sp++; // 2 chars 547 } 548 } else { 549 // 3 bytes, 16 bits 550 dst[dp++] = (byte)(0xe0 | ((c >> 12))); 551 dst[dp++] = (byte)(0x80 | ((c >> 6) & 0x3f)); 552 dst[dp++] = (byte)(0x80 | (c & 0x3f)); 553 } 554 } 555 } 556 if (dp == dst.length) { 557 return dst; 558 } 559 return Arrays.copyOf(dst, dp); 560 } 561 562 static byte[] encode(String charsetName, byte coder, byte[] val) 563 throws UnsupportedEncodingException 564 { 565 StringEncoder se = deref(encoder); 566 String csn = (charsetName == null) ? "ISO-8859-1" : charsetName; 567 if ((se == null) || !(csn.equals(se.requestedCharsetName()) 568 || csn.equals(se.charsetName()))) { 569 se = null; 570 try { 571 Charset cs = lookupCharset(csn); 572 if (cs != null) { 573 if (cs == UTF_8) { 574 return encodeUTF8(coder, val); 575 } else if (cs == ISO_8859_1) { 576 return encode8859_1(coder, val); 577 } else if (cs == US_ASCII) { 578 return encodeASCII(coder, val); 579 } 580 se = new StringEncoder(cs, csn); 581 } 582 } catch (IllegalCharsetNameException x) {} 583 if (se == null) { 584 throw new UnsupportedEncodingException (csn); 585 } 586 set(encoder, se); 587 } 588 return se.encode(coder, val); 589 } 590 591 static byte[] encode(Charset cs, byte coder, byte[] val) { 592 if (cs == UTF_8) { 593 return encodeUTF8(coder, val); 594 } else if (cs == ISO_8859_1) { 595 return encode8859_1(coder, val); 596 } else if (cs == US_ASCII) { 597 return encodeASCII(coder, val); 598 } 599 CharsetEncoder ce = cs.newEncoder(); 600 // fastpath for ascii compatible 601 if (coder == LATIN1 && (((ce instanceof ArrayEncoder) && 602 ((ArrayEncoder)ce).isASCIICompatible() && 603 !hasNegatives(val, 0, val.length)))) { 604 return Arrays.copyOf(val, val.length); 605 } 606 int len = val.length >> coder; // assume LATIN1=0/UTF16=1; 607 int en = scale(len, ce.maxBytesPerChar()); 608 byte[] ba = new byte[en]; 609 if (len == 0) { 610 return ba; 611 } 612 boolean isTrusted = System.getSecurityManager() == null || 613 cs.getClass().getClassLoader0() == null; 614 ce.onMalformedInput(CodingErrorAction.REPLACE) 615 .onUnmappableCharacter(CodingErrorAction.REPLACE) 616 .reset(); 617 if (ce instanceof ArrayEncoder) { 618 if (!isTrusted) { 619 val = Arrays.copyOf(val, val.length); 620 } 621 int blen = (coder == LATIN1 ) ? ((ArrayEncoder)ce).encodeFromLatin1(val, 0, len, ba) 622 : ((ArrayEncoder)ce).encodeFromUTF16(val, 0, len, ba); 623 if (blen != -1) { 624 return safeTrim(ba, blen, isTrusted); 625 } 626 } 627 char[] ca = (coder == LATIN1 ) ? StringLatin1.toChars(val) 628 : StringUTF16.toChars(val); 629 ByteBuffer bb = ByteBuffer.wrap(ba); 630 CharBuffer cb = CharBuffer.wrap(ca, 0, len); 631 try { 632 CoderResult cr = ce.encode(cb, bb, true); 633 if (!cr.isUnderflow()) 634 cr.throwException(); 635 cr = ce.flush(bb); 636 if (!cr.isUnderflow()) 637 cr.throwException(); 638 } catch (CharacterCodingException x) { 639 throw new Error(x); 640 } 641 return safeTrim(ba, bb.position(), isTrusted); 642 } 643 644 static byte[] encode(byte coder, byte[] val) { 645 String csn = Charset.defaultCharset().name(); 646 try { 647 // use charset name encode() variant which provides caching. 648 return encode(csn, coder, val); 649 } catch (UnsupportedEncodingException x) { 650 warnUnsupportedCharset(csn); 651 } 652 try { 653 return encode("ISO-8859-1", coder, val); 654 } catch (UnsupportedEncodingException x) { 655 // If this code is hit during VM initialization, err(String) is 656 // the only way we will be able to get any kind of error message. 657 err("ISO-8859-1 charset not available: " + x.toString() + "\n"); 658 // If we can not find ISO-8859-1 (a required encoding) then things 659 // are seriously wrong with the installation. 660 System.exit(1); 661 return null; 662 } 663 } 664 665 /** 666 * Print a message directly to stderr, bypassing all character conversion 667 * methods. 668 * @param msg message to print 669 */ 670 private static native void err(String msg); 671 }