View Javadoc
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  package org.apache.bcel.verifier.statics;
18  
19  import java.util.HashMap;
20  import java.util.HashSet;
21  import java.util.Locale;
22  import java.util.Map;
23  import java.util.Objects;
24  import java.util.Set;
25  
26  import org.apache.bcel.Const;
27  import org.apache.bcel.Constants;
28  import org.apache.bcel.Repository;
29  import org.apache.bcel.classfile.Attribute;
30  import org.apache.bcel.classfile.ClassFormatException;
31  import org.apache.bcel.classfile.Code;
32  import org.apache.bcel.classfile.CodeException;
33  import org.apache.bcel.classfile.Constant;
34  import org.apache.bcel.classfile.ConstantClass;
35  import org.apache.bcel.classfile.ConstantDouble;
36  import org.apache.bcel.classfile.ConstantFieldref;
37  import org.apache.bcel.classfile.ConstantFloat;
38  import org.apache.bcel.classfile.ConstantInteger;
39  import org.apache.bcel.classfile.ConstantInterfaceMethodref;
40  import org.apache.bcel.classfile.ConstantLong;
41  import org.apache.bcel.classfile.ConstantMethodref;
42  import org.apache.bcel.classfile.ConstantNameAndType;
43  import org.apache.bcel.classfile.ConstantPool;
44  import org.apache.bcel.classfile.ConstantString;
45  import org.apache.bcel.classfile.ConstantUtf8;
46  import org.apache.bcel.classfile.ConstantValue;
47  import org.apache.bcel.classfile.Deprecated;
48  import org.apache.bcel.classfile.DescendingVisitor;
49  import org.apache.bcel.classfile.EmptyVisitor;
50  import org.apache.bcel.classfile.ExceptionTable;
51  import org.apache.bcel.classfile.Field;
52  import org.apache.bcel.classfile.InnerClass;
53  import org.apache.bcel.classfile.InnerClasses;
54  import org.apache.bcel.classfile.JavaClass;
55  import org.apache.bcel.classfile.LineNumber;
56  import org.apache.bcel.classfile.LineNumberTable;
57  import org.apache.bcel.classfile.LocalVariable;
58  import org.apache.bcel.classfile.LocalVariableTable;
59  import org.apache.bcel.classfile.Method;
60  import org.apache.bcel.classfile.Node;
61  import org.apache.bcel.classfile.SourceFile;
62  import org.apache.bcel.classfile.Synthetic;
63  import org.apache.bcel.classfile.Unknown;
64  import org.apache.bcel.classfile.Utility;
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 pass 2 as described in The Java Virtual Machine Specification,
78   * 2nd edition. More detailed information is to be found at the do_verify() method's documentation.
79   *
80   * @see #do_verify()
81   */
82  public final class Pass2Verifier extends PassVerifier implements Constants {
83  
84      /**
85       * A Visitor class that ensures the constant pool satisfies the static constraints. The visitXXX() methods throw
86       * ClassConstraintException instances otherwise.
87       *
88       * @see #constantPoolEntriesSatisfyStaticConstraints()
89       */
90      private final class CPESSC_Visitor extends EmptyVisitor {
91          private final Class<?> CONST_Class;
92  
93          /*
94           * private Class<?> CONST_Fieldref; private Class<?> CONST_Methodref; private Class<?> CONST_InterfaceMethodref;
95           */
96          private final Class<?> CONST_String;
97          private final Class<?> CONST_Integer;
98          private final Class<?> CONST_Float;
99          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 (this.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 }