001: /***
002: * ASM: a very small and fast Java bytecode manipulation framework
003: * Copyright (c) 2000-2005 INRIA, France Telecom
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: * 2. Redistributions in binary form must reproduce the above copyright
012: * notice, this list of conditions and the following disclaimer in the
013: * documentation and/or other materials provided with the distribution.
014: * 3. Neither the name of the copyright holders nor the names of its
015: * contributors may be used to endorse or promote products derived from
016: * this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
028: * THE POSSIBILITY OF SUCH DAMAGE.
029: */package org.ejb3unit.asm.util;
030:
031: import java.io.FileInputStream;
032: import java.io.PrintWriter;
033: import java.util.List;
034:
035: import org.ejb3unit.asm.AnnotationVisitor;
036: import org.ejb3unit.asm.Attribute;
037: import org.ejb3unit.asm.ClassAdapter;
038: import org.ejb3unit.asm.ClassReader;
039: import org.ejb3unit.asm.ClassVisitor;
040: import org.ejb3unit.asm.FieldVisitor;
041: import org.ejb3unit.asm.MethodVisitor;
042: import org.ejb3unit.asm.Opcodes;
043: import org.ejb3unit.asm.Type;
044: import org.ejb3unit.asm.tree.ClassNode;
045: import org.ejb3unit.asm.tree.MethodNode;
046: import org.ejb3unit.asm.tree.TryCatchBlockNode;
047: import org.ejb3unit.asm.tree.analysis.Analyzer;
048: import org.ejb3unit.asm.tree.analysis.Frame;
049: import org.ejb3unit.asm.tree.analysis.SimpleVerifier;
050:
051: /**
052: * A {@link ClassAdapter} that checks that its methods are properly used. More
053: * precisely this class adapter checks each method call individually, based
054: * <i>only</i> on its arguments, but does <i>not</i> check the <i>sequence</i>
055: * of method calls. For example, the invalid sequence
056: * <tt>visitField(ACC_PUBLIC, "i", "I", null)</tt> <tt>visitField(ACC_PUBLIC,
057: * "i", "D", null)</tt>
058: * will <i>not</i> be detected by this class adapter.
059: *
060: * @author Eric Bruneton
061: */
062: public class CheckClassAdapter extends ClassAdapter {
063:
064: /**
065: * <tt>true</tt> if the visit method has been called.
066: */
067: private boolean start;
068:
069: /**
070: * <tt>true</tt> if the visitSource method has been called.
071: */
072: private boolean source;
073:
074: /**
075: * <tt>true</tt> if the visitOuterClass method has been called.
076: */
077: private boolean outer;
078:
079: /**
080: * <tt>true</tt> if the visitEnd method has been called.
081: */
082: private boolean end;
083:
084: /**
085: * Checks a given class. <p> Usage: CheckClassAdapter <fully qualified
086: * class name or class file name>
087: *
088: * @param args the command line arguments.
089: *
090: * @throws Exception if the class cannot be found, or if an IO exception
091: * occurs.
092: */
093: public static void main(final String[] args) throws Exception {
094: if (args.length != 1) {
095: System.err.println("Verifies the given class.");
096: System.err
097: .println("Usage: CheckClassAdapter "
098: + "<fully qualified class name or class file name>");
099: return;
100: }
101: ClassReader cr;
102: if (args[0].endsWith(".class")) {
103: cr = new ClassReader(new FileInputStream(args[0]));
104: } else {
105: cr = new ClassReader(args[0]);
106: }
107:
108: verify(cr, false, new PrintWriter(System.err));
109: }
110:
111: /**
112: * Checks a given class
113: *
114: * @param cr a <code>ClassReader</code> that contains bytecode for the
115: * analysis.
116: * @param dump true if bytecode should be printed out not only when errors
117: * are found.
118: * @param pw write where results going to be printed
119: */
120: public static void verify(final ClassReader cr, final boolean dump,
121: final PrintWriter pw) {
122: ClassNode cn = new ClassNode();
123: cr.accept(new CheckClassAdapter(cn), ClassReader.SKIP_DEBUG);
124:
125: List methods = cn.methods;
126: for (int i = 0; i < methods.size(); ++i) {
127: MethodNode method = (MethodNode) methods.get(i);
128: Analyzer a = new Analyzer(new SimpleVerifier(Type
129: .getObjectType(cn.name), Type
130: .getObjectType(cn.super Name), false));
131: try {
132: a.analyze(cn.name, method);
133: if (!dump) {
134: continue;
135: }
136: } catch (Exception e) {
137: e.printStackTrace();
138: }
139: Frame[] frames = a.getFrames();
140:
141: TraceMethodVisitor mv = new TraceMethodVisitor();
142:
143: pw.println(method.name + method.desc);
144: for (int j = 0; j < method.instructions.size(); ++j) {
145: method.instructions.get(j).accept(mv);
146:
147: StringBuffer s = new StringBuffer();
148: Frame f = frames[j];
149: if (f == null) {
150: s.append('?');
151: } else {
152: for (int k = 0; k < f.getLocals(); ++k) {
153: s
154: .append(
155: getShortName(f.getLocal(k)
156: .toString())).append(
157: ' ');
158: }
159: s.append(" : ");
160: for (int k = 0; k < f.getStackSize(); ++k) {
161: s
162: .append(
163: getShortName(f.getStack(k)
164: .toString())).append(
165: ' ');
166: }
167: }
168: while (s.length() < method.maxStack + method.maxLocals
169: + 1) {
170: s.append(' ');
171: }
172: pw.print(Integer.toString(j + 100000).substring(1));
173: pw.print(" " + s + " : " + mv.buf); // mv.text.get(j));
174: }
175: for (int j = 0; j < method.tryCatchBlocks.size(); ++j) {
176: ((TryCatchBlockNode) method.tryCatchBlocks.get(j))
177: .accept(mv);
178: pw.print(" " + mv.buf);
179: }
180: pw.println();
181: }
182: }
183:
184: private static String getShortName(final String name) {
185: int n = name.lastIndexOf('/');
186: int k = name.length();
187: if (name.charAt(k - 1) == ';') {
188: k--;
189: }
190: return n == -1 ? name : name.substring(n + 1, k);
191: }
192:
193: /**
194: * Constructs a new {@link CheckClassAdapter}.
195: *
196: * @param cv the class visitor to which this adapter must delegate calls.
197: */
198: public CheckClassAdapter(final ClassVisitor cv) {
199: super (cv);
200: }
201:
202: // ------------------------------------------------------------------------
203: // Implementation of the ClassVisitor interface
204: // ------------------------------------------------------------------------
205:
206: public void visit(final int version, final int access,
207: final String name, final String signature,
208: final String super Name, final String[] interfaces) {
209: if (start) {
210: throw new IllegalStateException(
211: "visit must be called only once");
212: } else {
213: start = true;
214: }
215: checkState();
216: checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL
217: + Opcodes.ACC_SUPER + Opcodes.ACC_INTERFACE
218: + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC
219: + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM
220: + Opcodes.ACC_DEPRECATED);
221: if (!name.endsWith("package-info")) {
222: CheckMethodAdapter.checkInternalName(name, "class name");
223: }
224: if ("java/lang/Object".equals(name)) {
225: if (super Name != null) {
226: throw new IllegalArgumentException(
227: "The super class name of the Object class must be 'null'");
228: }
229: } else {
230: CheckMethodAdapter.checkInternalName(super Name,
231: "super class name");
232: }
233: if (signature != null) {
234: // TODO
235: }
236: if ((access & Opcodes.ACC_INTERFACE) != 0) {
237: if (!"java/lang/Object".equals(super Name)) {
238: throw new IllegalArgumentException(
239: "The super class name of interfaces must be 'java/lang/Object'");
240: }
241: }
242: if (interfaces != null) {
243: for (int i = 0; i < interfaces.length; ++i) {
244: CheckMethodAdapter.checkInternalName(interfaces[i],
245: "interface name at index " + i);
246: }
247: }
248: cv.visit(version, access, name, signature, super Name,
249: interfaces);
250: }
251:
252: public void visitSource(final String file, final String debug) {
253: checkState();
254: if (source) {
255: throw new IllegalStateException(
256: "visitSource can be called only once.");
257: }
258: source = true;
259: cv.visitSource(file, debug);
260: }
261:
262: public void visitOuterClass(final String owner, final String name,
263: final String desc) {
264: checkState();
265: if (outer) {
266: throw new IllegalStateException(
267: "visitOuterClass can be called only once.");
268: }
269: outer = true;
270: if (owner == null) {
271: throw new IllegalArgumentException(
272: "Illegal outer class owner");
273: }
274: if (desc != null) {
275: CheckMethodAdapter.checkMethodDesc(desc);
276: }
277: cv.visitOuterClass(owner, name, desc);
278: }
279:
280: public void visitInnerClass(final String name,
281: final String outerName, final String innerName,
282: final int access) {
283: checkState();
284: CheckMethodAdapter.checkInternalName(name, "class name");
285: if (outerName != null) {
286: CheckMethodAdapter.checkInternalName(outerName,
287: "outer class name");
288: }
289: if (innerName != null) {
290: CheckMethodAdapter.checkIdentifier(innerName,
291: "inner class name");
292: }
293: checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE
294: + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC
295: + Opcodes.ACC_FINAL + Opcodes.ACC_INTERFACE
296: + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC
297: + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM);
298: cv.visitInnerClass(name, outerName, innerName, access);
299: }
300:
301: public FieldVisitor visitField(final int access, final String name,
302: final String desc, final String signature,
303: final Object value) {
304: checkState();
305: checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE
306: + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC
307: + Opcodes.ACC_FINAL + Opcodes.ACC_VOLATILE
308: + Opcodes.ACC_TRANSIENT + Opcodes.ACC_SYNTHETIC
309: + Opcodes.ACC_ENUM + Opcodes.ACC_DEPRECATED);
310: CheckMethodAdapter.checkIdentifier(name, "field name");
311: CheckMethodAdapter.checkDesc(desc, false);
312: if (signature != null) {
313: // TODO
314: }
315: if (value != null) {
316: CheckMethodAdapter.checkConstant(value);
317: }
318: FieldVisitor av = cv.visitField(access, name, desc, signature,
319: value);
320: return new CheckFieldAdapter(av);
321: }
322:
323: public MethodVisitor visitMethod(final int access,
324: final String name, final String desc,
325: final String signature, final String[] exceptions) {
326: checkState();
327: checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE
328: + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC
329: + Opcodes.ACC_FINAL + Opcodes.ACC_SYNCHRONIZED
330: + Opcodes.ACC_BRIDGE + Opcodes.ACC_VARARGS
331: + Opcodes.ACC_NATIVE + Opcodes.ACC_ABSTRACT
332: + Opcodes.ACC_STRICT + Opcodes.ACC_SYNTHETIC
333: + Opcodes.ACC_DEPRECATED);
334: CheckMethodAdapter.checkMethodIdentifier(name, "method name");
335: CheckMethodAdapter.checkMethodDesc(desc);
336: if (signature != null) {
337: // TODO
338: }
339: if (exceptions != null) {
340: for (int i = 0; i < exceptions.length; ++i) {
341: CheckMethodAdapter.checkInternalName(exceptions[i],
342: "exception name at index " + i);
343: }
344: }
345: return new CheckMethodAdapter(cv.visitMethod(access, name,
346: desc, signature, exceptions));
347: }
348:
349: public AnnotationVisitor visitAnnotation(final String desc,
350: final boolean visible) {
351: checkState();
352: CheckMethodAdapter.checkDesc(desc, false);
353: return new CheckAnnotationAdapter(cv.visitAnnotation(desc,
354: visible));
355: }
356:
357: public void visitAttribute(final Attribute attr) {
358: checkState();
359: if (attr == null) {
360: throw new IllegalArgumentException(
361: "Invalid attribute (must not be null)");
362: }
363: cv.visitAttribute(attr);
364: }
365:
366: public void visitEnd() {
367: checkState();
368: end = true;
369: cv.visitEnd();
370: }
371:
372: // ------------------------------------------------------------------------
373: // Utility methods
374: // ------------------------------------------------------------------------
375:
376: /**
377: * Checks that the visit method has been called and that visitEnd has not
378: * been called.
379: */
380: private void checkState() {
381: if (!start) {
382: throw new IllegalStateException(
383: "Cannot visit member before visit has been called.");
384: }
385: if (end) {
386: throw new IllegalStateException(
387: "Cannot visit member after visitEnd has been called.");
388: }
389: }
390:
391: /**
392: * Checks that the given access flags do not contain invalid flags. This
393: * method also checks that mutually incompatible flags are not set
394: * simultaneously.
395: *
396: * @param access the access flags to be checked
397: * @param possibleAccess the valid access flags.
398: */
399: static void checkAccess(final int access, final int possibleAccess) {
400: if ((access & ~possibleAccess) != 0) {
401: throw new IllegalArgumentException("Invalid access flags: "
402: + access);
403: }
404: int pub = (access & Opcodes.ACC_PUBLIC) != 0 ? 1 : 0;
405: int pri = (access & Opcodes.ACC_PRIVATE) != 0 ? 1 : 0;
406: int pro = (access & Opcodes.ACC_PROTECTED) != 0 ? 1 : 0;
407: if (pub + pri + pro > 1) {
408: throw new IllegalArgumentException(
409: "public private and protected are mutually exclusive: "
410: + access);
411: }
412: int fin = (access & Opcodes.ACC_FINAL) != 0 ? 1 : 0;
413: int abs = (access & Opcodes.ACC_ABSTRACT) != 0 ? 1 : 0;
414: if (fin + abs > 1) {
415: throw new IllegalArgumentException(
416: "final and abstract are mutually exclusive: "
417: + access);
418: }
419: }
420: }
|