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