/* * Nocturne * Copyright (c) 2015-2016, Lapis <https://github.com/LapisBlue> * * The MIT License * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package blue.lapis.nocturne.processor.index; import static blue.lapis.nocturne.util.Constants.CLASS_FORMAT_CONSTANT_POOL_OFFSET; import static blue.lapis.nocturne.util.Constants.INNER_CLASS_SEPARATOR_CHAR; import static blue.lapis.nocturne.util.helper.ByteHelper.asUshort; import static com.google.common.base.Preconditions.checkArgument; import blue.lapis.nocturne.Main; import blue.lapis.nocturne.jar.model.JarClassEntry; import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; import blue.lapis.nocturne.jar.model.attribute.Type; import blue.lapis.nocturne.processor.ClassProcessor; import blue.lapis.nocturne.processor.constantpool.ConstantPoolReader; import blue.lapis.nocturne.processor.constantpool.model.ConstantPool; import blue.lapis.nocturne.processor.constantpool.model.ImmutableConstantPool; import blue.lapis.nocturne.processor.constantpool.model.structure.ClassStructure; import blue.lapis.nocturne.processor.constantpool.model.structure.ConstantStructure; import blue.lapis.nocturne.processor.constantpool.model.structure.Utf8Structure; import blue.lapis.nocturne.processor.index.model.IndexedClass; import blue.lapis.nocturne.processor.index.model.IndexedField; import blue.lapis.nocturne.processor.index.model.IndexedMethod; import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Optional; /** * Creates an index of select information from a given class. */ public class ClassIndexer extends ClassProcessor { private final JarClassEntry jce; public ClassIndexer(JarClassEntry clazz) { super(clazz.getName(), clazz.getContent()); this.jce = clazz; } /** * Indexes the class file and returns an {@link IndexedClass} object * representing it. * * @return The created index of the class */ public IndexedClass index() { ImmutableConstantPool pool = new ConstantPoolReader(getClassName(), getOriginalBytes()).read(); // get the pool ByteBuffer buffer = ByteBuffer.wrap(bytes); // create a buffer for the bytecode buffer.position(CLASS_FORMAT_CONSTANT_POOL_OFFSET + pool.length() + 4); // position the buffer final String superClass = getClassNameFromIndex(pool, asUshort(buffer.getShort())); // read the superclass name int interfaceCount = asUshort(buffer.getShort()); // read the interface count List<String> interfaces = new ArrayList<>(); for (int i = 0; i < interfaceCount; i++) { interfaces.add(getClassNameFromIndex(pool, buffer.getShort())); // read each interface name } if (getClassName().contains(INNER_CLASS_SEPARATOR_CHAR + "")) { int lastIndex = getClassName().lastIndexOf(INNER_CLASS_SEPARATOR_CHAR); Optional<JarClassEntry> parent = Main.getLoadedJar() .getClass(getClassName().substring(0, lastIndex)); if (parent.isPresent()) { String simpleName = getClassName().substring(lastIndex + 1); parent.get().getCurrentInnerClassNames().put(simpleName, simpleName); } } List<IndexedField> fields = indexFields(buffer, pool); List<IndexedMethod> methods = indexMethods(buffer, pool); return new IndexedClass(getClassName(), pool, superClass, interfaces, fields, methods); } /** * Skips the fields of the provided buffer, given it is positioned at their * immediate start. * * @param buffer The buffer to read from */ private List<IndexedField> indexFields(ByteBuffer buffer, ConstantPool pool) { List<IndexedField> fields = new ArrayList<>(); int fieldCount = buffer.getShort(); // read the field count for (int i = 0; i < fieldCount; i++) { IndexedField.Visibility vis = IndexedField.Visibility.fromAccessFlags(buffer.getShort()); // get the access String name = getString(pool, buffer.getShort()); // get the name Type desc = Type.fromString(getString(pool, buffer.getShort())); // get the descriptor FieldSignature sig = new FieldSignature(name, desc); fields.add(new IndexedField(sig, vis)); jce.getCurrentFields().put(sig, sig); // index the field name for future reference skipAttributes(buffer); } return fields; } /** * Reads the methods of the provided buffer, given it is positioned at their * immediate start. * * @param buffer The buffer to read from * @param pool The constant pool to read strings from * @return A {@link List} of read {@link IndexedMethod}s */ private List<IndexedMethod> indexMethods(ByteBuffer buffer, ConstantPool pool) { List<IndexedMethod> methods = new ArrayList<>(); int methodCount = asUshort(buffer.getShort()); for (int i = 0; i < methodCount; i++) { IndexedMethod.Visibility vis = IndexedMethod.Visibility.fromAccessFlags(buffer.getShort()); String name = getString(pool, buffer.getShort()); MethodDescriptor desc = MethodDescriptor.fromString(getString(pool, buffer.getShort())); MethodSignature sig = new MethodSignature(name, desc); methods.add(new IndexedMethod(sig, vis)); jce.getCurrentMethods().put(sig, sig); // index the method sig for future reference skipAttributes(buffer); } return methods; } /** * Skips the upcoming attribute table of the provided buffer, given it is * positioned at their immediate start. * * @param buffer The buffer to read from */ private void skipAttributes(ByteBuffer buffer) { int attrCount = asUshort(buffer.getShort()); // read the attribute count for (int j = 0; j < attrCount; j++) { buffer.position(buffer.position() + 2); // skip the attribute name // the length is a uint, but anything larger than Integer.MAX_VALUE won't fit in a Java array/buffer // also, why the hell would you have a 2 GB attribute in the first place? int attrLen = buffer.getInt(); // get the body length buffer.position(buffer.position() + attrLen); // skip the attribute body } } private String getString(ConstantPool pool, int strIndex) { assert strIndex <= pool.size(); ConstantStructure cs = pool.get(strIndex); assert cs instanceof Utf8Structure; return ((Utf8Structure) cs).asString(); } private String getClassNameFromIndex(ConstantPool pool, int index) { ConstantStructure classStruct = pool.get(index); checkArgument(classStruct instanceof ClassStructure, "Index does not point to class structure"); return getString(pool, ((ClassStructure) classStruct).getNameIndex()); } }