diff --git a/swift-codec/src/main/java/com/facebook/swift/codec/metadata/BytecodeReaderParanamer.java b/swift-codec/src/main/java/com/facebook/swift/codec/metadata/BytecodeReaderParanamer.java new file mode 100644 index 00000000..498f2794 --- /dev/null +++ b/swift-codec/src/main/java/com/facebook/swift/codec/metadata/BytecodeReaderParanamer.java @@ -0,0 +1,1172 @@ +/*** + * + * Portions Copyright (c) 2007 Paul Hammant + * Portions copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.facebook.swift.codec.metadata; + +import com.thoughtworks.paranamer.ParameterNamesNotFoundException; +import com.thoughtworks.paranamer.Paranamer; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + + +/** + * An ASM-based implementation of Paranamer. It relies on debug information compiled + * with the "-g" javac option to retrieve parameter names. + *

+ * Portions of this source file are a fork of ASM. + *

+ * Identical to com.thoughtworks.paranamer.BytecodeReadingParanamer + * but with fix for PARANAMER-22 BytecodeReadingParanamer doesn't work if method has byte[] or short[] parameter + * + * @author Guilherme Silveira + * @author Paul Hammant + */ +class BytecodeReadingParanamer implements Paranamer { + + private static final Map primitives = new HashMap() { + { + put("int","I"); + put("boolean","Z"); + put("char","C"); + put("byte","B"); // fix PARANAMER-22 + put("short","S"); // fix PARANAMER-22 + put("float","F"); + put("long","J"); + put("double","D"); + } + }; + + public String[] lookupParameterNames(AccessibleObject methodOrConstructor) { + return lookupParameterNames(methodOrConstructor, true); + } + + public String[] lookupParameterNames(AccessibleObject methodOrCtor, boolean throwExceptionIfMissing) { + + Class[] types = null; + Class declaringClass = null; + String name = null; + if (methodOrCtor instanceof Method) { + Method method = (Method) methodOrCtor; + types = method.getParameterTypes(); + name = method.getName(); + declaringClass = method.getDeclaringClass(); + } else { + Constructor constructor = (Constructor) methodOrCtor; + types = constructor.getParameterTypes(); + declaringClass = constructor.getDeclaringClass(); + name = ""; + } + + if (types.length == 0) { + return EMPTY_NAMES; + } + InputStream byteCodeStream = getClassAsStream(declaringClass); + if (byteCodeStream == null) { + if (throwExceptionIfMissing) { + throw new ParameterNamesNotFoundException("Unable to get class bytes"); + } else { + return Paranamer.EMPTY_NAMES; + } + } + try { + ClassReader reader = new ClassReader(byteCodeStream); + TypeCollector visitor = new TypeCollector(name, types, throwExceptionIfMissing); + reader.accept(visitor); + String[] parameterNamesForMethod = visitor.getParameterNamesForMethod(); + try { + byteCodeStream.close(); + } catch (IOException e) { + } + return parameterNamesForMethod; + } catch (IOException e) { + if (throwExceptionIfMissing) { + throw new ParameterNamesNotFoundException("IoException while reading class bytes", e); + } else { + return Paranamer.EMPTY_NAMES; + } + } + } + + private InputStream getClassAsStream(Class clazz) { + ClassLoader classLoader = clazz.getClassLoader(); + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } + return getClassAsStream(classLoader, clazz.getName()); + } + + private InputStream getClassAsStream(ClassLoader classLoader, String className) { + String name = className.replace('.', '/') + ".class"; + // better pre-cache all methods otherwise this content will be loaded + // multiple times + InputStream asStream = classLoader.getResourceAsStream(name); + if (asStream == null) { + asStream = BytecodeReadingParanamer.class.getResourceAsStream(name); + } + return asStream; + } + + /** + * The type collector waits for an specific method in order to start a method + * collector. + * + * @author Guilherme Silveira + */ + private static class TypeCollector { + + private static final String COMMA = ","; + + private final String methodName; + + private final Class[] parameterTypes; + private final boolean throwExceptionIfMissing; + + private MethodCollector collector; + + private TypeCollector(String methodName, Class[] parameterTypes, boolean throwExceptionIfMissing) { + this.methodName = methodName; + this.parameterTypes = parameterTypes; + this.throwExceptionIfMissing = throwExceptionIfMissing; + this.collector = null; + } + + public MethodCollector visitMethod(int access, String name, String desc) { + // already found the method, skip any processing + if (collector != null) { + return null; + } + // not the same name + if (!name.equals(methodName)) { + return null; + } + Type[] argumentTypes = Type.getArgumentTypes(desc); + int longOrDoubleQuantity = 0; + for (Type t : argumentTypes) { + if (t.getClassName().equals("long") + || t.getClassName().equals("double")) { + longOrDoubleQuantity++; + } + } + int paramCount = argumentTypes.length; + // not the same quantity of parameters + if (paramCount != this.parameterTypes.length) { + return null; + } + for (int i = 0; i < argumentTypes.length; i++) { + if (!correctTypeName(argumentTypes, i).equals( + this.parameterTypes[i].getName())) { + return null; + } + } + this.collector = new MethodCollector((Modifier.isStatic(access) ? 0 : 1), + argumentTypes.length + longOrDoubleQuantity); + return collector; + } + +// { +// String s = argumentTypes[i].getClassName(); +// // array notation needs cleanup. +// if (s.endsWith("[]")) { +// String prefix = s.substring(0, s.length() - 2); +// +// // adufilie: added brackets variable and while loop to fix bug with multi-dimensional arrays. +// String brackets = "["; +// while (prefix.endsWith("[]")) +// { +// prefix = prefix.substring(0, prefix.length() - 2); +// brackets += "["; +// } +// +// if (primitives.containsKey(prefix)) { +// s = brackets + primitives.get(prefix); +// } else { +// s = brackets + "L" + prefix + ";"; +// } +// } +// return s; } + + private String correctTypeName(Type[] argumentTypes, int i) { + String s = argumentTypes[i].getClassName(); + // array notation needs cleanup. + String braces = ""; + while (s.endsWith("[]")) { + braces = braces + "["; + s = s.substring(0, s.length() - 2); + } + if (!braces.equals("")) { + if (primitives.containsKey(s)) { + s = braces + primitives.get(s); + } else { + s = braces + "L" + s + ";"; + } + } + return s; + } + + private String[] getParameterNamesForMethod() { + if (collector == null) { + return Paranamer.EMPTY_NAMES; + } + if (!collector.isDebugInfoPresent()) { + if (throwExceptionIfMissing) { + throw new ParameterNamesNotFoundException("Parameter names not found for " + methodName); + } else { + return Paranamer.EMPTY_NAMES; + } + } + return collector.getResult().split(COMMA); + } + + } + + /** + * Objects of this class collects information from a specific method. + * + * @author Guilherme Silveira + */ + private static class MethodCollector { + + private final int paramCount; + + private final int ignoreCount; + + private int currentParameter; + + private final StringBuffer result; + + private boolean debugInfoPresent; + + private MethodCollector(int ignoreCount, int paramCount) { + this.ignoreCount = ignoreCount; + this.paramCount = paramCount; + this.result = new StringBuffer(); + this.currentParameter = 0; + // if there are 0 parameters, there is no need for debug info + this.debugInfoPresent = paramCount == 0; + } + + public void visitLocalVariable(String name, int index) { + if (index >= ignoreCount && index < ignoreCount + paramCount) { + if (!name.equals("arg" + currentParameter)) { + debugInfoPresent = true; + } + result.append(','); + result.append(name); + currentParameter++; + } + } + + private String getResult() { + return result.length() != 0 ? result.substring(1) : ""; + } + + private boolean isDebugInfoPresent() { + return debugInfoPresent; + } + + } + +/*** + * Portions Copyright (c) 2007 Paul Hammant + * Portions copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + + + /** + * A Java class parser to make a Class Visitor visit an existing class. + * This class parses a byte array conforming to the Java class file format and + * calls the appropriate visit methods of a given class visitor for each field, + * method and bytecode instruction encountered. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ + private static class ClassReader { + + /** + * The class to be parsed. The content of this array must not be + * modified. This field is intended for Attribute sub classes, and + * is normally not needed by class generators or adapters. + */ + public final byte[] b; + + /** + * The start index of each constant pool item in {@link #b b}, plus one. + * The one byte offset skips the constant pool item tag that indicates its + * type. + */ + private final int[] items; + + /** + * The String objects corresponding to the CONSTANT_Utf8 items. This cache + * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item, + * which GREATLY improves performances (by a factor 2 to 3). This caching + * strategy could be extended to all constant pool items, but its benefit + * would not be so great for these items (because they are much less + * expensive to parse than CONSTANT_Utf8 items). + */ + private final String[] strings; + + /** + * Maximum length of the strings contained in the constant pool of the + * class. + */ + private final int maxStringLength; + + /** + * Start index of the class header information (access, name...) in + * {@link #b b}. + */ + public final int header; + + + /** + * The type of CONSTANT_Fieldref constant pool items. + */ + final static int FIELD = 9; + + /** + * The type of CONSTANT_Methodref constant pool items. + */ + final static int METH = 10; + + /** + * The type of CONSTANT_InterfaceMethodref constant pool items. + */ + final static int IMETH = 11; + + /** + * The type of CONSTANT_Integer constant pool items. + */ + final static int INT = 3; + + /** + * The type of CONSTANT_Float constant pool items. + */ + final static int FLOAT = 4; + + /** + * The type of CONSTANT_Long constant pool items. + */ + final static int LONG = 5; + + /** + * The type of CONSTANT_Double constant pool items. + */ + final static int DOUBLE = 6; + + /** + * The type of CONSTANT_NameAndType constant pool items. + */ + final static int NAME_TYPE = 12; + + /** + * The type of CONSTANT_Utf8 constant pool items. + */ + final static int UTF8 = 1; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Constructs a new {@link ClassReader} object. + * + * @param b the bytecode of the class to be read. + */ + private ClassReader(final byte[] b) { + this(b, 0); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param b the bytecode of the class to be read. + * @param off the start offset of the class data. + */ + private ClassReader(final byte[] b, final int off) { + this.b = b; + // parses the constant pool + items = new int[readUnsignedShort(off + 8)]; + int n = items.length; + strings = new String[n]; + int max = 0; + int index = off + 10; + for (int i = 1; i < n; ++i) { + items[i] = index + 1; + int size; + switch (b[index]) { + case FIELD: + case METH: + case IMETH: + case INT: + case FLOAT: + case NAME_TYPE: + size = 5; + break; + case LONG: + case DOUBLE: + size = 9; + ++i; + break; + case UTF8: + size = 3 + readUnsignedShort(index + 1); + if (size > max) { + max = size; + } + break; + // case HamConstants.CLASS: + // case HamConstants.STR: + default: + size = 3; + break; + } + index += size; + } + maxStringLength = max; + // the class header information starts just after the constant pool + header = index; + } + + + /** + * Constructs a new {@link ClassReader} object. + * + * @param is an input stream from which to read the class. + * @throws IOException if a problem occurs during reading. + */ + private ClassReader(final InputStream is) throws IOException { + this(readClass(is)); + } + + /** + * Reads the bytecode of a class. + * + * @param is an input stream from which to read the class. + * @return the bytecode read from the given input stream. + * @throws IOException if a problem occurs during reading. + */ + private static byte[] readClass(final InputStream is) throws IOException { + if (is == null) { + throw new IOException("Class not found"); + } + try { + byte[] b = new byte[is.available()]; + int len = 0; + while (true) { + int n = is.read(b, len, b.length - len); + if (n == -1) { + if (len < b.length) { + byte[] c = new byte[len]; + System.arraycopy(b, 0, c, 0, len); + b = c; + } + return b; + } + len += n; + if (len == b.length) { + int last = is.read(); + if (last < 0) { + return b; + } + byte[] c = new byte[b.length + 1000]; + System.arraycopy(b, 0, c, 0, len); + c[len++] = (byte) last; + b = c; + } + } + } finally { + try { + is.close (); + } catch (IOException ex) { + //ignore + } + } + } + + // ------------------------------------------------------------------------ + // Public methods + // ------------------------------------------------------------------------ + + /** + * Makes the given visitor visit the Java class of this {@link ClassReader}. + * This class is the one specified in the constructor (see + * {@link #ClassReader(byte[]) ClassReader}). + * + * @param classVisitor the visitor that must visit this class. + */ + private void accept(final TypeCollector classVisitor) { + char[] c = new char[maxStringLength]; // buffer used to read strings + int i, j, k; // loop variables + int u, v, w; // indexes in b + + String attrName; + int anns = 0; + int ianns = 0; + + // visits the header + u = header; + v = items[readUnsignedShort(u + 4)]; + int len = readUnsignedShort(u + 6); + w = 0; + u += 8; + for (i = 0; i < len; ++i) { + u += 2; + } + v = u; + i = readUnsignedShort(v); + v += 2; + for (; i > 0; --i) { + j = readUnsignedShort(v + 6); + v += 8; + for (; j > 0; --j) { + v += 6 + readInt(v + 2); + } + } + i = readUnsignedShort(v); + v += 2; + for (; i > 0; --i) { + j = readUnsignedShort(v + 6); + v += 8; + for (; j > 0; --j) { + v += 6 + readInt(v + 2); + } + } + + i = readUnsignedShort(v); + v += 2; + for (; i > 0; --i) { + v += 6 + readInt(v + 2); + } + + //annotations not needed. + + // visits the fields + i = readUnsignedShort(u); + u += 2; + for (; i > 0; --i) { + j = readUnsignedShort(u + 6); + u += 8; + for (; j > 0; --j) { + u += 6 + readInt(u + 2); + } + } + + // visits the methods + i = readUnsignedShort(u); + u += 2; + for (; i > 0; --i) { + // inlined in original ASM source, now a method call + u = readMethod(classVisitor, c, u); + } + + } + + private int readMethod(TypeCollector classVisitor, char[] c, int u) { + int v; + int w; + int j; + String attrName; + int k; + int access = readUnsignedShort(u); + String name = readUTF8(u + 2, c); + String desc = readUTF8(u + 4, c); + v = 0; + w = 0; + + // looks for Code and Exceptions attributes + j = readUnsignedShort(u + 6); + u += 8; + for (; j > 0; --j) { + attrName = readUTF8(u, c); + int attrSize = readInt(u + 2); + u += 6; + // tests are sorted in decreasing frequency order + // (based on frequencies observed on typical classes) + if (attrName.equals("Code")) { + v = u; + } + u += attrSize; + } + // reads declared exceptions + if (w == 0) { + } else { + w += 2; + for (j = 0; j < readUnsignedShort(w); ++j) { + w += 2; + } + } + + // visits the method's code, if any + MethodCollector mv = classVisitor.visitMethod(access, name, desc); + + if (mv != null && v != 0) { + int codeLength = readInt(v + 4); + v += 8; + + int codeStart = v; + int codeEnd = v + codeLength; + v = codeEnd; + + j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + v += 8; + } + // parses the local variable, line number tables, and code + // attributes + int varTable = 0; + int varTypeTable = 0; + j = readUnsignedShort(v); + v += 2; + for (; j > 0; --j) { + attrName = readUTF8(v, c); + if (attrName.equals("LocalVariableTable")) { + varTable = v + 6; + } else if (attrName.equals("LocalVariableTypeTable")) { + varTypeTable = v + 6; + } + v += 6 + readInt(v + 2); + } + + v = codeStart; + // visits the local variable tables + if (varTable != 0) { + if (varTypeTable != 0) { + k = readUnsignedShort(varTypeTable) * 3; + w = varTypeTable + 2; + int[] typeTable = new int[k]; + while (k > 0) { + typeTable[--k] = w + 6; // signature + typeTable[--k] = readUnsignedShort(w + 8); // index + typeTable[--k] = readUnsignedShort(w); // start + w += 10; + } + } + k = readUnsignedShort(varTable); + w = varTable + 2; + for (; k > 0; --k) { + int index = readUnsignedShort(w + 8); + mv.visitLocalVariable(readUTF8(w + 4, c), index); + w += 10; + } + } + } + return u; + } + + /** + * Reads an unsigned short value in {@link #b b}. This method is + * intended for Attribute sub classes, and is normally not needed by + * class generators or adapters. + * + * @param index the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + private int readUnsignedShort(final int index) { + byte[] b = this.b; + return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF); + } + + /** + * Reads a signed int value in {@link #b b}. This method is intended for + * Attribute sub classes, and is normally not needed by class + * generators or adapters. + * + * @param index the start index of the value to be read in {@link #b b}. + * @return the read value. + */ + private int readInt(final int index) { + byte[] b = this.b; + return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16) + | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF); + } + + /** + * Reads an UTF8 string constant pool item in {@link #b b}. This method + * is intended for Attribute sub classes, and is normally not needed + * by class generators or adapters. + * + * @param index the start index of an unsigned short value in {@link #b b}, + * whose value is the index of an UTF8 constant pool item. + * @param buf buffer to be used to read the item. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 item. + */ + private String readUTF8(int index, final char[] buf) { + int item = readUnsignedShort(index); + String s = strings[item]; + if (s != null) { + return s; + } + index = items[item]; + return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf); + } + + /** + * Reads UTF8 string in {@link #b b}. + * + * @param index start offset of the UTF8 string to be read. + * @param utfLen length of the UTF8 string to be read. + * @param buf buffer to be used to read the string. This buffer must be + * sufficiently large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 string. + */ + private String readUTF(int index, final int utfLen, final char[] buf) { + int endIndex = index + utfLen; + byte[] b = this.b; + int strLen = 0; + int c; + int st = 0; + char cc = 0; + while (index < endIndex) { + c = b[index++]; + switch (st) { + case 0: + c = c & 0xFF; + if (c < 0x80) { // 0xxxxxxx + buf[strLen++] = (char) c; + } else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx + cc = (char) (c & 0x1F); + st = 1; + } else { // 1110 xxxx 10xx xxxx 10xx xxxx + cc = (char) (c & 0x0F); + st = 2; + } + break; + + case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char + buf[strLen++] = (char) ((cc << 6) | (c & 0x3F)); + st = 0; + break; + + case 2: // byte 2 of 3-byte char + cc = (char) ((cc << 6) | (c & 0x3F)); + st = 1; + break; + } + } + return new String(buf, 0, strLen); + } + + } + +/*** + * Portions Copyright (c) 2007 Paul Hammant + * Portions copyright (c) 2000-2007 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + + /** + * A Java type. This class can be used to make it easier to manipulate type and + * method descriptors. + * + * @author Eric Bruneton + * @author Chris Nokleberg + */ + private static class Type { + + /** + * The sort of the void type. + */ + private final static int VOID = 0; + + /** + * The sort of the boolean type. + */ + private final static int BOOLEAN = 1; + + /** + * The sort of the char type. + */ + private final static int CHAR = 2; + + /** + * The sort of the byte type. + */ + private final static int BYTE = 3; + + /** + * The sort of the short type. + */ + private final static int SHORT = 4; + + /** + * The sort of the int type. + */ + private final static int INT = 5; + + /** + * The sort of the float type. + */ + private final static int FLOAT = 6; + + /** + * The sort of the long type. + */ + private final static int LONG = 7; + + /** + * The sort of the double type. + */ + private final static int DOUBLE = 8; + + /** + * The sort of array reference types. + */ + private final static int ARRAY = 9; + + /** + * The sort of object reference type. + */ + private final static int OBJECT = 10; + + /** + * The void type. + */ + private final static Type VOID_TYPE = new Type(VOID, null, ('V' << 24) + | (5 << 16) | (0 << 8) | 0, 1); + + /** + * The boolean type. + */ + private final static Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24) + | (0 << 16) | (5 << 8) | 1, 1); + + /** + * The char type. + */ + private final static Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24) + | (0 << 16) | (6 << 8) | 1, 1); + + /** + * The byte type. + */ + private final static Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24) + | (0 << 16) | (5 << 8) | 1, 1); + + /** + * The short type. + */ + private final static Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24) + | (0 << 16) | (7 << 8) | 1, 1); + + /** + * The int type. + */ + private final static Type INT_TYPE = new Type(INT, null, ('I' << 24) + | (0 << 16) | (0 << 8) | 1, 1); + + /** + * The float type. + */ + private final static Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24) + | (2 << 16) | (2 << 8) | 1, 1); + + /** + * The long type. + */ + private final static Type LONG_TYPE = new Type(LONG, null, ('J' << 24) + | (1 << 16) | (1 << 8) | 2, 1); + + /** + * The double type. + */ + private final static Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24) + | (3 << 16) | (3 << 8) | 2, 1); + + // ------------------------------------------------------------------------ + // Fields + // ------------------------------------------------------------------------ + + /** + * The sort of this Java type. + */ + private final int sort; + + /** + * A buffer containing the internal name of this Java type. This field is + * only used for reference types. + */ + private char[] buf; + + /** + * The offset of the internal name of this Java type in {@link #buf buf} or, + * for primitive types, the size, descriptor and getOpcode offsets for this + * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset + * for IALOAD or IASTORE, byte 3 the offset for all other instructions). + */ + private int off; + + /** + * The length of the internal name of this Java type. + */ + private final int len; + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + /** + * Constructs a primitive type. + * + * @param sort the sort of the primitive type to be constructed. + */ + private Type(final int sort) { + this.sort = sort; + this.len = 1; + } + + /** + * Constructs a reference type. + * + * @param sort the sort of the reference type to be constructed. + * @param buf a buffer containing the descriptor of the previous type. + * @param off the offset of this descriptor in the previous buffer. + * @param len the length of this descriptor. + */ + private Type(final int sort, final char[] buf, final int off, final int len) { + this.sort = sort; + this.buf = buf; + this.off = off; + this.len = len; + } + + + /** + * Returns the Java types corresponding to the argument types of the given + * method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the Java types corresponding to the argument types of the given + * method descriptor. + */ + private static Type[] getArgumentTypes(final String methodDescriptor) { + char[] buf = methodDescriptor.toCharArray(); + int off = 1; + int size = 0; + while (true) { + char car = buf[off++]; + if (car == ')') { + break; + } else if (car == 'L') { + while (buf[off++] != ';') { + } + ++size; + } else if (car != '[') { + ++size; + } + } + + Type[] args = new Type[size]; + off = 1; + size = 0; + while (buf[off] != ')') { + args[size] = getType(buf, off); + off += args[size].len + (args[size].sort == OBJECT ? 2 : 0); + size += 1; + } + return args; + } + + + /** + * Returns the Java type corresponding to the given type descriptor. + * + * @param buf a buffer containing a type descriptor. + * @param off the offset of this descriptor in the previous buffer. + * @return the Java type corresponding to the given type descriptor. + */ + private static Type getType(final char[] buf, final int off) { + int len; + switch (buf[off]) { + case 'V': + return VOID_TYPE; + case 'Z': + return BOOLEAN_TYPE; + case 'C': + return CHAR_TYPE; + case 'B': + return BYTE_TYPE; + case 'S': + return SHORT_TYPE; + case 'I': + return INT_TYPE; + case 'F': + return FLOAT_TYPE; + case 'J': + return LONG_TYPE; + case 'D': + return DOUBLE_TYPE; + case '[': + len = 1; + while (buf[off + len] == '[') { + ++len; + } + if (buf[off + len] == 'L') { + ++len; + while (buf[off + len] != ';') { + ++len; + } + } + return new Type(ARRAY, buf, off, len + 1); + // case 'L': + default: + len = 1; + while (buf[off + len] != ';') { + ++len; + } + return new Type(OBJECT, buf, off + 1, len - 1); + } + } + + // ------------------------------------------------------------------------ + // Accessors + // ------------------------------------------------------------------------ + + /** + * Returns the number of dimensions of this array type. This method should + * only be used for an array type. + * + * @return the number of dimensions of this array type. + */ + private int getDimensions() { + int i = 1; + while (buf[off + i] == '[') { + ++i; + } + return i; + } + + /** + * Returns the type of the elements of this array type. This method should + * only be used for an array type. + * + * @return Returns the type of the elements of this array type. + */ + private Type getElementType() { + return getType(buf, off + getDimensions()); + } + + /** + * Returns the name of the class corresponding to this type. + * + * @return the fully qualified name of the class corresponding to this type. + */ + private String getClassName() { + switch (sort) { + case VOID: + return "void"; + case BOOLEAN: + return "boolean"; + case CHAR: + return "char"; + case BYTE: + return "byte"; + case SHORT: + return "short"; + case INT: + return "int"; + case FLOAT: + return "float"; + case LONG: + return "long"; + case DOUBLE: + return "double"; + case ARRAY: + StringBuffer b = new StringBuffer(getElementType().getClassName()); + for (int i = getDimensions(); i > 0; --i) { + b.append("[]"); + } + return b.toString(); + // case OBJECT: + default: + return new String(buf, off, len).replace('/', '.'); + } + } + } +} diff --git a/swift-codec/src/main/java/com/facebook/swift/codec/metadata/ReflectionHelper.java b/swift-codec/src/main/java/com/facebook/swift/codec/metadata/ReflectionHelper.java index 3fbc55da..22c8b3b5 100644 --- a/swift-codec/src/main/java/com/facebook/swift/codec/metadata/ReflectionHelper.java +++ b/swift-codec/src/main/java/com/facebook/swift/codec/metadata/ReflectionHelper.java @@ -22,7 +22,6 @@ import com.google.common.reflect.TypeToken; import com.thoughtworks.paranamer.AdaptiveParanamer; import com.thoughtworks.paranamer.AnnotationParanamer; -import com.thoughtworks.paranamer.BytecodeReadingParanamer; import com.thoughtworks.paranamer.CachingParanamer; import com.thoughtworks.paranamer.Paranamer; diff --git a/swift-codec/src/test/java/com/facebook/swift/codec/metadata/TestBytecodeReaderParanamer.java b/swift-codec/src/test/java/com/facebook/swift/codec/metadata/TestBytecodeReaderParanamer.java new file mode 100644 index 00000000..fe754569 --- /dev/null +++ b/swift-codec/src/test/java/com/facebook/swift/codec/metadata/TestBytecodeReaderParanamer.java @@ -0,0 +1,52 @@ +package com.facebook.swift.codec.metadata; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.lang.reflect.Constructor; + +public class TestBytecodeReaderParanamer +{ + @Test + public void testExtractPrimiteAndArrayParameterNames() throws Exception + { + BytecodeReadingParanamer paranamer = new BytecodeReadingParanamer(); + Constructor constructor = Foo.class.getConstructor( + String.class, String[].class, + Foo.class, Foo[].class, + boolean.class, boolean[].class, + char.class, char[].class, + byte.class, byte[].class, + short.class, short[].class, + int.class, int[].class, + long.class, long[].class, + float.class, float[].class, + double.class, double[].class + ); + String[] parameterNames = paranamer.lookupParameterNames(constructor); + String[] expectedParameterNames = new String[]{ + "bar1", "bar2", "bar3", "bar4", "bar5", "bar6", "bar7", "bar8", "bar9", "bar10", + "bar11", "bar12", "bar13", "bar14", "bar15", "bar16", "bar17", "bar18", "bar19", "bar20" + }; + + Assert.assertEquals(parameterNames, expectedParameterNames); + } + + public static class Foo + { + public Foo( + String bar1, String[] bar2, + Foo bar3, Foo[] bar4, + boolean bar5, boolean[] bar6, + char bar7, char[] bar8, + byte bar9, byte[] bar10, + short bar11, short[] bar12, + int bar13, int[] bar14, + long bar15, long[] bar16, + float bar17, float[] bar18, + double bar19, double[] bar20 + ) + { + } + } +}