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