/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.bytecode; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; import com.caucho.inject.Module; import com.caucho.util.L10N; /** * Scans for matching classes. */ @Module public class ByteCodeClassScanner { private static final Logger log = Logger.getLogger(ByteCodeClassScanner.class.getName()); private static final L10N L = new L10N(ByteCodeClassScanner.class); private static final char []RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations".toCharArray(); private String _className; private InputStream _is; private ByteCodeClassMatcher _matcher; private char []_charBuffer; private int _charBufferOffset; private int _cpCount; private int []_cpData; private int []_cpLengths; private int []_classData; public ByteCodeClassScanner() { _charBuffer = new char[16384]; } public void init(String className, InputStream is, ByteCodeClassMatcher matcher) { _className = className; _is = is; _matcher = matcher; _charBufferOffset = 0; } public boolean scan() { try { InputStream is = _is; int magic = readInt(is); if (magic != JavaClass.MAGIC) throw error(L.l("bad magic number in class file")); is.skip(2); // major is.skip(2); // minor parseConstantPool(is); int modifiers = readShort(is); int thisClassIndex = readShort(is); int cpIndex = _classData[thisClassIndex]; if (cpIndex > 0) { String className = new String(_charBuffer, _cpData[cpIndex], _cpLengths[cpIndex]); if (! _matcher.scanClass(className, modifiers)) { return false; } } int superClassIndex = readShort(is); if (superClassIndex > 0) { cpIndex = _classData[superClassIndex]; if (cpIndex > 0) { _matcher.addSuperClass(_charBuffer, _cpData[cpIndex], _cpLengths[cpIndex]); } } int interfaceCount = readShort(is); for (int i = 0; i < interfaceCount; i++) { int classIndex = readShort(is); cpIndex = _classData[classIndex]; if (cpIndex > 0) { _matcher.addInterface(_charBuffer, _cpData[cpIndex], _cpLengths[cpIndex]); } } int fieldCount = readShort(is); for (int i = 0; i < fieldCount; i++) { scanField(is); } int methodCount = readShort(is); for (int i = 0; i < methodCount; i++) { scanMethod(is); } int attrCount = readShort(is); for (int i = 0; i < attrCount; i++) { scanClassAttribute(is); } char []charBuffer = _charBuffer; for (int i = 0; i < _cpCount; i++) { int cpLength = _cpLengths[i]; if (cpLength > 0) { int cpOffset = _cpData[i]; if (charBuffer[cpOffset] == 'L' && charBuffer[cpOffset + cpLength - 1] == ';') { _matcher.addPoolString(charBuffer, cpOffset + 1, cpLength - 2); } } } return _matcher.finishScan(); } catch (Exception e) { log.log(Level.WARNING, "failed scanning class " + _className + "\n" + e.toString(), e); return false; } } /** * Parses the constant pool. */ public boolean parseConstantPool(InputStream is) throws IOException { int count = readShort(is); _cpCount = count; if (_cpData == null || _cpData.length <= count) { _cpData = new int[count]; _cpLengths = new int[count]; _classData = new int[count]; } Arrays.fill(_cpData, 0); Arrays.fill(_cpLengths, 0); Arrays.fill(_classData, 0); int i = 1; while (i < count) { int index = i; int code = is.read(); if (code == ByteCodeParser.CP_LONG || code == ByteCodeParser.CP_DOUBLE) i += 2; else i += 1; switch (code) { case ByteCodeParser.CP_CLASS: int utf8Index = readShort(is); // index of the UTF-8 string _classData[index] = utf8Index; break; case ByteCodeParser.CP_FIELD_REF: // int classIndex = readShort(); // int nameAndTypeIndex = readShort(); is.skip(4); break; case ByteCodeParser.CP_METHOD_REF: // int classIndex = readShort(); // int nameAndTypeIndex = readShort(); is.skip(4); break; case ByteCodeParser.CP_INTERFACE_METHOD_REF: // int classIndex = readShort(); // int nameAndTypeIndex = readShort(); is.skip(4); break; case ByteCodeParser.CP_STRING: // int stringIndex = readShort(); is.skip(2); break; case ByteCodeParser.CP_INTEGER: is.skip(4); break; case ByteCodeParser.CP_FLOAT: is.skip(4); break; case ByteCodeParser.CP_LONG: is.skip(8); break; case ByteCodeParser.CP_DOUBLE: is.skip(8); break; case ByteCodeParser.CP_NAME_AND_TYPE: // int nameIndex = readShort(); // int descriptorIndex = readShort(); is.skip(4); break; case ByteCodeParser.CP_UTF8: { int length = readShort(is); _cpData[index] = _charBufferOffset; _cpLengths[index] = parseUtf8(is, length); break; } default: throw error(L.l("'{0}' is an unknown constant pool type.", code)); } } return false; } /** * Parses the UTF. */ private int parseUtf8(InputStream is, int length) throws IOException { if (length <= 0) return 0; if (length > 256) { is.skip(length); return 0; } int offset = _charBufferOffset; if (_charBuffer.length <= offset + length) { char []buffer = new char[2 * _charBuffer.length]; System.arraycopy(_charBuffer, 0, buffer, 0, _charBuffer.length); _charBuffer = buffer; } char []buffer = _charBuffer; boolean isIdentifier = true; while (length > 0) { int d1 = is.read(); char ch; if (d1 == '/') { ch = '.'; length--; } else if (d1 < 0x80) { ch = (char) d1; length--; } else if (d1 < 0xe0) { int d2 = is.read() & 0x3f; ch = (char) (((d1 & 0x1f) << 6) + (d2)); length -= 2; } else if (d1 < 0xf0) { int d2 = is.read() & 0x3f; int d3 = is.read() & 0x3f; ch = (char) (((d1 & 0xf) << 12) + (d2 << 6) + d3); length -= 3; } else throw new IllegalStateException(); if (isIdentifier && (Character.isJavaIdentifierPart(ch) || ch == '.' || ch == ';')) { buffer[offset++] = ch; } else { isIdentifier = false; } } if (! isIdentifier) return 0; int charLength = offset - _charBufferOffset; _charBufferOffset = offset; return charLength; } /** * Parses a field entry. */ private void skipField(InputStream is) throws IOException { // int accessFlags = readShort(); // int nameIndex = readShort(); // int descriptorIndex = readShort(); is.skip(6); int attributesCount = readShort(is); for (int i = 0; i < attributesCount; i++) { skipAttribute(is); } } /** * Parses a field entry. */ private void scanField(InputStream is) throws IOException { // int accessFlags = readShort(); // int nameIndex = readShort(); // int descriptorIndex = readShort(); is.skip(6); int attributesCount = readShort(is); for (int i = 0; i < attributesCount; i++) { scanAttributeForAnnotation(is); } } /** * Parses a method entry. */ private void skipMethod(InputStream is) throws IOException { /* int accessFlags = readShort(); int nameIndex = readShort(); int descriptorIndex = readShort(); */ is.skip(6); int attributesCount = readShort(is); for (int i = 0; i < attributesCount; i++) { skipAttribute(is); } } /** * Parses a method entry. */ private void scanMethod(InputStream is) throws IOException { /* int accessFlags = readShort(); int nameIndex = readShort(); int descriptorIndex = readShort(); */ is.skip(6); int attributesCount = readShort(is); for (int i = 0; i < attributesCount; i++) { scanAttributeForAnnotation(is); } } /** * Parses an attribute. */ private void scanClassAttribute(InputStream is) throws IOException { int nameIndex = readShort(is); // String name = _cp.getUtf8(nameIndex).getValue(); int length = readInt(is); if (! isNameAnnotation(nameIndex)) { is.skip(length); return; } int count = readShort(is); for (int i = 0; i < count; i++) { int annTypeIndex = scanAnnotation(is); if (annTypeIndex > 0) { _matcher.addClassAnnotation(_charBuffer, _cpData[annTypeIndex] + 1, _cpLengths[annTypeIndex] - 2); } } } /** * Parses an attribute for an annotation. */ private void scanAttributeForAnnotation(InputStream is) throws IOException { int nameIndex = readShort(is); // String name = _cp.getUtf8(nameIndex).getValue(); int length = readInt(is); if (! isNameAnnotation(nameIndex)) { is.skip(length); return; } int count = readShort(is); for (int i = 0; i < count; i++) { int annTypeIndex = scanAnnotation(is); if (annTypeIndex > 0 && _cpLengths[annTypeIndex] > 2) { _matcher.addClassAnnotation(_charBuffer, _cpData[annTypeIndex] + 1, _cpLengths[annTypeIndex] - 2); } } } private int scanAnnotation(InputStream is) throws IOException { int typeIndex = readShort(is); int valueCount = readShort(is); for (int j = 0; j < valueCount; j++) { is.skip(2); // int eltIndex = readShort(is); skipElementValue(is); } return typeIndex; } private void skipElementValue(InputStream is) throws IOException { int code = is.read(); switch (code) { case 'B': case 'C': case 'D': case 'F': case 'I': case 'J': case 'S': case 'Z': case 's': is.skip(2); return; case 'e': is.skip(4); return; case 'c': is.skip(2); return; case '@': scanAnnotation(is); return; case '[': int len = readShort(is); for (int i = 0; i < len; i++) { skipElementValue(is); } return; default: throw new IllegalStateException("unknown code: " + (char) code); } } private boolean isNameAnnotation(int nameIndex) { if (nameIndex <= 0) return false; int offset = _cpData[nameIndex]; int length = _cpLengths[nameIndex]; char []charBuffer = _charBuffer; if (length != RUNTIME_VISIBLE_ANNOTATIONS.length) return false; for (int i = 0; i < length; i++) { if (charBuffer[i + offset] != RUNTIME_VISIBLE_ANNOTATIONS[i]) return false; } return true; } /** * Parses an attribute. */ private void skipAttribute(InputStream is) throws IOException { int nameIndex = readShort(is); // String name = _cp.getUtf8(nameIndex).getValue(); int length = readInt(is); is.skip(length); } /** * Parses a 32-bit int. */ private int readInt(InputStream is) throws IOException { return ((is.read() << 24) | (is.read() << 16) | (is.read() << 8) | (is.read())); } /** * Parses a 16-bit int. */ private int readShort(InputStream is) throws IOException { int c1 = is.read(); int c2 = is.read(); return ((c1 << 8) | c2); } /** * Returns an error message. */ private IllegalStateException error(String message) { return new IllegalStateException(_className + ": " + message); } }