1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 package org.apache.bcel.verifier.statics; 19 20 21 import java.util.HashMap; 22 import java.util.HashSet; 23 import java.util.Locale; 24 import java.util.Map; 25 import java.util.Set; 26 27 import org.apache.bcel.Const; 28 import org.apache.bcel.Constants; 29 import org.apache.bcel.Repository; 30 import org.apache.bcel.classfile.Attribute; 31 import org.apache.bcel.classfile.ClassFormatException; 32 import org.apache.bcel.classfile.Code; 33 import org.apache.bcel.classfile.CodeException; 34 import org.apache.bcel.classfile.Constant; 35 import org.apache.bcel.classfile.ConstantClass; 36 import org.apache.bcel.classfile.ConstantDouble; 37 import org.apache.bcel.classfile.ConstantFieldref; 38 import org.apache.bcel.classfile.ConstantFloat; 39 import org.apache.bcel.classfile.ConstantInteger; 40 import org.apache.bcel.classfile.ConstantInterfaceMethodref; 41 import org.apache.bcel.classfile.ConstantLong; 42 import org.apache.bcel.classfile.ConstantMethodref; 43 import org.apache.bcel.classfile.ConstantNameAndType; 44 import org.apache.bcel.classfile.ConstantPool; 45 import org.apache.bcel.classfile.ConstantString; 46 import org.apache.bcel.classfile.ConstantUtf8; 47 import org.apache.bcel.classfile.ConstantValue; 48 import org.apache.bcel.classfile.Deprecated; 49 import org.apache.bcel.classfile.DescendingVisitor; 50 import org.apache.bcel.classfile.EmptyVisitor; 51 import org.apache.bcel.classfile.ExceptionTable; 52 import org.apache.bcel.classfile.Field; 53 import org.apache.bcel.classfile.InnerClass; 54 import org.apache.bcel.classfile.InnerClasses; 55 import org.apache.bcel.classfile.JavaClass; 56 import org.apache.bcel.classfile.LineNumber; 57 import org.apache.bcel.classfile.LineNumberTable; 58 import org.apache.bcel.classfile.LocalVariable; 59 import org.apache.bcel.classfile.LocalVariableTable; 60 import org.apache.bcel.classfile.Method; 61 import org.apache.bcel.classfile.Node; 62 import org.apache.bcel.classfile.SourceFile; 63 import org.apache.bcel.classfile.Synthetic; 64 import org.apache.bcel.classfile.Unknown; 65 import org.apache.bcel.generic.ArrayType; 66 import org.apache.bcel.generic.ObjectType; 67 import org.apache.bcel.generic.Type; 68 import org.apache.bcel.verifier.PassVerifier; 69 import org.apache.bcel.verifier.VerificationResult; 70 import org.apache.bcel.verifier.Verifier; 71 import org.apache.bcel.verifier.VerifierFactory; 72 import org.apache.bcel.verifier.exc.AssertionViolatedException; 73 import org.apache.bcel.verifier.exc.ClassConstraintException; 74 import org.apache.bcel.verifier.exc.LocalVariableInfoInconsistentException; 75 76 /** 77 * This PassVerifier verifies a class file according to 78 * pass 2 as described in The Java Virtual Machine 79 * Specification, 2nd edition. 80 * More detailed information is to be found at the do_verify() 81 * method's documentation. 82 * 83 * @version $Id$ 84 * @see #do_verify() 85 */ 86 public final class Pass2Verifier extends PassVerifier implements Constants { 87 88 /** 89 * The LocalVariableInfo instances used by Pass3bVerifier. 90 * localVariablesInfos[i] denotes the information for the 91 * local variables of method number i in the 92 * JavaClass this verifier operates on. 93 */ 94 private LocalVariablesInfo[] localVariablesInfos; 95 96 /** The Verifier that created this. */ 97 private final Verifier myOwner; 98 99 /** 100 * Should only be instantiated by a Verifier. 101 * 102 * @see Verifier 103 */ Pass2Verifier(final Verifier owner)104 public Pass2Verifier(final Verifier owner) { 105 myOwner = owner; 106 } 107 108 /** 109 * Returns a LocalVariablesInfo object containing information 110 * about the usage of the local variables in the Code attribute 111 * of the said method or <B>null</B> if the class file this 112 * Pass2Verifier operates on could not be pass-2-verified correctly. 113 * The method number method_nr is the method you get using 114 * <B>Repository.lookupClass(myOwner.getClassname()).getMethods()[method_nr];</B>. 115 * You should not add own information. Leave that to JustIce. 116 */ getLocalVariablesInfo(final int method_nr)117 public LocalVariablesInfo getLocalVariablesInfo(final int method_nr) { 118 if (this.verify() != VerificationResult.VR_OK) { 119 return null; // It's cached, don't worry. 120 } 121 if (method_nr < 0 || method_nr >= localVariablesInfos.length) { 122 throw new AssertionViolatedException("Method number out of range."); 123 } 124 return localVariablesInfos[method_nr]; 125 } 126 127 /** 128 * Pass 2 is the pass where static properties of the 129 * class file are checked without looking into "Code" 130 * arrays of methods. 131 * This verification pass is usually invoked when 132 * a class is resolved; and it may be possible that 133 * this verification pass has to load in other classes 134 * such as superclasses or implemented interfaces. 135 * Therefore, Pass 1 is run on them.<BR> 136 * Note that most referenced classes are <B>not</B> loaded 137 * in for verification or for an existance check by this 138 * pass; only the syntactical correctness of their names 139 * and descriptors (a.k.a. signatures) is checked.<BR> 140 * Very few checks that conceptually belong here 141 * are delayed until pass 3a in JustIce. JustIce does 142 * not only check for syntactical correctness but also 143 * for semantical sanity - therefore it needs access to 144 * the "Code" array of methods in a few cases. Please 145 * see the pass 3a documentation, too. 146 * 147 * @see Pass3aVerifier 148 */ 149 @Override do_verify()150 public VerificationResult do_verify() { 151 try { 152 final VerificationResult vr1 = myOwner.doPass1(); 153 if (vr1.equals(VerificationResult.VR_OK)) { 154 155 // For every method, we could have information about the local variables out of LocalVariableTable attributes of 156 // the Code attributes. 157 localVariablesInfos = new LocalVariablesInfo[Repository.lookupClass(myOwner.getClassName()).getMethods().length]; 158 159 VerificationResult vr = VerificationResult.VR_OK; // default. 160 try{ 161 constant_pool_entries_satisfy_static_constraints(); 162 field_and_method_refs_are_valid(); 163 every_class_has_an_accessible_superclass(); 164 final_methods_are_not_overridden(); 165 } 166 catch (final ClassConstraintException cce) { 167 vr = new VerificationResult(VerificationResult.VERIFIED_REJECTED, cce.getMessage()); 168 } 169 return vr; 170 } 171 return VerificationResult.VR_NOTYET; 172 173 } catch (final ClassNotFoundException e) { 174 // FIXME: this might not be the best way to handle missing classes. 175 throw new AssertionViolatedException("Missing class: " + e, e); 176 } 177 } 178 179 /** 180 * Ensures that every class has a super class and that 181 * <B>final</B> classes are not subclassed. 182 * This means, the class this Pass2Verifier operates 183 * on has proper super classes (transitively) up to 184 * java.lang.Object. 185 * The reason for really loading (and Pass1-verifying) 186 * all of those classes here is that we need them in 187 * Pass2 anyway to verify no final methods are overridden 188 * (that could be declared anywhere in the ancestor hierarchy). 189 * 190 * @throws ClassConstraintException otherwise. 191 */ every_class_has_an_accessible_superclass()192 private void every_class_has_an_accessible_superclass() { 193 try { 194 final Set<String> hs = new HashSet<>(); // save class names to detect circular inheritance 195 JavaClass jc = Repository.lookupClass(myOwner.getClassName()); 196 int supidx = -1; 197 198 while (supidx != 0) { 199 supidx = jc.getSuperclassNameIndex(); 200 201 if (supidx == 0) { 202 if (jc != Repository.lookupClass(Type.OBJECT.getClassName())) { 203 throw new ClassConstraintException("Superclass of '"+jc.getClassName()+ 204 "' missing but not "+Type.OBJECT.getClassName()+" itself!"); 205 } 206 } 207 else{ 208 final String supername = jc.getSuperclassName(); 209 if (! hs.add(supername)) { // If supername already is in the list 210 throw new ClassConstraintException("Circular superclass hierarchy detected."); 211 } 212 final Verifier v = VerifierFactory.getVerifier(supername); 213 final VerificationResult vr = v.doPass1(); 214 215 if (vr != VerificationResult.VR_OK) { 216 throw new ClassConstraintException("Could not load in ancestor class '"+supername+"'."); 217 } 218 jc = Repository.lookupClass(supername); 219 220 if (jc.isFinal()) { 221 throw new ClassConstraintException("Ancestor class '"+supername+ 222 "' has the FINAL access modifier and must therefore not be subclassed."); 223 } 224 } 225 } 226 227 } catch (final ClassNotFoundException e) { 228 // FIXME: this might not be the best way to handle missing classes. 229 throw new AssertionViolatedException("Missing class: " + e, e); 230 } 231 } 232 233 /** 234 * Ensures that <B>final</B> methods are not overridden. 235 * <B>Precondition to run this method: 236 * constant_pool_entries_satisfy_static_constraints() and 237 * every_class_has_an_accessible_superclass() have to be invoked before 238 * (in that order).</B> 239 * 240 * @throws ClassConstraintException otherwise. 241 * @see #constant_pool_entries_satisfy_static_constraints() 242 * @see #every_class_has_an_accessible_superclass() 243 */ final_methods_are_not_overridden()244 private void final_methods_are_not_overridden() { 245 try { 246 final Map<String, String> hashmap = new HashMap<>(); 247 JavaClass jc = Repository.lookupClass(myOwner.getClassName()); 248 249 int supidx = -1; 250 while (supidx != 0) { 251 supidx = jc.getSuperclassNameIndex(); 252 253 final Method[] methods = jc.getMethods(); 254 for (final Method method : methods) { 255 final String nameAndSig = method.getName() + method.getSignature(); 256 257 if (hashmap.containsKey(nameAndSig)) { 258 if (method.isFinal()) { 259 if (!(method.isPrivate())) { 260 throw new ClassConstraintException("Method '" + nameAndSig + "' in class '" + hashmap.get(nameAndSig) + 261 "' overrides the final (not-overridable) definition in class '" + jc.getClassName() + "'."); 262 } 263 addMessage("Method '" + nameAndSig + "' in class '" + hashmap.get(nameAndSig) + 264 "' overrides the final (not-overridable) definition in class '" + jc.getClassName() + 265 "'. This is okay, as the original definition was private; however this constraint leverage"+ 266 " was introduced by JLS 8.4.6 (not vmspec2) and the behaviour of the Sun verifiers."); 267 } else { 268 if (!method.isStatic()) { // static methods don't inherit 269 hashmap.put(nameAndSig, jc.getClassName()); 270 } 271 } 272 } else { 273 if (!method.isStatic()) { // static methods don't inherit 274 hashmap.put(nameAndSig, jc.getClassName()); 275 } 276 } 277 } 278 279 jc = Repository.lookupClass(jc.getSuperclassName()); 280 // Well, for OBJECT this returns OBJECT so it works (could return anything but must not throw an Exception). 281 } 282 283 } catch (final ClassNotFoundException e) { 284 // FIXME: this might not be the best way to handle missing classes. 285 throw new AssertionViolatedException("Missing class: " + e, e); 286 } 287 288 } 289 290 /** 291 * Ensures that the constant pool entries satisfy the static constraints 292 * as described in The Java Virtual Machine Specification, 2nd Edition. 293 * 294 * @throws ClassConstraintException otherwise. 295 */ constant_pool_entries_satisfy_static_constraints()296 private void constant_pool_entries_satisfy_static_constraints() { 297 try { 298 // Most of the consistency is handled internally by BCEL; here 299 // we only have to verify if the indices of the constants point 300 // to constants of the appropriate type and such. 301 final JavaClass jc = Repository.lookupClass(myOwner.getClassName()); 302 new CPESSC_Visitor(jc); // constructor implicitly traverses jc 303 304 } catch (final ClassNotFoundException e) { 305 // FIXME: this might not be the best way to handle missing classes. 306 throw new AssertionViolatedException("Missing class: " + e, e); 307 } 308 } 309 310 /** 311 * A Visitor class that ensures the constant pool satisfies the static 312 * constraints. 313 * The visitXXX() methods throw ClassConstraintException instances otherwise. 314 * 315 * @see #constant_pool_entries_satisfy_static_constraints() 316 */ 317 private final class CPESSC_Visitor extends org.apache.bcel.classfile.EmptyVisitor{ 318 private final Class<?> CONST_Class; 319 /* 320 private Class<?> CONST_Fieldref; 321 private Class<?> CONST_Methodref; 322 private Class<?> CONST_InterfaceMethodref; 323 */ 324 private final Class<?> CONST_String; 325 private final Class<?> CONST_Integer; 326 private final Class<?> CONST_Float; 327 private final Class<?> CONST_Long; 328 private final Class<?> CONST_Double; 329 private final Class<?> CONST_NameAndType; 330 private final Class<?> CONST_Utf8; 331 332 private final JavaClass jc; 333 private final ConstantPool cp; // ==jc.getConstantPool() -- only here to save typing work and computing power. 334 private final int cplen; // == cp.getLength() -- to save computing power. 335 private final DescendingVisitor carrier; 336 337 private final Set<String> field_names = new HashSet<>(); 338 private final Set<String> field_names_and_desc = new HashSet<>(); 339 private final Set<String> method_names_and_desc = new HashSet<>(); 340 CPESSC_Visitor(final JavaClass _jc)341 private CPESSC_Visitor(final JavaClass _jc) { 342 jc = _jc; 343 cp = _jc.getConstantPool(); 344 cplen = cp.getLength(); 345 346 CONST_Class = ConstantClass.class; 347 /* 348 CONST_Fieldref = ConstantFieldref.class; 349 CONST_Methodref = ConstantMethodref.class; 350 CONST_InterfaceMethodref = ConstantInterfaceMethodref.class; 351 */ 352 CONST_String = ConstantString.class; 353 CONST_Integer = ConstantInteger.class; 354 CONST_Float = ConstantFloat.class; 355 CONST_Long = ConstantLong.class; 356 CONST_Double = ConstantDouble.class; 357 CONST_NameAndType = ConstantNameAndType.class; 358 CONST_Utf8 = ConstantUtf8.class; 359 360 carrier = new DescendingVisitor(_jc, this); 361 carrier.visit(); 362 } 363 checkIndex(final Node referrer, final int index, final Class<?> shouldbe)364 private void checkIndex(final Node referrer, final int index, final Class<?> shouldbe) { 365 if ((index < 0) || (index >= cplen)) { 366 throw new ClassConstraintException("Invalid index '"+index+"' used by '"+tostring(referrer)+"'."); 367 } 368 final Constant c = cp.getConstant(index); 369 if (! shouldbe.isInstance(c)) { 370 /* String isnot = shouldbe.toString().substring(shouldbe.toString().lastIndexOf(".")+1); //Cut all before last "." */ 371 throw new ClassCastException("Illegal constant '"+tostring(c)+"' at index '"+ 372 index+"'. '"+tostring(referrer)+"' expects a '"+shouldbe+"'."); 373 } 374 } 375 /////////////////////////////////////// 376 // ClassFile structure (vmspec2 4.1) // 377 /////////////////////////////////////// 378 @Override visitJavaClass(final JavaClass obj)379 public void visitJavaClass(final JavaClass obj) { 380 final Attribute[] atts = obj.getAttributes(); 381 boolean foundSourceFile = false; 382 boolean foundInnerClasses = false; 383 384 // Is there an InnerClass referenced? 385 // This is a costly check; existing verifiers don't do it! 386 final boolean hasInnerClass = new InnerClassDetector(jc).innerClassReferenced(); 387 388 for (final Attribute att : atts) { 389 if ((!(att instanceof SourceFile)) && 390 (!(att instanceof Deprecated)) && 391 (!(att instanceof InnerClasses)) && 392 (!(att instanceof Synthetic))) { 393 addMessage("Attribute '" + tostring(att) + "' as an attribute of the ClassFile structure '" + 394 tostring(obj) + "' is unknown and will therefore be ignored."); 395 } 396 397 if (att instanceof SourceFile) { 398 if (!foundSourceFile) { 399 foundSourceFile = true; 400 } else { 401 throw new ClassConstraintException("A ClassFile structure (like '" + 402 tostring(obj) + "') may have no more than one SourceFile attribute."); //vmspec2 4.7.7 403 } 404 } 405 406 if (att instanceof InnerClasses) { 407 if (!foundInnerClasses) { 408 foundInnerClasses = true; 409 } else { 410 if (hasInnerClass) { 411 throw new ClassConstraintException("A Classfile structure (like '" + tostring(obj) + 412 "') must have exactly one InnerClasses attribute"+ 413 " if at least one Inner Class is referenced (which is the case)."+ 414 " More than one InnerClasses attribute was found."); 415 } 416 } 417 if (!hasInnerClass) { 418 addMessage("No referenced Inner Class found, but InnerClasses attribute '" + tostring(att) + 419 "' found. Strongly suggest removal of that attribute."); 420 } 421 } 422 423 } 424 if (hasInnerClass && !foundInnerClasses) { 425 //throw new ClassConstraintException("A Classfile structure (like '"+tostring(obj)+ 426 // "') must have exactly one InnerClasses attribute if at least one Inner Class is referenced (which is the case)."+ 427 // " No InnerClasses attribute was found."); 428 //vmspec2, page 125 says it would be a constraint: but existing verifiers 429 //don't check it and javac doesn't satisfy it when it comes to anonymous 430 //inner classes 431 addMessage("A Classfile structure (like '"+tostring(obj)+ 432 "') must have exactly one InnerClasses attribute if at least one Inner Class is referenced (which is the case)."+ 433 " No InnerClasses attribute was found."); 434 } 435 } 436 ///////////////////////////// 437 // CONSTANTS (vmspec2 4.4) // 438 ///////////////////////////// 439 @Override visitConstantClass(final ConstantClass obj)440 public void visitConstantClass(final ConstantClass obj) { 441 if (obj.getTag() != Const.CONSTANT_Class) { 442 throw new ClassConstraintException("Wrong constant tag in '"+tostring(obj)+"'."); 443 } 444 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 445 446 } 447 @Override visitConstantFieldref(final ConstantFieldref obj)448 public void visitConstantFieldref(final ConstantFieldref obj) { 449 if (obj.getTag() != Const.CONSTANT_Fieldref) { 450 throw new ClassConstraintException("Wrong constant tag in '"+tostring(obj)+"'."); 451 } 452 checkIndex(obj, obj.getClassIndex(), CONST_Class); 453 checkIndex(obj, obj.getNameAndTypeIndex(), CONST_NameAndType); 454 } 455 @Override visitConstantMethodref(final ConstantMethodref obj)456 public void visitConstantMethodref(final ConstantMethodref obj) { 457 if (obj.getTag() != Const.CONSTANT_Methodref) { 458 throw new ClassConstraintException("Wrong constant tag in '"+tostring(obj)+"'."); 459 } 460 checkIndex(obj, obj.getClassIndex(), CONST_Class); 461 checkIndex(obj, obj.getNameAndTypeIndex(), CONST_NameAndType); 462 } 463 @Override visitConstantInterfaceMethodref(final ConstantInterfaceMethodref obj)464 public void visitConstantInterfaceMethodref(final ConstantInterfaceMethodref obj) { 465 if (obj.getTag() != Const.CONSTANT_InterfaceMethodref) { 466 throw new ClassConstraintException("Wrong constant tag in '"+tostring(obj)+"'."); 467 } 468 checkIndex(obj, obj.getClassIndex(), CONST_Class); 469 checkIndex(obj, obj.getNameAndTypeIndex(), CONST_NameAndType); 470 } 471 @Override visitConstantString(final ConstantString obj)472 public void visitConstantString(final ConstantString obj) { 473 if (obj.getTag() != Const.CONSTANT_String) { 474 throw new ClassConstraintException("Wrong constant tag in '"+tostring(obj)+"'."); 475 } 476 checkIndex(obj, obj.getStringIndex(), CONST_Utf8); 477 } 478 @Override visitConstantInteger(final ConstantInteger obj)479 public void visitConstantInteger(final ConstantInteger obj) { 480 if (obj.getTag() != Const.CONSTANT_Integer) { 481 throw new ClassConstraintException("Wrong constant tag in '"+tostring(obj)+"'."); 482 } 483 // no indices to check 484 } 485 @Override visitConstantFloat(final ConstantFloat obj)486 public void visitConstantFloat(final ConstantFloat obj) { 487 if (obj.getTag() != Const.CONSTANT_Float) { 488 throw new ClassConstraintException("Wrong constant tag in '"+tostring(obj)+"'."); 489 } 490 //no indices to check 491 } 492 @Override visitConstantLong(final ConstantLong obj)493 public void visitConstantLong(final ConstantLong obj) { 494 if (obj.getTag() != Const.CONSTANT_Long) { 495 throw new ClassConstraintException("Wrong constant tag in '"+tostring(obj)+"'."); 496 } 497 //no indices to check 498 } 499 @Override visitConstantDouble(final ConstantDouble obj)500 public void visitConstantDouble(final ConstantDouble obj) { 501 if (obj.getTag() != Const.CONSTANT_Double) { 502 throw new ClassConstraintException("Wrong constant tag in '"+tostring(obj)+"'."); 503 } 504 //no indices to check 505 } 506 @Override visitConstantNameAndType(final ConstantNameAndType obj)507 public void visitConstantNameAndType(final ConstantNameAndType obj) { 508 if (obj.getTag() != Const.CONSTANT_NameAndType) { 509 throw new ClassConstraintException("Wrong constant tag in '"+tostring(obj)+"'."); 510 } 511 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 512 //checkIndex(obj, obj.getDescriptorIndex(), CONST_Utf8); //inconsistently named in BCEL, see below. 513 checkIndex(obj, obj.getSignatureIndex(), CONST_Utf8); 514 } 515 @Override visitConstantUtf8(final ConstantUtf8 obj)516 public void visitConstantUtf8(final ConstantUtf8 obj) { 517 if (obj.getTag() != Const.CONSTANT_Utf8) { 518 throw new ClassConstraintException("Wrong constant tag in '"+tostring(obj)+"'."); 519 } 520 //no indices to check 521 } 522 ////////////////////////// 523 // FIELDS (vmspec2 4.5) // 524 ////////////////////////// 525 @Override visitField(final Field obj)526 public void visitField(final Field obj) { 527 528 if (jc.isClass()) { 529 int maxone=0; 530 if (obj.isPrivate()) { 531 maxone++; 532 } 533 if (obj.isProtected()) { 534 maxone++; 535 } 536 if (obj.isPublic()) { 537 maxone++; 538 } 539 if (maxone > 1) { 540 throw new ClassConstraintException("Field '"+tostring(obj)+ 541 "' must only have at most one of its ACC_PRIVATE, ACC_PROTECTED, ACC_PUBLIC modifiers set."); 542 } 543 544 if (obj.isFinal() && obj.isVolatile()) { 545 throw new ClassConstraintException("Field '"+tostring(obj)+ 546 "' must only have at most one of its ACC_FINAL, ACC_VOLATILE modifiers set."); 547 } 548 } 549 else{ // isInterface! 550 if (!obj.isPublic()) { 551 throw new ClassConstraintException("Interface field '"+tostring(obj)+ 552 "' must have the ACC_PUBLIC modifier set but hasn't!"); 553 } 554 if (!obj.isStatic()) { 555 throw new ClassConstraintException("Interface field '"+tostring(obj)+ 556 "' must have the ACC_STATIC modifier set but hasn't!"); 557 } 558 if (!obj.isFinal()) { 559 throw new ClassConstraintException("Interface field '"+tostring(obj)+ 560 "' must have the ACC_FINAL modifier set but hasn't!"); 561 } 562 } 563 564 if ((obj.getAccessFlags() & ~(Const.ACC_PUBLIC|Const.ACC_PRIVATE|Const.ACC_PROTECTED|Const.ACC_STATIC| 565 Const.ACC_FINAL|Const.ACC_VOLATILE|Const.ACC_TRANSIENT)) > 0) { 566 addMessage("Field '"+tostring(obj)+ 567 "' has access flag(s) other than ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED,"+ 568 " ACC_STATIC, ACC_FINAL, ACC_VOLATILE, ACC_TRANSIENT set (ignored)."); 569 } 570 571 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 572 573 final String name = obj.getName(); 574 if (! validFieldName(name)) { 575 throw new ClassConstraintException("Field '"+tostring(obj)+"' has illegal name '"+obj.getName()+"'."); 576 } 577 578 // A descriptor is often named signature in BCEL 579 checkIndex(obj, obj.getSignatureIndex(), CONST_Utf8); 580 581 final String sig = ((ConstantUtf8) (cp.getConstant(obj.getSignatureIndex()))).getBytes(); // Field or Method sig.(=descriptor) 582 583 try{ 584 Type.getType(sig); /* Don't need the return value */ 585 } 586 catch (final ClassFormatException cfe) { 587 throw new ClassConstraintException("Illegal descriptor (==signature) '"+sig+"' used by '"+tostring(obj)+"'.", cfe); 588 } 589 590 final String nameanddesc = name+sig; 591 if (field_names_and_desc.contains(nameanddesc)) { 592 throw new ClassConstraintException("No two fields (like '"+tostring(obj)+ 593 "') are allowed have same names and descriptors!"); 594 } 595 if (field_names.contains(name)) { 596 addMessage("More than one field of name '"+name+ 597 "' detected (but with different type descriptors). This is very unusual."); 598 } 599 field_names_and_desc.add(nameanddesc); 600 field_names.add(name); 601 602 final Attribute[] atts = obj.getAttributes(); 603 for (final Attribute att : atts) { 604 if ((!(att instanceof ConstantValue)) && 605 (!(att instanceof Synthetic)) && 606 (!(att instanceof Deprecated))) { 607 addMessage("Attribute '" + tostring(att) + "' as an attribute of Field '" + 608 tostring(obj) + "' is unknown and will therefore be ignored."); 609 } 610 if (!(att instanceof ConstantValue)) { 611 addMessage("Attribute '" + tostring(att) + "' as an attribute of Field '" + tostring(obj) + 612 "' is not a ConstantValue and is therefore only of use for debuggers and such."); 613 } 614 } 615 } 616 /////////////////////////// 617 // METHODS (vmspec2 4.6) // 618 /////////////////////////// 619 @Override visitMethod(final Method obj)620 public void visitMethod(final Method obj) { 621 622 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 623 624 final String name = obj.getName(); 625 if (! validMethodName(name, true)) { 626 throw new ClassConstraintException("Method '"+tostring(obj)+"' has illegal name '"+name+"'."); 627 } 628 629 // A descriptor is often named signature in BCEL 630 checkIndex(obj, obj.getSignatureIndex(), CONST_Utf8); 631 632 final String sig = ((ConstantUtf8) (cp.getConstant(obj.getSignatureIndex()))).getBytes(); // Method's signature(=descriptor) 633 634 Type t; 635 Type[] ts; // needed below the try block. 636 try{ 637 t = Type.getReturnType(sig); 638 ts = Type.getArgumentTypes(sig); 639 } 640 catch (final ClassFormatException cfe) { 641 throw new ClassConstraintException( 642 "Illegal descriptor (==signature) '"+sig+"' used by Method '"+tostring(obj)+"'.", cfe); 643 } 644 645 // Check if referenced objects exist. 646 Type act = t; 647 if (act instanceof ArrayType) { 648 act = ((ArrayType) act).getBasicType(); 649 } 650 if (act instanceof ObjectType) { 651 final Verifier v = VerifierFactory.getVerifier( ((ObjectType) act).getClassName() ); 652 final VerificationResult vr = v.doPass1(); 653 if (vr != VerificationResult.VR_OK) { 654 throw new ClassConstraintException( 655 "Method '"+tostring(obj)+"' has a return type that does not pass verification pass 1: '"+vr+"'."); 656 } 657 } 658 659 for (final Type element : ts) { 660 act = element; 661 if (act instanceof ArrayType) { 662 act = ((ArrayType) act).getBasicType(); 663 } 664 if (act instanceof ObjectType) { 665 final Verifier v = VerifierFactory.getVerifier( ((ObjectType) act).getClassName() ); 666 final VerificationResult vr = v.doPass1(); 667 if (vr != VerificationResult.VR_OK) { 668 throw new ClassConstraintException( 669 "Method '"+tostring(obj)+"' has an argument type that does not pass verification pass 1: '"+vr+"'."); 670 } 671 } 672 } 673 674 // Nearly forgot this! Funny return values are allowed, but a non-empty arguments list makes a different method out of it! 675 if (name.equals(Const.STATIC_INITIALIZER_NAME) && (ts.length != 0)) { 676 throw new ClassConstraintException( 677 "Method '"+tostring(obj)+"' has illegal name '"+name+"'."+ 678 " Its name resembles the class or interface initialization method"+ 679 " which it isn't because of its arguments (==descriptor)."); 680 } 681 682 if (jc.isClass()) { 683 int maxone=0; 684 if (obj.isPrivate()) { 685 maxone++; 686 } 687 if (obj.isProtected()) { 688 maxone++; 689 } 690 if (obj.isPublic()) { 691 maxone++; 692 } 693 if (maxone > 1) { 694 throw new ClassConstraintException("Method '"+tostring(obj)+ 695 "' must only have at most one of its ACC_PRIVATE, ACC_PROTECTED, ACC_PUBLIC modifiers set."); 696 } 697 698 if (obj.isAbstract()) { 699 if (obj.isFinal()) { 700 throw new ClassConstraintException( 701 "Abstract method '"+tostring(obj)+"' must not have the ACC_FINAL modifier set."); 702 } 703 if (obj.isNative()) { 704 throw new ClassConstraintException( 705 "Abstract method '"+tostring(obj)+"' must not have the ACC_NATIVE modifier set."); 706 } 707 if (obj.isPrivate()) { 708 throw new ClassConstraintException( 709 "Abstract method '"+tostring(obj)+"' must not have the ACC_PRIVATE modifier set."); 710 } 711 if (obj.isStatic()) { 712 throw new ClassConstraintException( 713 "Abstract method '"+tostring(obj)+"' must not have the ACC_STATIC modifier set."); 714 } 715 if (obj.isStrictfp()) { 716 throw new ClassConstraintException( 717 "Abstract method '"+tostring(obj)+"' must not have the ACC_STRICT modifier set."); 718 } 719 if (obj.isSynchronized()) { 720 throw new ClassConstraintException( 721 "Abstract method '"+tostring(obj)+"' must not have the ACC_SYNCHRONIZED modifier set."); 722 } 723 } 724 725 // A specific instance initialization method... (vmspec2,Page 116). 726 if (name.equals(Const.CONSTRUCTOR_NAME)) { 727 //..may have at most one of ACC_PRIVATE, ACC_PROTECTED, ACC_PUBLIC set: is checked above. 728 //..may also have ACC_STRICT set, but none of the other flags in table 4.5 (vmspec2, page 115) 729 if (obj.isStatic() || 730 obj.isFinal() || 731 obj.isSynchronized() || 732 obj.isNative() || 733 obj.isAbstract()) { 734 throw new ClassConstraintException("Instance initialization method '" + tostring(obj) + "' must not have" + 735 " any of the ACC_STATIC, ACC_FINAL, ACC_SYNCHRONIZED, ACC_NATIVE, ACC_ABSTRACT modifiers set."); 736 } 737 } 738 } 739 else{ // isInterface! 740 if (!name.equals(Const.STATIC_INITIALIZER_NAME)) {//vmspec2, p.116, 2nd paragraph 741 if (jc.getMajor() >= Const.MAJOR_1_8) { 742 if (!(obj.isPublic() ^ obj.isPrivate())) { 743 throw new ClassConstraintException("Interface method '" + tostring(obj) + "' must have" + 744 " exactly one of its ACC_PUBLIC and ACC_PRIVATE modifiers set."); 745 } 746 if (obj.isProtected() 747 || obj.isFinal() 748 || obj.isSynchronized() 749 || obj.isNative()) { 750 throw new ClassConstraintException("Interface method '"+tostring(obj)+ "' must not have" + 751 " any of the ACC_PROTECTED, ACC_FINAL, ACC_SYNCHRONIZED, or ACC_NATIVE modifiers set."); 752 } 753 754 } else { 755 if (!obj.isPublic()) { 756 throw new ClassConstraintException( 757 "Interface method '"+tostring(obj)+"' must have the ACC_PUBLIC modifier set but hasn't!"); 758 } 759 if (!obj.isAbstract()) { 760 throw new ClassConstraintException( 761 "Interface method '"+tostring(obj)+"' must have the ACC_ABSTRACT modifier set but hasn't!"); 762 } 763 if (obj.isPrivate() 764 || obj.isProtected() 765 || obj.isStatic() 766 || obj.isFinal() 767 || obj.isSynchronized() 768 || obj.isNative() 769 || obj.isStrictfp() ) { 770 throw new ClassConstraintException("Interface method '"+tostring(obj)+ "' must not have" + 771 " any of the ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_SYNCHRONIZED,"+ 772 " ACC_NATIVE, ACC_ABSTRACT, ACC_STRICT modifiers set."); 773 } 774 } 775 } 776 } 777 778 if ((obj.getAccessFlags() & 779 ~(Const.ACC_PUBLIC|Const.ACC_PRIVATE|Const.ACC_PROTECTED|Const.ACC_STATIC|Const.ACC_FINAL| 780 Const.ACC_SYNCHRONIZED|Const.ACC_NATIVE|Const.ACC_ABSTRACT|Const.ACC_STRICT)) > 0) { 781 addMessage("Method '"+tostring(obj)+"' has access flag(s) other than"+ 782 " ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL,"+ 783 " ACC_SYNCHRONIZED, ACC_NATIVE, ACC_ABSTRACT, ACC_STRICT set (ignored)."); 784 } 785 786 final String nameanddesc = name+sig; 787 if (method_names_and_desc.contains(nameanddesc)) { 788 throw new ClassConstraintException( 789 "No two methods (like '"+tostring(obj)+"') are allowed have same names and desciptors!"); 790 } 791 method_names_and_desc.add(nameanddesc); 792 793 final Attribute[] atts = obj.getAttributes(); 794 int num_code_atts = 0; 795 for (final Attribute att : atts) { 796 if ((!(att instanceof Code)) && 797 (!(att instanceof ExceptionTable)) && 798 (!(att instanceof Synthetic)) && 799 (!(att instanceof Deprecated))) { 800 addMessage("Attribute '" + tostring(att) + "' as an attribute of Method '" + tostring(obj) + 801 "' is unknown and will therefore be ignored."); 802 } 803 if ((!(att instanceof Code)) && 804 (!(att instanceof ExceptionTable))) { 805 addMessage("Attribute '" + tostring(att) + "' as an attribute of Method '" + tostring(obj) + 806 "' is neither Code nor Exceptions and is therefore only of use for debuggers and such."); 807 } 808 if ((att instanceof Code) && (obj.isNative() || obj.isAbstract())) { 809 throw new ClassConstraintException("Native or abstract methods like '" + tostring(obj) + 810 "' must not have a Code attribute like '" + tostring(att) + "'."); //vmspec2 page120, 4.7.3 811 } 812 if (att instanceof Code) { 813 num_code_atts++; 814 } 815 } 816 if ( !obj.isNative() && !obj.isAbstract() && num_code_atts != 1) { 817 throw new ClassConstraintException("Non-native, non-abstract methods like '"+tostring(obj)+ 818 "' must have exactly one Code attribute (found: "+num_code_atts+")."); 819 } 820 } 821 /////////////////////////////////////////////////////// 822 // ClassFile-structure-ATTRIBUTES (vmspec2 4.1, 4.7) // 823 /////////////////////////////////////////////////////// 824 @Override visitSourceFile(final SourceFile obj)825 public void visitSourceFile(final SourceFile obj) {//vmspec2 4.7.7 826 827 // zero or one SourceFile attr per ClassFile: see visitJavaClass() 828 829 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 830 831 final String name = ((ConstantUtf8) cp.getConstant(obj.getNameIndex())).getBytes(); 832 if (! name.equals("SourceFile")) { 833 throw new ClassConstraintException( 834 "The SourceFile attribute '"+tostring(obj)+"' is not correctly named 'SourceFile' but '"+name+"'."); 835 } 836 837 checkIndex(obj, obj.getSourceFileIndex(), CONST_Utf8); 838 839 final String sourcefilename = ((ConstantUtf8) cp.getConstant(obj.getSourceFileIndex())).getBytes(); //==obj.getSourceFileName() ? 840 final String sourcefilenamelc = sourcefilename.toLowerCase(Locale.ENGLISH); 841 842 if ( (sourcefilename.indexOf('/') != -1) || 843 (sourcefilename.indexOf('\\') != -1) || 844 (sourcefilename.indexOf(':') != -1) || 845 (sourcefilenamelc.lastIndexOf(".java") == -1) ) { 846 addMessage("SourceFile attribute '"+tostring(obj)+ 847 "' has a funny name: remember not to confuse certain parsers working on javap's output. Also, this name ('"+ 848 sourcefilename+"') is considered an unqualified (simple) file name only."); 849 } 850 } 851 @Override visitDeprecated(final Deprecated obj)852 public void visitDeprecated(final Deprecated obj) {//vmspec2 4.7.10 853 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 854 855 final String name = ((ConstantUtf8) cp.getConstant(obj.getNameIndex())).getBytes(); 856 if (! name.equals("Deprecated")) { 857 throw new ClassConstraintException("The Deprecated attribute '"+tostring(obj)+ 858 "' is not correctly named 'Deprecated' but '"+name+"'."); 859 } 860 } 861 @Override visitSynthetic(final Synthetic obj)862 public void visitSynthetic(final Synthetic obj) {//vmspec2 4.7.6 863 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 864 final String name = ((ConstantUtf8) cp.getConstant(obj.getNameIndex())).getBytes(); 865 if (! name.equals("Synthetic")) { 866 throw new ClassConstraintException( 867 "The Synthetic attribute '"+tostring(obj)+"' is not correctly named 'Synthetic' but '"+name+"'."); 868 } 869 } 870 @Override visitInnerClasses(final InnerClasses obj)871 public void visitInnerClasses(final InnerClasses obj) {//vmspec2 4.7.5 872 873 // exactly one InnerClasses attr per ClassFile if some inner class is refernced: see visitJavaClass() 874 875 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 876 877 final String name = ((ConstantUtf8) cp.getConstant(obj.getNameIndex())).getBytes(); 878 if (! name.equals("InnerClasses")) { 879 throw new ClassConstraintException( 880 "The InnerClasses attribute '"+tostring(obj)+"' is not correctly named 'InnerClasses' but '"+name+"'."); 881 } 882 883 final InnerClass[] ics = obj.getInnerClasses(); 884 885 for (final InnerClass ic : ics) { 886 checkIndex(obj, ic.getInnerClassIndex(), CONST_Class); 887 final int outer_idx = ic.getOuterClassIndex(); 888 if (outer_idx != 0) { 889 checkIndex(obj, outer_idx, CONST_Class); 890 } 891 final int innername_idx = ic.getInnerNameIndex(); 892 if (innername_idx != 0) { 893 checkIndex(obj, innername_idx, CONST_Utf8); 894 } 895 int acc = ic.getInnerAccessFlags(); 896 acc = acc & (~ (Const.ACC_PUBLIC | Const.ACC_PRIVATE | Const.ACC_PROTECTED | 897 Const.ACC_STATIC | Const.ACC_FINAL | Const.ACC_INTERFACE | Const.ACC_ABSTRACT)); 898 if (acc != 0) { 899 addMessage( 900 "Unknown access flag for inner class '"+tostring(ic)+"' set (InnerClasses attribute '"+tostring(obj)+"')."); 901 } 902 } 903 // Semantical consistency is not yet checked by Sun, see vmspec2 4.7.5. 904 // [marked TODO in JustIce] 905 } 906 //////////////////////////////////////////////////////// 907 // field_info-structure-ATTRIBUTES (vmspec2 4.5, 4.7) // 908 //////////////////////////////////////////////////////// 909 @Override visitConstantValue(final ConstantValue obj)910 public void visitConstantValue(final ConstantValue obj) {//vmspec2 4.7.2 911 // Despite its name, this really is an Attribute, 912 // not a constant! 913 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 914 915 final String name = ((ConstantUtf8) cp.getConstant(obj.getNameIndex())).getBytes(); 916 if (! name.equals("ConstantValue")) { 917 throw new ClassConstraintException( 918 "The ConstantValue attribute '"+tostring(obj)+"' is not correctly named 'ConstantValue' but '"+name+"'."); 919 } 920 921 final Object pred = carrier.predecessor(); 922 if (pred instanceof Field) { //ConstantValue attributes are quite senseless if the predecessor is not a field. 923 final Field f = (Field) pred; 924 // Field constraints have been checked before -- so we are safe using their type information. 925 final Type field_type = Type.getType(((ConstantUtf8) (cp.getConstant(f.getSignatureIndex()))).getBytes()); 926 927 final int index = obj.getConstantValueIndex(); 928 if ((index < 0) || (index >= cplen)) { 929 throw new ClassConstraintException("Invalid index '"+index+"' used by '"+tostring(obj)+"'."); 930 } 931 final Constant c = cp.getConstant(index); 932 933 if (CONST_Long.isInstance(c) && field_type.equals(Type.LONG)) { 934 return; 935 } 936 if (CONST_Float.isInstance(c) && field_type.equals(Type.FLOAT)) { 937 return; 938 } 939 if (CONST_Double.isInstance(c) && field_type.equals(Type.DOUBLE)) { 940 return; 941 } 942 if (CONST_Integer.isInstance(c) && (field_type.equals(Type.INT) || field_type.equals(Type.SHORT) || 943 field_type.equals(Type.CHAR) || field_type.equals(Type.BYTE) || field_type.equals(Type.BOOLEAN))) { 944 return; 945 } 946 if (CONST_String.isInstance(c) && field_type.equals(Type.STRING)) { 947 return; 948 } 949 950 throw new ClassConstraintException("Illegal type of ConstantValue '"+obj+"' embedding Constant '"+c+ 951 "'. It is referenced by field '"+tostring(f)+"' expecting a different type: '"+field_type+"'."); 952 } 953 } 954 // SYNTHETIC: see above 955 // DEPRECATED: see above 956 ///////////////////////////////////////////////////////// 957 // method_info-structure-ATTRIBUTES (vmspec2 4.6, 4.7) // 958 ///////////////////////////////////////////////////////// 959 @Override visitCode(final Code obj)960 public void visitCode(final Code obj) {//vmspec2 4.7.3 961 try { 962 // No code attribute allowed for native or abstract methods: see visitMethod(Method). 963 // Code array constraints are checked in Pass3 (3a and 3b). 964 965 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 966 967 final String name = ((ConstantUtf8) cp.getConstant(obj.getNameIndex())).getBytes(); 968 if (! name.equals("Code")) { 969 throw new ClassConstraintException( 970 "The Code attribute '"+tostring(obj)+"' is not correctly named 'Code' but '"+name+"'."); 971 } 972 973 Method m = null; // satisfy compiler 974 if (!(carrier.predecessor() instanceof Method)) { 975 addMessage("Code attribute '"+tostring(obj)+"' is not declared in a method_info structure but in '"+ 976 carrier.predecessor()+"'. Ignored."); 977 return; 978 } 979 m = (Method) carrier.predecessor(); // we can assume this method was visited before; 980 // i.e. the data consistency was verified. 981 982 if (obj.getCode().length == 0) { 983 throw new ClassConstraintException( 984 "Code array of Code attribute '"+tostring(obj)+"' (method '"+m+"') must not be empty."); 985 } 986 987 //In JustIce, the check for correct offsets into the code array is delayed to Pass 3a. 988 final CodeException[] exc_table = obj.getExceptionTable(); 989 for (final CodeException element : exc_table) { 990 final int exc_index = element.getCatchType(); 991 if (exc_index != 0) { // if 0, it catches all Throwables 992 checkIndex(obj, exc_index, CONST_Class); 993 final ConstantClass cc = (ConstantClass) (cp.getConstant(exc_index)); 994 // cannot be sure this ConstantClass has already been visited (checked)! 995 checkIndex(cc, cc.getNameIndex(), CONST_Utf8); 996 final String cname = ((ConstantUtf8) cp.getConstant(cc.getNameIndex())).getBytes().replace('/','.'); 997 998 Verifier v = VerifierFactory.getVerifier(cname); 999 VerificationResult vr = v.doPass1(); 1000 1001 if (vr != VerificationResult.VR_OK) { 1002 throw new ClassConstraintException("Code attribute '"+tostring(obj)+"' (method '"+m+ 1003 "') has an exception_table entry '"+tostring(element)+"' that references '"+cname+ 1004 "' as an Exception but it does not pass verification pass 1: "+vr); 1005 } 1006 // We cannot safely trust any other "instanceof" mechanism. We need to transitively verify 1007 // the ancestor hierarchy. 1008 JavaClass e = Repository.lookupClass(cname); 1009 final JavaClass t = Repository.lookupClass(Type.THROWABLE.getClassName()); 1010 final JavaClass o = Repository.lookupClass(Type.OBJECT.getClassName()); 1011 while (e != o) { 1012 if (e == t) { 1013 break; // It's a subclass of Throwable, OKAY, leave. 1014 } 1015 1016 v = VerifierFactory.getVerifier(e.getSuperclassName()); 1017 vr = v.doPass1(); 1018 if (vr != VerificationResult.VR_OK) { 1019 throw new ClassConstraintException("Code attribute '"+tostring(obj)+"' (method '"+m+ 1020 "') has an exception_table entry '"+tostring(element)+"' that references '"+cname+ 1021 "' as an Exception but '"+e.getSuperclassName()+ 1022 "' in the ancestor hierachy does not pass verification pass 1: "+vr); 1023 } 1024 e = Repository.lookupClass(e.getSuperclassName()); 1025 } 1026 if (e != t) { 1027 throw new ClassConstraintException("Code attribute '"+tostring(obj)+"' (method '"+m+ 1028 "') has an exception_table entry '"+tostring(element)+"' that references '"+cname+ 1029 "' as an Exception but it is not a subclass of '"+t.getClassName()+"'."); 1030 } 1031 } 1032 } 1033 1034 // Create object for local variables information 1035 // This is highly unelegant due to usage of the Visitor pattern. 1036 // TODO: rework it. 1037 int method_number = -1; 1038 final Method[] ms = Repository.lookupClass(myOwner.getClassName()).getMethods(); 1039 for (int mn=0; mn<ms.length; mn++) { 1040 if (m == ms[mn]) { 1041 method_number = mn; 1042 break; 1043 } 1044 } 1045 if (method_number < 0) { // Mmmmh. Can we be sure BCEL does not sometimes instantiate new objects? 1046 throw new AssertionViolatedException( 1047 "Could not find a known BCEL Method object in the corresponding BCEL JavaClass object."); 1048 } 1049 localVariablesInfos[method_number] = new LocalVariablesInfo(obj.getMaxLocals()); 1050 1051 int num_of_lvt_attribs = 0; 1052 // Now iterate through the attributes the Code attribute has. 1053 final Attribute[] atts = obj.getAttributes(); 1054 for (int a=0; a<atts.length; a++) { 1055 if ((! (atts[a] instanceof LineNumberTable)) && 1056 (! (atts[a] instanceof LocalVariableTable))) { 1057 addMessage("Attribute '"+tostring(atts[a])+"' as an attribute of Code attribute '"+tostring(obj)+ 1058 "' (method '"+m+"') is unknown and will therefore be ignored."); 1059 } 1060 else{// LineNumberTable or LocalVariableTable 1061 addMessage("Attribute '"+tostring(atts[a])+"' as an attribute of Code attribute '"+tostring(obj)+ 1062 "' (method '"+m+"') will effectively be ignored and is only useful for debuggers and such."); 1063 } 1064 1065 //LocalVariableTable check (partially delayed to Pass3a). 1066 //Here because its easier to collect the information of the 1067 //(possibly more than one) LocalVariableTables belonging to 1068 //one certain Code attribute. 1069 if (atts[a] instanceof LocalVariableTable) { // checks conforming to vmspec2 4.7.9 1070 1071 final LocalVariableTable lvt = (LocalVariableTable) atts[a]; 1072 1073 checkIndex(lvt, lvt.getNameIndex(), CONST_Utf8); 1074 1075 final String lvtname = ((ConstantUtf8) cp.getConstant(lvt.getNameIndex())).getBytes(); 1076 if (! lvtname.equals("LocalVariableTable")) { 1077 throw new ClassConstraintException("The LocalVariableTable attribute '"+tostring(lvt)+ 1078 "' is not correctly named 'LocalVariableTable' but '"+lvtname+"'."); 1079 } 1080 1081 final Code code = obj; 1082 1083 //In JustIce, the check for correct offsets into the code array is delayed to Pass 3a. 1084 final LocalVariable[] localvariables = lvt.getLocalVariableTable(); 1085 1086 for (final LocalVariable localvariable : localvariables) { 1087 checkIndex(lvt, localvariable.getNameIndex(), CONST_Utf8); 1088 final String localname = ((ConstantUtf8) cp.getConstant(localvariable.getNameIndex())).getBytes(); 1089 if (!validJavaIdentifier(localname)) { 1090 throw new ClassConstraintException("LocalVariableTable '"+tostring(lvt)+ 1091 "' references a local variable by the name '"+localname+"' which is not a legal Java simple name."); 1092 } 1093 1094 checkIndex(lvt, localvariable.getSignatureIndex(), CONST_Utf8); 1095 final String localsig = 1096 ((ConstantUtf8) (cp.getConstant(localvariable.getSignatureIndex()))).getBytes(); // Local sig.(=descriptor) 1097 Type t; 1098 try{ 1099 t = Type.getType(localsig); 1100 } 1101 catch (final ClassFormatException cfe) { 1102 throw new ClassConstraintException("Illegal descriptor (==signature) '"+localsig+ 1103 "' used by LocalVariable '"+tostring(localvariable)+"' referenced by '"+tostring(lvt)+"'.", cfe); 1104 } 1105 final int localindex = localvariable.getIndex(); 1106 if ( ( (t==Type.LONG || t==Type.DOUBLE)? localindex+1:localindex) >= code.getMaxLocals()) { 1107 throw new ClassConstraintException("LocalVariableTable attribute '"+tostring(lvt)+ 1108 "' references a LocalVariable '"+tostring(localvariable)+ 1109 "' with an index that exceeds the surrounding Code attribute's max_locals value of '"+ 1110 code.getMaxLocals()+"'."); 1111 } 1112 1113 try{ 1114 localVariablesInfos[method_number].add(localindex, localname, localvariable.getStartPC(), 1115 localvariable.getLength(), t); 1116 } 1117 catch(final LocalVariableInfoInconsistentException lviie) { 1118 throw new ClassConstraintException("Conflicting information in LocalVariableTable '"+tostring(lvt)+ 1119 "' found in Code attribute '"+tostring(obj)+ 1120 "' (method '"+tostring(m)+"'). "+lviie.getMessage(), lviie); 1121 } 1122 }// for all local variables localvariables[i] in the LocalVariableTable attribute atts[a] END 1123 1124 num_of_lvt_attribs++; 1125 if (!m.isStatic() && num_of_lvt_attribs > obj.getMaxLocals()) { 1126 throw new ClassConstraintException("Number of LocalVariableTable attributes of Code attribute '"+ 1127 tostring(obj)+"' (method '"+tostring(m)+"') exceeds number of local variable slots '"+obj.getMaxLocals()+ 1128 "' ('There may be at most one LocalVariableTable attribute per local variable in the Code attribute.')."); 1129 } 1130 }// if atts[a] instanceof LocalVariableTable END 1131 }// for all attributes atts[a] END 1132 1133 } catch (final ClassNotFoundException e) { 1134 // FIXME: this might not be the best way to handle missing classes. 1135 throw new AssertionViolatedException("Missing class: " + e, e); 1136 } 1137 1138 }// visitCode(Code) END 1139 1140 @Override visitExceptionTable(final ExceptionTable obj)1141 public void visitExceptionTable(final ExceptionTable obj) {//vmspec2 4.7.4 1142 try { 1143 // incorrectly named, it's the Exceptions attribute (vmspec2 4.7.4) 1144 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 1145 1146 final String name = ((ConstantUtf8) cp.getConstant(obj.getNameIndex())).getBytes(); 1147 if (! name.equals("Exceptions")) { 1148 throw new ClassConstraintException( 1149 "The Exceptions attribute '"+tostring(obj)+"' is not correctly named 'Exceptions' but '"+name+"'."); 1150 } 1151 1152 final int[] exc_indices = obj.getExceptionIndexTable(); 1153 1154 for (final int exc_indice : exc_indices) { 1155 checkIndex(obj, exc_indice, CONST_Class); 1156 1157 final ConstantClass cc = (ConstantClass) (cp.getConstant(exc_indice)); 1158 checkIndex(cc, cc.getNameIndex(), CONST_Utf8); // can't be sure this ConstantClass has already been visited (checked)! 1159 //convert internal notation on-the-fly to external notation: 1160 final String cname = ((ConstantUtf8) cp.getConstant(cc.getNameIndex())).getBytes().replace('/','.'); 1161 1162 Verifier v = VerifierFactory.getVerifier(cname); 1163 VerificationResult vr = v.doPass1(); 1164 1165 if (vr != VerificationResult.VR_OK) { 1166 throw new ClassConstraintException("Exceptions attribute '"+tostring(obj)+"' references '"+cname+ 1167 "' as an Exception but it does not pass verification pass 1: "+vr); 1168 } 1169 // We cannot safely trust any other "instanceof" mechanism. We need to transitively verify 1170 // the ancestor hierarchy. 1171 JavaClass e = Repository.lookupClass(cname); 1172 final JavaClass t = Repository.lookupClass(Type.THROWABLE.getClassName()); 1173 final JavaClass o = Repository.lookupClass(Type.OBJECT.getClassName()); 1174 while (e != o) { 1175 if (e == t) { 1176 break; // It's a subclass of Throwable, OKAY, leave. 1177 } 1178 1179 v = VerifierFactory.getVerifier(e.getSuperclassName()); 1180 vr = v.doPass1(); 1181 if (vr != VerificationResult.VR_OK) { 1182 throw new ClassConstraintException("Exceptions attribute '"+tostring(obj)+"' references '"+cname+ 1183 "' as an Exception but '"+e.getSuperclassName()+ 1184 "' in the ancestor hierachy does not pass verification pass 1: "+vr); 1185 } 1186 e = Repository.lookupClass(e.getSuperclassName()); 1187 } 1188 if (e != t) { 1189 throw new ClassConstraintException("Exceptions attribute '"+tostring(obj)+"' references '"+cname+ 1190 "' as an Exception but it is not a subclass of '"+t.getClassName()+"'."); 1191 } 1192 } 1193 1194 } catch (final ClassNotFoundException e) { 1195 // FIXME: this might not be the best way to handle missing classes. 1196 throw new AssertionViolatedException("Missing class: " + e, e); 1197 } 1198 } 1199 // SYNTHETIC: see above 1200 // DEPRECATED: see above 1201 ////////////////////////////////////////////////////////////// 1202 // code_attribute-structure-ATTRIBUTES (vmspec2 4.7.3, 4.7) // 1203 ////////////////////////////////////////////////////////////// 1204 @Override visitLineNumberTable(final LineNumberTable obj)1205 public void visitLineNumberTable(final LineNumberTable obj) {//vmspec2 4.7.8 1206 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 1207 1208 final String name = ((ConstantUtf8) cp.getConstant(obj.getNameIndex())).getBytes(); 1209 if (! name.equals("LineNumberTable")) { 1210 throw new ClassConstraintException("The LineNumberTable attribute '"+tostring(obj)+ 1211 "' is not correctly named 'LineNumberTable' but '"+name+"'."); 1212 } 1213 1214 //In JustIce,this check is delayed to Pass 3a. 1215 //LineNumber[] linenumbers = obj.getLineNumberTable(); 1216 // ...validity check... 1217 1218 } 1219 @Override visitLocalVariableTable(final LocalVariableTable obj)1220 public void visitLocalVariableTable(final LocalVariableTable obj) {//vmspec2 4.7.9 1221 //In JustIce,this check is partially delayed to Pass 3a. 1222 //The other part can be found in the visitCode(Code) method. 1223 } 1224 //////////////////////////////////////////////////// 1225 // MISC-structure-ATTRIBUTES (vmspec2 4.7.1, 4.7) // 1226 //////////////////////////////////////////////////// 1227 @Override visitUnknown(final Unknown obj)1228 public void visitUnknown(final Unknown obj) {//vmspec2 4.7.1 1229 // Represents an unknown attribute. 1230 checkIndex(obj, obj.getNameIndex(), CONST_Utf8); 1231 1232 // Maybe only misnamed? Give a (warning) message. 1233 addMessage("Unknown attribute '"+tostring(obj)+"'. This attribute is not known in any context!"); 1234 } 1235 ////////// 1236 // BCEL // 1237 ////////// 1238 @Override visitLocalVariable(final LocalVariable obj)1239 public void visitLocalVariable(final LocalVariable obj) { 1240 // This does not represent an Attribute but is only 1241 // related to internal BCEL data representation. 1242 1243 // see visitLocalVariableTable(LocalVariableTable) 1244 } 1245 @Override visitCodeException(final CodeException obj)1246 public void visitCodeException(final CodeException obj) { 1247 // Code constraints are checked in Pass3 (3a and 3b). 1248 // This does not represent an Attribute but is only 1249 // related to internal BCEL data representation. 1250 1251 // see visitCode(Code) 1252 } 1253 @Override visitConstantPool(final ConstantPool obj)1254 public void visitConstantPool(final ConstantPool obj) { 1255 // No need to. We're piggybacked by the DescendingVisitor. 1256 // This does not represent an Attribute but is only 1257 // related to internal BCEL data representation. 1258 } 1259 @Override visitInnerClass(final InnerClass obj)1260 public void visitInnerClass(final InnerClass obj) { 1261 // This does not represent an Attribute but is only 1262 // related to internal BCEL data representation. 1263 } 1264 @Override visitLineNumber(final LineNumber obj)1265 public void visitLineNumber(final LineNumber obj) { 1266 // This does not represent an Attribute but is only 1267 // related to internal BCEL data representation. 1268 1269 // see visitLineNumberTable(LineNumberTable) 1270 } 1271 } 1272 1273 /** 1274 * Ensures that the ConstantCP-subclassed entries of the constant 1275 * pool are valid. According to "Yellin: Low Level Security in Java", 1276 * this method does not verify the existence of referenced entities 1277 * (such as classes) but only the formal correctness (such as well-formed 1278 * signatures). 1279 * The visitXXX() methods throw ClassConstraintException instances otherwise. 1280 * <B>Precondition: index-style cross referencing in the constant 1281 * pool must be valid. Simply invoke constant_pool_entries_satisfy_static_constraints() 1282 * before.</B> 1283 * 1284 * @throws ClassConstraintException otherwise. 1285 * @see #constant_pool_entries_satisfy_static_constraints() 1286 */ field_and_method_refs_are_valid()1287 private void field_and_method_refs_are_valid() { 1288 try { 1289 final JavaClass jc = Repository.lookupClass(myOwner.getClassName()); 1290 final DescendingVisitor v = new DescendingVisitor(jc, new FAMRAV_Visitor(jc)); 1291 v.visit(); 1292 1293 } catch (final ClassNotFoundException e) { 1294 // FIXME: this might not be the best way to handle missing classes. 1295 throw new AssertionViolatedException("Missing class: " + e, e); 1296 } 1297 } 1298 1299 /** 1300 * A Visitor class that ensures the ConstantCP-subclassed entries 1301 * of the constant pool are valid. 1302 * <B>Precondition: index-style cross referencing in the constant 1303 * pool must be valid.</B> 1304 * 1305 * @see #constant_pool_entries_satisfy_static_constraints() 1306 * @see org.apache.bcel.classfile.ConstantCP 1307 */ 1308 private final class FAMRAV_Visitor extends EmptyVisitor{ 1309 private final ConstantPool cp; // ==jc.getConstantPool() -- only here to save typing work. FAMRAV_Visitor(final JavaClass _jc)1310 private FAMRAV_Visitor(final JavaClass _jc) { 1311 cp = _jc.getConstantPool(); 1312 } 1313 1314 @Override visitConstantFieldref(final ConstantFieldref obj)1315 public void visitConstantFieldref(final ConstantFieldref obj) { 1316 if (obj.getTag() != Const.CONSTANT_Fieldref) { 1317 throw new ClassConstraintException("ConstantFieldref '"+tostring(obj)+"' has wrong tag!"); 1318 } 1319 final int name_and_type_index = obj.getNameAndTypeIndex(); 1320 final ConstantNameAndType cnat = (ConstantNameAndType) (cp.getConstant(name_and_type_index)); 1321 final String name = ((ConstantUtf8) (cp.getConstant(cnat.getNameIndex()))).getBytes(); // Field or Method name 1322 if (!validFieldName(name)) { 1323 throw new ClassConstraintException("Invalid field name '"+name+"' referenced by '"+tostring(obj)+"'."); 1324 } 1325 1326 final int class_index = obj.getClassIndex(); 1327 final ConstantClass cc = (ConstantClass) (cp.getConstant(class_index)); 1328 final String className = ((ConstantUtf8) (cp.getConstant(cc.getNameIndex()))).getBytes(); // Class Name in internal form 1329 if (! validClassName(className)) { 1330 throw new ClassConstraintException("Illegal class name '"+className+"' used by '"+tostring(obj)+"'."); 1331 } 1332 1333 final String sig = ((ConstantUtf8) (cp.getConstant(cnat.getSignatureIndex()))).getBytes(); // Field or Method sig.(=descriptor) 1334 1335 try{ 1336 Type.getType(sig); /* Don't need the return value */ 1337 } 1338 catch (final ClassFormatException cfe) { 1339 throw new ClassConstraintException("Illegal descriptor (==signature) '"+sig+"' used by '"+tostring(obj)+"'.", cfe); 1340 } 1341 } 1342 1343 @Override visitConstantMethodref(final ConstantMethodref obj)1344 public void visitConstantMethodref(final ConstantMethodref obj) { 1345 if (obj.getTag() != Const.CONSTANT_Methodref) { 1346 throw new ClassConstraintException("ConstantMethodref '"+tostring(obj)+"' has wrong tag!"); 1347 } 1348 final int name_and_type_index = obj.getNameAndTypeIndex(); 1349 final ConstantNameAndType cnat = (ConstantNameAndType) (cp.getConstant(name_and_type_index)); 1350 final String name = ((ConstantUtf8) (cp.getConstant(cnat.getNameIndex()))).getBytes(); // Field or Method name 1351 if (!validClassMethodName(name)) { 1352 throw new ClassConstraintException( 1353 "Invalid (non-interface) method name '"+name+"' referenced by '"+tostring(obj)+"'."); 1354 } 1355 1356 final int class_index = obj.getClassIndex(); 1357 final ConstantClass cc = (ConstantClass) (cp.getConstant(class_index)); 1358 final String className = ((ConstantUtf8) (cp.getConstant(cc.getNameIndex()))).getBytes(); // Class Name in internal form 1359 if (! validClassName(className)) { 1360 throw new ClassConstraintException("Illegal class name '"+className+"' used by '"+tostring(obj)+"'."); 1361 } 1362 1363 final String sig = ((ConstantUtf8) (cp.getConstant(cnat.getSignatureIndex()))).getBytes(); // Field or Method sig.(=descriptor) 1364 1365 try{ 1366 final Type t = Type.getReturnType(sig); 1367 if ( name.equals(Const.CONSTRUCTOR_NAME) && (t != Type.VOID) ) { 1368 throw new ClassConstraintException("Instance initialization method must have VOID return type."); 1369 } 1370 } 1371 catch (final ClassFormatException cfe) { 1372 throw new ClassConstraintException("Illegal descriptor (==signature) '"+sig+"' used by '"+tostring(obj)+"'.", cfe); 1373 } 1374 } 1375 1376 @Override visitConstantInterfaceMethodref(final ConstantInterfaceMethodref obj)1377 public void visitConstantInterfaceMethodref(final ConstantInterfaceMethodref obj) { 1378 if (obj.getTag() != Const.CONSTANT_InterfaceMethodref) { 1379 throw new ClassConstraintException("ConstantInterfaceMethodref '"+tostring(obj)+"' has wrong tag!"); 1380 } 1381 final int name_and_type_index = obj.getNameAndTypeIndex(); 1382 final ConstantNameAndType cnat = (ConstantNameAndType) (cp.getConstant(name_and_type_index)); 1383 final String name = ((ConstantUtf8) (cp.getConstant(cnat.getNameIndex()))).getBytes(); // Field or Method name 1384 if (!validInterfaceMethodName(name)) { 1385 throw new ClassConstraintException("Invalid (interface) method name '"+name+"' referenced by '"+tostring(obj)+"'."); 1386 } 1387 1388 final int class_index = obj.getClassIndex(); 1389 final ConstantClass cc = (ConstantClass) (cp.getConstant(class_index)); 1390 final String className = ((ConstantUtf8) (cp.getConstant(cc.getNameIndex()))).getBytes(); // Class Name in internal form 1391 if (! validClassName(className)) { 1392 throw new ClassConstraintException("Illegal class name '"+className+"' used by '"+tostring(obj)+"'."); 1393 } 1394 1395 final String sig = ((ConstantUtf8) (cp.getConstant(cnat.getSignatureIndex()))).getBytes(); // Field or Method sig.(=descriptor) 1396 1397 try{ 1398 final Type t = Type.getReturnType(sig); 1399 if ( name.equals(Const.STATIC_INITIALIZER_NAME) && (t != Type.VOID) ) { 1400 addMessage("Class or interface initialization method '"+Const.STATIC_INITIALIZER_NAME+ 1401 "' usually has VOID return type instead of '"+t+ 1402 "'. Note this is really not a requirement of The Java Virtual Machine Specification, Second Edition."); 1403 } 1404 } 1405 catch (final ClassFormatException cfe) { 1406 throw new ClassConstraintException("Illegal descriptor (==signature) '"+sig+"' used by '"+tostring(obj)+"'.", cfe); 1407 } 1408 1409 } 1410 1411 } 1412 1413 /** 1414 * This method returns true if and only if the supplied String 1415 * represents a valid Java class name. 1416 */ validClassName(final String name)1417 private static boolean validClassName(final String name) { 1418 /* 1419 * TODO: implement. 1420 * Are there any restrictions? 1421 */ 1422 return true; 1423 } 1424 /** 1425 * This method returns true if and only if the supplied String 1426 * represents a valid method name. 1427 * This is basically the same as a valid identifier name in the 1428 * Java programming language, but the special name for 1429 * the instance initialization method is allowed and the special name 1430 * for the class/interface initialization method may be allowed. 1431 */ validMethodName(final String name, final boolean allowStaticInit)1432 private static boolean validMethodName(final String name, final boolean allowStaticInit) { 1433 if (validJavaLangMethodName(name)) { 1434 return true; 1435 } 1436 1437 if (allowStaticInit) { 1438 return name.equals(Const.CONSTRUCTOR_NAME) || name.equals(Const.STATIC_INITIALIZER_NAME); 1439 } 1440 return name.equals(Const.CONSTRUCTOR_NAME); 1441 } 1442 1443 /** 1444 * This method returns true if and only if the supplied String 1445 * represents a valid method name that may be referenced by 1446 * ConstantMethodref objects. 1447 */ validClassMethodName(final String name)1448 private static boolean validClassMethodName(final String name) { 1449 return validMethodName(name, false); 1450 } 1451 1452 /** 1453 * This method returns true if and only if the supplied String 1454 * represents a valid Java programming language method name stored as a simple 1455 * (non-qualified) name. 1456 * Conforming to: The Java Virtual Machine Specification, Second Edition, �2.7, �2.7.1, �2.2. 1457 */ validJavaLangMethodName(final String name)1458 private static boolean validJavaLangMethodName(final String name) { 1459 if (!Character.isJavaIdentifierStart(name.charAt(0))) { 1460 return false; 1461 } 1462 1463 for (int i=1; i<name.length(); i++) { 1464 if (!Character.isJavaIdentifierPart(name.charAt(i))) { 1465 return false; 1466 } 1467 } 1468 return true; 1469 } 1470 1471 /** 1472 * This method returns true if and only if the supplied String 1473 * represents a valid Java interface method name that may be 1474 * referenced by ConstantInterfaceMethodref objects. 1475 */ validInterfaceMethodName(final String name)1476 private static boolean validInterfaceMethodName(final String name) { 1477 // I guess we should assume special names forbidden here. 1478 if (name.startsWith("<")) { 1479 return false; 1480 } 1481 return validJavaLangMethodName(name); 1482 } 1483 1484 /** 1485 * This method returns true if and only if the supplied String 1486 * represents a valid Java identifier (so-called simple name). 1487 */ validJavaIdentifier(final String name)1488 private static boolean validJavaIdentifier(final String name) { 1489 if (name.length() == 0) { 1490 return false; // must not be empty, reported by <francis.andre@easynet.fr>, thanks! 1491 } 1492 1493 // vmspec2 2.7, vmspec2 2.2 1494 if (!Character.isJavaIdentifierStart(name.charAt(0))) { 1495 return false; 1496 } 1497 1498 for (int i=1; i<name.length(); i++) { 1499 if (!Character.isJavaIdentifierPart(name.charAt(i))) { 1500 return false; 1501 } 1502 } 1503 return true; 1504 } 1505 1506 /** 1507 * This method returns true if and only if the supplied String 1508 * represents a valid Java field name. 1509 */ validFieldName(final String name)1510 private static boolean validFieldName(final String name) { 1511 // vmspec2 2.7, vmspec2 2.2 1512 return validJavaIdentifier(name); 1513 } 1514 1515 /** 1516 * This class serves for finding out if a given JavaClass' ConstantPool 1517 * references an Inner Class. 1518 * The Java Virtual Machine Specification, Second Edition is not very precise 1519 * about when an "InnerClasses" attribute has to appear. However, it states that 1520 * there has to be exactly one InnerClasses attribute in the ClassFile structure 1521 * if the constant pool of a class or interface refers to any class or interface 1522 * "that is not a member of a package". Sun does not mean "member of the default 1523 * package". In "Inner Classes Specification" they point out how a "bytecode name" 1524 * is derived so one has to deduce what a class name of a class "that is not a 1525 * member of a package" looks like: there is at least one character in the byte- 1526 * code name that cannot be part of a legal Java Language Class name (and not equal 1527 * to '/'). This assumption is wrong as the delimiter is '$' for which 1528 * Character.isJavaIdentifierPart() == true. 1529 * Hence, you really run into trouble if you have a toplevel class called 1530 * "A$XXX" and another toplevel class called "A" with in inner class called "XXX". 1531 * JustIce cannot repair this; please note that existing verifiers at this 1532 * time even fail to detect missing InnerClasses attributes in pass 2. 1533 */ 1534 private static class InnerClassDetector extends EmptyVisitor{ 1535 private boolean hasInnerClass = false; 1536 private final JavaClass jc; 1537 private final ConstantPool cp; 1538 1539 /** Constructs an InnerClassDetector working on the JavaClass _jc. */ InnerClassDetector(final JavaClass _jc)1540 public InnerClassDetector(final JavaClass _jc) { 1541 jc = _jc; 1542 cp = jc.getConstantPool(); 1543 (new DescendingVisitor(jc, this)).visit(); 1544 } 1545 /** 1546 * Returns if the JavaClass this InnerClassDetector is working on 1547 * has an Inner Class reference in its constant pool. 1548 */ innerClassReferenced()1549 public boolean innerClassReferenced() { 1550 return hasInnerClass; 1551 } 1552 /** This method casually visits ConstantClass references. */ 1553 @Override visitConstantClass(final ConstantClass obj)1554 public void visitConstantClass(final ConstantClass obj) { 1555 final Constant c = cp.getConstant(obj.getNameIndex()); 1556 if (c instanceof ConstantUtf8) { //Ignore the case where it's not a ConstantUtf8 here, we'll find out later. 1557 final String classname = ((ConstantUtf8) c).getBytes(); 1558 if (classname.startsWith(jc.getClassName().replace('.','/')+"$")) { 1559 hasInnerClass = true; 1560 } 1561 } 1562 } 1563 } 1564 1565 /** 1566 * This method is here to save typing work and improve code readability. 1567 */ tostring(final Node n)1568 private static String tostring(final Node n) { 1569 return new StringRepresentation(n).toString(); 1570 } 1571 } 1572