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 com.tc.asm.util;
030:
031: import java.io.FileInputStream;
032: import java.io.PrintWriter;
033: import java.util.List;
034:
035: import com.tc.asm.AnnotationVisitor;
036: import com.tc.asm.Attribute;
037: import com.tc.asm.ClassAdapter;
038: import com.tc.asm.ClassReader;
039: import com.tc.asm.ClassVisitor;
040: import com.tc.asm.FieldVisitor;
041: import com.tc.asm.MethodVisitor;
042: import com.tc.asm.Opcodes;
043: import com.tc.asm.Type;
044: import com.tc.asm.tree.ClassNode;
045: import com.tc.asm.tree.MethodNode;
046: import com.tc.asm.tree.TryCatchBlockNode;
047: import com.tc.asm.tree.analysis.Analyzer;
048: import com.tc.asm.tree.analysis.Frame;
049: import com.tc.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(pw);
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 + "\n " + 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: pw.flush();
183: }
184:
185: private static String getShortName(final String name) {
186: int n = name.lastIndexOf('/');
187: int k = name.length();
188: if (name.charAt(k - 1) == ';') {
189: k--;
190: }
191: return n == -1 ? name : name.substring(n + 1, k);
192: }
193:
194: /**
195: * Constructs a new {@link CheckClassAdapter}.
196: *
197: * @param cv the class visitor to which this adapter must delegate calls.
198: */
199: public CheckClassAdapter(final ClassVisitor cv) {
200: super (cv);
201: }
202:
203: // ------------------------------------------------------------------------
204: // Implementation of the ClassVisitor interface
205: // ------------------------------------------------------------------------
206:
207: public void visit(final int version, final int access,
208: final String name, final String signature,
209: final String super Name, final String[] interfaces) {
210: if (start) {
211: throw new IllegalStateException(
212: "visit must be called only once");
213: } else {
214: start = true;
215: }
216: checkState();
217: checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL
218: + Opcodes.ACC_SUPER + Opcodes.ACC_INTERFACE
219: + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC
220: + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM
221: + Opcodes.ACC_DEPRECATED);
222: if (name == null || !name.endsWith("package-info")) {
223: CheckMethodAdapter.checkInternalName(name, "class name");
224: }
225: if ("java/lang/Object".equals(name)) {
226: if (super Name != null) {
227: throw new IllegalArgumentException(
228: "The super class name of the Object class must be 'null'");
229: }
230: } else {
231: CheckMethodAdapter.checkInternalName(super Name,
232: "super class name");
233: }
234: if (signature != null) {
235: CheckMethodAdapter.checkClassSignature(signature);
236: }
237: if ((access & Opcodes.ACC_INTERFACE) != 0) {
238: if (!"java/lang/Object".equals(super Name)) {
239: throw new IllegalArgumentException(
240: "The super class name of interfaces must be 'java/lang/Object'");
241: }
242: }
243: if (interfaces != null) {
244: for (int i = 0; i < interfaces.length; ++i) {
245: CheckMethodAdapter.checkInternalName(interfaces[i],
246: "interface name at index " + i);
247: }
248: }
249: cv.visit(version, access, name, signature, super Name,
250: interfaces);
251: }
252:
253: public void visitSource(final String file, final String debug) {
254: checkState();
255: if (source) {
256: throw new IllegalStateException(
257: "visitSource can be called only once.");
258: }
259: source = true;
260: cv.visitSource(file, debug);
261: }
262:
263: public void visitOuterClass(final String owner, final String name,
264: final String desc) {
265: checkState();
266: if (outer) {
267: throw new IllegalStateException(
268: "visitOuterClass can be called only once.");
269: }
270: outer = true;
271: if (owner == null) {
272: throw new IllegalArgumentException(
273: "Illegal outer class owner");
274: }
275: if (desc != null) {
276: CheckMethodAdapter.checkMethodDesc(desc);
277: }
278: cv.visitOuterClass(owner, name, desc);
279: }
280:
281: public void visitInnerClass(final String name,
282: final String outerName, final String innerName,
283: final int access) {
284: checkState();
285: CheckMethodAdapter.checkInternalName(name, "class name");
286: if (outerName != null) {
287: CheckMethodAdapter.checkInternalName(outerName,
288: "outer class name");
289: }
290: if (innerName != null) {
291: CheckMethodAdapter.checkIdentifier(innerName,
292: "inner class name");
293: }
294: checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE
295: + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC
296: + Opcodes.ACC_FINAL + Opcodes.ACC_INTERFACE
297: + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC
298: + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM);
299: cv.visitInnerClass(name, outerName, innerName, access);
300: }
301:
302: public FieldVisitor visitField(final int access, final String name,
303: final String desc, final String signature,
304: final Object value) {
305: checkState();
306: checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE
307: + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC
308: + Opcodes.ACC_FINAL + Opcodes.ACC_VOLATILE
309: + Opcodes.ACC_TRANSIENT + Opcodes.ACC_SYNTHETIC
310: + Opcodes.ACC_ENUM + Opcodes.ACC_DEPRECATED);
311: CheckMethodAdapter.checkIdentifier(name, "field name");
312: CheckMethodAdapter.checkDesc(desc, false);
313: if (signature != null) {
314: CheckMethodAdapter.checkFieldSignature(signature);
315: }
316: if (value != null) {
317: CheckMethodAdapter.checkConstant(value);
318: }
319: FieldVisitor av = cv.visitField(access, name, desc, signature,
320: value);
321: return new CheckFieldAdapter(av);
322: }
323:
324: public MethodVisitor visitMethod(final int access,
325: final String name, final String desc,
326: final String signature, final String[] exceptions) {
327: checkState();
328: checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE
329: + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC
330: + Opcodes.ACC_FINAL + Opcodes.ACC_SYNCHRONIZED
331: + Opcodes.ACC_BRIDGE + Opcodes.ACC_VARARGS
332: + Opcodes.ACC_NATIVE + Opcodes.ACC_ABSTRACT
333: + Opcodes.ACC_STRICT + Opcodes.ACC_SYNTHETIC
334: + Opcodes.ACC_DEPRECATED);
335: CheckMethodAdapter.checkMethodIdentifier(name, "method name");
336: CheckMethodAdapter.checkMethodDesc(desc);
337: if (signature != null) {
338: CheckMethodAdapter.checkMethodSignature(signature);
339: }
340: if (exceptions != null) {
341: for (int i = 0; i < exceptions.length; ++i) {
342: CheckMethodAdapter.checkInternalName(exceptions[i],
343: "exception name at index " + i);
344: }
345: }
346: return new CheckMethodAdapter(cv.visitMethod(access, name,
347: desc, signature, exceptions));
348: }
349:
350: public AnnotationVisitor visitAnnotation(final String desc,
351: final boolean visible) {
352: checkState();
353: CheckMethodAdapter.checkDesc(desc, false);
354: return new CheckAnnotationAdapter(cv.visitAnnotation(desc,
355: visible));
356: }
357:
358: public void visitAttribute(final Attribute attr) {
359: checkState();
360: if (attr == null) {
361: throw new IllegalArgumentException(
362: "Invalid attribute (must not be null)");
363: }
364: cv.visitAttribute(attr);
365: }
366:
367: public void visitEnd() {
368: checkState();
369: end = true;
370: cv.visitEnd();
371: }
372:
373: // ------------------------------------------------------------------------
374: // Utility methods
375: // ------------------------------------------------------------------------
376:
377: /**
378: * Checks that the visit method has been called and that visitEnd has not
379: * been called.
380: */
381: private void checkState() {
382: if (!start) {
383: throw new IllegalStateException(
384: "Cannot visit member before visit has been called.");
385: }
386: if (end) {
387: throw new IllegalStateException(
388: "Cannot visit member after visitEnd has been called.");
389: }
390: }
391:
392: /**
393: * Checks that the given access flags do not contain invalid flags. This
394: * method also checks that mutually incompatible flags are not set
395: * simultaneously.
396: *
397: * @param access the access flags to be checked
398: * @param possibleAccess the valid access flags.
399: */
400: static void checkAccess(final int access, final int possibleAccess) {
401: if ((access & ~possibleAccess) != 0) {
402: throw new IllegalArgumentException("Invalid access flags: "
403: + access);
404: }
405: int pub = (access & Opcodes.ACC_PUBLIC) != 0 ? 1 : 0;
406: int pri = (access & Opcodes.ACC_PRIVATE) != 0 ? 1 : 0;
407: int pro = (access & Opcodes.ACC_PROTECTED) != 0 ? 1 : 0;
408: if (pub + pri + pro > 1) {
409: throw new IllegalArgumentException(
410: "public private and protected are mutually exclusive: "
411: + access);
412: }
413: int fin = (access & Opcodes.ACC_FINAL) != 0 ? 1 : 0;
414: int abs = (access & Opcodes.ACC_ABSTRACT) != 0 ? 1 : 0;
415: if (fin + abs > 1) {
416: throw new IllegalArgumentException(
417: "final and abstract are mutually exclusive: "
418: + access);
419: }
420: }
421: }
|