1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 3 * 4 * This code is free software; you can redistribute it and/or modify it 5 * under the terms of the GNU General Public License version 2 only, as 6 * published by the Free Software Foundation. Oracle designates this 7 * particular file as subject to the "Classpath" exception as provided 8 * by Oracle in the LICENSE file that accompanied this code. 9 * 10 * This code is distributed in the hope that it will be useful, but WITHOUT 11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 * version 2 for more details (a copy is included in the LICENSE file that 14 * accompanied this code). 15 * 16 * You should have received a copy of the GNU General Public License version 17 * 2 along with this work; if not, write to the Free Software Foundation, 18 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 19 * 20 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 21 * or visit www.oracle.com if you need additional information or have any 22 * questions. 23 */ 24 25 /* 26 * This file is available under and governed by the GNU General Public 27 * License version 2 only, as published by the Free Software Foundation. 28 * However, the following notice accompanied the original version of this 29 * file: 30 * 31 * ASM: a very small and fast Java bytecode manipulation framework 32 * Copyright (c) 2000-2011 INRIA, France Telecom 33 * All rights reserved. 34 * 35 * Redistribution and use in source and binary forms, with or without 36 * modification, are permitted provided that the following conditions 37 * are met: 38 * 1. Redistributions of source code must retain the above copyright 39 * notice, this list of conditions and the following disclaimer. 40 * 2. Redistributions in binary form must reproduce the above copyright 41 * notice, this list of conditions and the following disclaimer in the 42 * documentation and/or other materials provided with the distribution. 43 * 3. Neither the name of the copyright holders nor the names of its 44 * contributors may be used to endorse or promote products derived from 45 * this software without specific prior written permission. 46 * 47 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 48 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 49 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 50 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 51 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 52 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 53 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 54 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 55 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 56 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 57 * THE POSSIBILITY OF SUCH DAMAGE. 58 */ 59 package jdk.internal.org.objectweb.asm.util; 60 61 import java.io.FileInputStream; 62 import java.io.PrintWriter; 63 import java.util.ArrayList; 64 import java.util.HashMap; 65 import java.util.Iterator; 66 import java.util.List; 67 import java.util.Map; 68 69 import jdk.internal.org.objectweb.asm.AnnotationVisitor; 70 import jdk.internal.org.objectweb.asm.Attribute; 71 import jdk.internal.org.objectweb.asm.ClassReader; 72 import jdk.internal.org.objectweb.asm.ClassVisitor; 73 import jdk.internal.org.objectweb.asm.FieldVisitor; 74 import jdk.internal.org.objectweb.asm.Label; 75 import jdk.internal.org.objectweb.asm.MethodVisitor; 76 import jdk.internal.org.objectweb.asm.Opcodes; 77 import jdk.internal.org.objectweb.asm.Type; 78 import jdk.internal.org.objectweb.asm.tree.ClassNode; 79 import jdk.internal.org.objectweb.asm.tree.MethodNode; 80 import jdk.internal.org.objectweb.asm.tree.analysis.Analyzer; 81 import jdk.internal.org.objectweb.asm.tree.analysis.BasicValue; 82 import jdk.internal.org.objectweb.asm.tree.analysis.Frame; 83 import jdk.internal.org.objectweb.asm.tree.analysis.SimpleVerifier; 84 85 /** 86 * A {@link ClassVisitor} that checks that its methods are properly used. More 87 * precisely this class adapter checks each method call individually, based 88 * <i>only</i> on its arguments, but does <i>not</i> check the <i>sequence</i> 89 * of method calls. For example, the invalid sequence 90 * <tt>visitField(ACC_PUBLIC, "i", "I", null)</tt> <tt>visitField(ACC_PUBLIC, 91 * "i", "D", null)</tt> 92 * will <i>not</i> be detected by this class adapter. 93 * 94 * <p><code>CheckClassAdapter</code> can be also used to verify bytecode 95 * transformations in order to make sure transformed bytecode is sane. For 96 * example: 97 * 98 * <pre> 99 * InputStream is = ...; // get bytes for the source class 100 * ClassReader cr = new ClassReader(is); 101 * ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); 102 * ClassVisitor cv = new <b>MyClassAdapter</b>(new CheckClassAdapter(cw)); 103 * cr.accept(cv, 0); 104 * 105 * StringWriter sw = new StringWriter(); 106 * PrintWriter pw = new PrintWriter(sw); 107 * CheckClassAdapter.verify(new ClassReader(cw.toByteArray()), false, pw); 108 * assertTrue(sw.toString(), sw.toString().length()==0); 109 * </pre> 110 * 111 * Above code runs transformed bytecode trough the 112 * <code>CheckClassAdapter</code>. It won't be exactly the same verification 113 * as JVM does, but it run data flow analysis for the code of each method and 114 * checks that expectations are met for each method instruction. 115 * 116 * <p>If method bytecode has errors, assertion text will show the erroneous 117 * instruction number and dump of the failed method with information about 118 * locals and stack slot for each instruction. For example (format is - 119 * insnNumber locals : stack): 120 * 121 * <pre> 122 * jdk.internal.org.objectweb.asm.tree.analysis.AnalyzerException: Error at instruction 71: Expected I, but found . 123 * at jdk.internal.org.objectweb.asm.tree.analysis.Analyzer.analyze(Analyzer.java:289) 124 * at jdk.internal.org.objectweb.asm.util.CheckClassAdapter.verify(CheckClassAdapter.java:135) 125 * ... 126 * remove()V 127 * 00000 LinkedBlockingQueue$Itr . . . . . . . . : 128 * ICONST_0 129 * 00001 LinkedBlockingQueue$Itr . . . . . . . . : I 130 * ISTORE 2 131 * 00001 LinkedBlockingQueue$Itr <b>.</b> I . . . . . . : 132 * ... 133 * 134 * 00071 LinkedBlockingQueue$Itr <b>.</b> I . . . . . . : 135 * ILOAD 1 136 * 00072 <b>?</b> 137 * INVOKESPECIAL java/lang/Integer.{@literal <init>} (I)V 138 * ... 139 * </pre> 140 * 141 * In the above output you can see that variable 1 loaded by 142 * <code>ILOAD 1</code> instruction at position <code>00071</code> is not 143 * initialized. You can also see that at the beginning of the method (code 144 * inserted by the transformation) variable 2 is initialized. 145 * 146 * <p>Note that when used like that, <code>CheckClassAdapter.verify()</code> 147 * can trigger additional class loading, because it is using 148 * <code>SimpleVerifier</code>. 149 * 150 * @author Eric Bruneton 151 */ 152 public class CheckClassAdapter extends ClassVisitor { 153 154 /** 155 * The class version number. 156 */ 157 private int version; 158 159 /** 160 * <tt>true</tt> if the visit method has been called. 161 */ 162 private boolean start; 163 164 /** 165 * <tt>true</tt> if the visitSource method has been called. 166 */ 167 private boolean source; 168 169 /** 170 * <tt>true</tt> if the visitOuterClass method has been called. 171 */ 172 private boolean outer; 173 174 /** 175 * <tt>true</tt> if the visitEnd method has been called. 176 */ 177 private boolean end; 178 179 /** 180 * The already visited labels. This map associate Integer values to Label 181 * keys. 182 */ 183 private Map<Label, Integer> labels; 184 185 /** 186 * <tt>true</tt> if the method code must be checked with a BasicVerifier. 187 */ 188 private boolean checkDataFlow; 189 190 /** 191 * Checks a given class. <p> Usage: CheckClassAdapter <binary 192 * class name or class file name> 193 * 194 * @param args the command line arguments. 195 * 196 * @throws Exception if the class cannot be found, or if an IO exception 197 * occurs. 198 */ 199 public static void main(final String[] args) throws Exception { 200 if (args.length != 1) { 201 System.err.println("Verifies the given class."); 202 System.err.println("Usage: CheckClassAdapter " 203 + "<fully qualified class name or class file name>"); 204 return; 205 } 206 ClassReader cr; 207 if (args[0].endsWith(".class")) { 208 cr = new ClassReader(new FileInputStream(args[0])); 209 } else { 210 cr = new ClassReader(args[0]); 211 } 212 213 verify(cr, false, new PrintWriter(System.err)); 214 } 215 216 /** 217 * Checks a given class. 218 * 219 * @param cr a <code>ClassReader</code> that contains bytecode for the 220 * analysis. 221 * @param loader a <code>ClassLoader</code> which will be used to load 222 * referenced classes. This is useful if you are verifiying multiple 223 * interdependent classes. 224 * @param dump true if bytecode should be printed out not only when errors 225 * are found. 226 * @param pw write where results going to be printed 227 */ 228 public static void verify( 229 final ClassReader cr, 230 final ClassLoader loader, 231 final boolean dump, 232 final PrintWriter pw) 233 { 234 ClassNode cn = new ClassNode(); 235 cr.accept(new CheckClassAdapter(cn, false), ClassReader.SKIP_DEBUG); 236 237 Type syperType = cn.superName == null 238 ? null 239 : Type.getObjectType(cn.superName); 240 List<MethodNode> methods = cn.methods; 241 242 List<Type> interfaces = new ArrayList<Type>(); 243 for (Iterator<String> i = cn.interfaces.iterator(); i.hasNext();) { 244 interfaces.add(Type.getObjectType(i.next().toString())); 245 } 246 247 for (int i = 0; i < methods.size(); ++i) { 248 MethodNode method = methods.get(i); 249 SimpleVerifier verifier = new SimpleVerifier(Type.getObjectType(cn.name), 250 syperType, 251 interfaces, 252 (cn.access & Opcodes.ACC_INTERFACE) != 0); 253 Analyzer<BasicValue> a = new Analyzer<BasicValue>(verifier); 254 if (loader != null) { 255 verifier.setClassLoader(loader); 256 } 257 try { 258 a.analyze(cn.name, method); 259 if (!dump) { 260 continue; 261 } 262 } catch (Exception e) { 263 e.printStackTrace(pw); 264 } 265 printAnalyzerResult(method, a, pw); 266 } 267 pw.flush(); 268 } 269 270 /** 271 * Checks a given class 272 * 273 * @param cr a <code>ClassReader</code> that contains bytecode for the 274 * analysis. 275 * @param dump true if bytecode should be printed out not only when errors 276 * are found. 277 * @param pw write where results going to be printed 278 */ 279 public static void verify( 280 final ClassReader cr, 281 final boolean dump, 282 final PrintWriter pw) 283 { 284 verify(cr, null, dump, pw); 285 } 286 287 static void printAnalyzerResult( 288 MethodNode method, 289 Analyzer<BasicValue> a, 290 final PrintWriter pw) 291 { 292 Frame<BasicValue>[] frames = a.getFrames(); 293 Textifier t = new Textifier(); 294 TraceMethodVisitor mv = new TraceMethodVisitor(t); 295 296 pw.println(method.name + method.desc); 297 for (int j = 0; j < method.instructions.size(); ++j) { 298 method.instructions.get(j).accept(mv); 299 300 StringBuffer s = new StringBuffer(); 301 Frame<BasicValue> f = frames[j]; 302 if (f == null) { 303 s.append('?'); 304 } else { 305 for (int k = 0; k < f.getLocals(); ++k) { 306 s.append(getShortName(f.getLocal(k).toString())) 307 .append(' '); 308 } 309 s.append(" : "); 310 for (int k = 0; k < f.getStackSize(); ++k) { 311 s.append(getShortName(f.getStack(k).toString())) 312 .append(' '); 313 } 314 } 315 while (s.length() < method.maxStack + method.maxLocals + 1) { 316 s.append(' '); 317 } 318 pw.print(Integer.toString(j + 100000).substring(1)); 319 pw.print(" " + s + " : " + t.text.get(t.text.size() - 1)); 320 } 321 for (int j = 0; j < method.tryCatchBlocks.size(); ++j) { 322 method.tryCatchBlocks.get(j).accept(mv); 323 pw.print(" " + t.text.get(t.text.size() - 1)); 324 } 325 pw.println(); 326 } 327 328 private static String getShortName(final String name) { 329 int n = name.lastIndexOf('/'); 330 int k = name.length(); 331 if (name.charAt(k - 1) == ';') { 332 k--; 333 } 334 return n == -1 ? name : name.substring(n + 1, k); 335 } 336 337 /** 338 * Constructs a new {@link CheckClassAdapter}. <i>Subclasses must not use 339 * this constructor</i>. Instead, they must use the 340 * {@link #CheckClassAdapter(int, ClassVisitor, boolean)} version. 341 * 342 * @param cv the class visitor to which this adapter must delegate calls. 343 */ 344 public CheckClassAdapter(final ClassVisitor cv) { 345 this(cv, true); 346 } 347 348 /** 349 * Constructs a new {@link CheckClassAdapter}. <i>Subclasses must not use 350 * this constructor</i>. Instead, they must use the 351 * {@link #CheckClassAdapter(int, ClassVisitor, boolean)} version. 352 * 353 * @param cv the class visitor to which this adapter must delegate calls. 354 * @param checkDataFlow <tt>true</tt> to perform basic data flow checks, or 355 * <tt>false</tt> to not perform any data flow check (see 356 * {@link CheckMethodAdapter}). This option requires valid maxLocals 357 * and maxStack values. 358 */ 359 public CheckClassAdapter(final ClassVisitor cv, final boolean checkDataFlow) 360 { 361 this(Opcodes.ASM4, cv, checkDataFlow); 362 } 363 364 /** 365 * Constructs a new {@link CheckClassAdapter}. 366 * 367 * @param api the ASM API version implemented by this visitor. Must be one 368 * of {@link Opcodes#ASM4}. 369 * @param cv the class visitor to which this adapter must delegate calls. 370 * @param checkDataFlow <tt>true</tt> to perform basic data flow checks, or 371 * <tt>false</tt> to not perform any data flow check (see 372 * {@link CheckMethodAdapter}). This option requires valid maxLocals 373 * and maxStack values. 374 */ 375 protected CheckClassAdapter( 376 final int api, 377 final ClassVisitor cv, 378 final boolean checkDataFlow) 379 { 380 super(api, cv); 381 this.labels = new HashMap<Label, Integer>(); 382 this.checkDataFlow = checkDataFlow; 383 } 384 385 // ------------------------------------------------------------------------ 386 // Implementation of the ClassVisitor interface 387 // ------------------------------------------------------------------------ 388 389 @Override 390 public void visit( 391 final int version, 392 final int access, 393 final String name, 394 final String signature, 395 final String superName, 396 final String[] interfaces) 397 { 398 if (start) { 399 throw new IllegalStateException("visit must be called only once"); 400 } 401 start = true; 402 checkState(); 403 checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL 404 + Opcodes.ACC_SUPER + Opcodes.ACC_INTERFACE 405 + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC 406 + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM 407 + Opcodes.ACC_DEPRECATED 408 + 0x40000); // ClassWriter.ACC_SYNTHETIC_ATTRIBUTE 409 if (name == null || !name.endsWith("package-info")) { 410 CheckMethodAdapter.checkInternalName(name, "class name"); 411 } 412 if ("java/lang/Object".equals(name)) { 413 if (superName != null) { 414 throw new IllegalArgumentException("The super class name of the Object class must be 'null'"); 415 } 416 } else { 417 CheckMethodAdapter.checkInternalName(superName, "super class name"); 418 } 419 if (signature != null) { 420 CheckMethodAdapter.checkClassSignature(signature); 421 } 422 if ((access & Opcodes.ACC_INTERFACE) != 0) { 423 if (!"java/lang/Object".equals(superName)) { 424 throw new IllegalArgumentException("The super class name of interfaces must be 'java/lang/Object'"); 425 } 426 } 427 if (interfaces != null) { 428 for (int i = 0; i < interfaces.length; ++i) { 429 CheckMethodAdapter.checkInternalName(interfaces[i], 430 "interface name at index " + i); 431 } 432 } 433 this.version = version; 434 super.visit(version, access, name, signature, superName, interfaces); 435 } 436 437 @Override 438 public void visitSource(final String file, final String debug) { 439 checkState(); 440 if (source) { 441 throw new IllegalStateException("visitSource can be called only once."); 442 } 443 source = true; 444 super.visitSource(file, debug); 445 } 446 447 @Override 448 public void visitOuterClass( 449 final String owner, 450 final String name, 451 final String desc) 452 { 453 checkState(); 454 if (outer) { 455 throw new IllegalStateException("visitOuterClass can be called only once."); 456 } 457 outer = true; 458 if (owner == null) { 459 throw new IllegalArgumentException("Illegal outer class owner"); 460 } 461 if (desc != null) { 462 CheckMethodAdapter.checkMethodDesc(desc); 463 } 464 super.visitOuterClass(owner, name, desc); 465 } 466 467 @Override 468 public void visitInnerClass( 469 final String name, 470 final String outerName, 471 final String innerName, 472 final int access) 473 { 474 checkState(); 475 CheckMethodAdapter.checkInternalName(name, "class name"); 476 if (outerName != null) { 477 CheckMethodAdapter.checkInternalName(outerName, "outer class name"); 478 } 479 if (innerName != null) { 480 CheckMethodAdapter.checkIdentifier(innerName, "inner class name"); 481 } 482 checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE 483 + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC 484 + Opcodes.ACC_FINAL + Opcodes.ACC_INTERFACE 485 + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC 486 + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM); 487 super.visitInnerClass(name, outerName, innerName, access); 488 } 489 490 @Override 491 public FieldVisitor visitField( 492 final int access, 493 final String name, 494 final String desc, 495 final String signature, 496 final Object value) 497 { 498 checkState(); 499 checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE 500 + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC 501 + Opcodes.ACC_FINAL + Opcodes.ACC_VOLATILE 502 + Opcodes.ACC_TRANSIENT + Opcodes.ACC_SYNTHETIC 503 + Opcodes.ACC_ENUM + Opcodes.ACC_DEPRECATED 504 + 0x40000); // ClassWriter.ACC_SYNTHETIC_ATTRIBUTE 505 CheckMethodAdapter.checkUnqualifiedName(version, name, "field name"); 506 CheckMethodAdapter.checkDesc(desc, false); 507 if (signature != null) { 508 CheckMethodAdapter.checkFieldSignature(signature); 509 } 510 if (value != null) { 511 CheckMethodAdapter.checkConstant(value); 512 } 513 FieldVisitor av = super.visitField(access, name, desc, signature, value); 514 return new CheckFieldAdapter(av); 515 } 516 517 @Override 518 public MethodVisitor visitMethod( 519 final int access, 520 final String name, 521 final String desc, 522 final String signature, 523 final String[] exceptions) 524 { 525 checkState(); 526 checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE 527 + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC 528 + Opcodes.ACC_FINAL + Opcodes.ACC_SYNCHRONIZED 529 + Opcodes.ACC_BRIDGE + Opcodes.ACC_VARARGS + Opcodes.ACC_NATIVE 530 + Opcodes.ACC_ABSTRACT + Opcodes.ACC_STRICT 531 + Opcodes.ACC_SYNTHETIC + Opcodes.ACC_DEPRECATED 532 + 0x40000); // ClassWriter.ACC_SYNTHETIC_ATTRIBUTE 533 CheckMethodAdapter.checkMethodIdentifier(version, name, "method name"); 534 CheckMethodAdapter.checkMethodDesc(desc); 535 if (signature != null) { 536 CheckMethodAdapter.checkMethodSignature(signature); 537 } 538 if (exceptions != null) { 539 for (int i = 0; i < exceptions.length; ++i) { 540 CheckMethodAdapter.checkInternalName(exceptions[i], 541 "exception name at index " + i); 542 } 543 } 544 CheckMethodAdapter cma; 545 if (checkDataFlow) { 546 cma = new CheckMethodAdapter(access, 547 name, 548 desc, 549 super.visitMethod(access, name, desc, signature, exceptions), 550 labels); 551 } else { 552 cma = new CheckMethodAdapter(super.visitMethod(access, 553 name, 554 desc, 555 signature, 556 exceptions), labels); 557 } 558 cma.version = version; 559 return cma; 560 } 561 562 @Override 563 public AnnotationVisitor visitAnnotation( 564 final String desc, 565 final boolean visible) 566 { 567 checkState(); 568 CheckMethodAdapter.checkDesc(desc, false); 569 return new CheckAnnotationAdapter(super.visitAnnotation(desc, visible)); 570 } 571 572 @Override 573 public void visitAttribute(final Attribute attr) { 574 checkState(); 575 if (attr == null) { 576 throw new IllegalArgumentException("Invalid attribute (must not be null)"); 577 } 578 super.visitAttribute(attr); 579 } 580 581 @Override 582 public void visitEnd() { 583 checkState(); 584 end = true; 585 super.visitEnd(); 586 } 587 588 // ------------------------------------------------------------------------ 589 // Utility methods 590 // ------------------------------------------------------------------------ 591 592 /** 593 * Checks that the visit method has been called and that visitEnd has not 594 * been called. 595 */ 596 private void checkState() { 597 if (!start) { 598 throw new IllegalStateException("Cannot visit member before visit has been called."); 599 } 600 if (end) { 601 throw new IllegalStateException("Cannot visit member after visitEnd has been called."); 602 } 603 } 604 605 /** 606 * Checks that the given access flags do not contain invalid flags. This 607 * method also checks that mutually incompatible flags are not set 608 * simultaneously. 609 * 610 * @param access the access flags to be checked 611 * @param possibleAccess the valid access flags. 612 */ 613 static void checkAccess(final int access, final int possibleAccess) { 614 if ((access & ~possibleAccess) != 0) { 615 throw new IllegalArgumentException("Invalid access flags: " 616 + access); 617 } 618 int pub = (access & Opcodes.ACC_PUBLIC) == 0 ? 0 : 1; 619 int pri = (access & Opcodes.ACC_PRIVATE) == 0 ? 0 : 1; 620 int pro = (access & Opcodes.ACC_PROTECTED) == 0 ? 0 : 1; 621 if (pub + pri + pro > 1) { 622 throw new IllegalArgumentException("public private and protected are mutually exclusive: " 623 + access); 624 } 625 int fin = (access & Opcodes.ACC_FINAL) == 0 ? 0 : 1; 626 int abs = (access & Opcodes.ACC_ABSTRACT) == 0 ? 0 : 1; 627 if (fin + abs > 1) { 628 throw new IllegalArgumentException("final and abstract are mutually exclusive: " 629 + access); 630 } 631 } 632 }