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