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