/******************************************************************************* * Copyright (c) 2002 - 2006 IBM Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package com.ibm.wala.util.strings; import static com.ibm.wala.types.TypeName.ArrayMask; import static com.ibm.wala.types.TypeName.ElementBits; import static com.ibm.wala.types.TypeName.PointerMask; import static com.ibm.wala.types.TypeName.ReferenceMask; import static com.ibm.wala.types.TypeReference.ArrayTypeCode; import static com.ibm.wala.types.TypeReference.PointerTypeCode; import static com.ibm.wala.types.TypeReference.ReferenceTypeCode; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import com.ibm.wala.classLoader.Language; import com.ibm.wala.types.ClassLoaderReference; import com.ibm.wala.types.MethodReference; import com.ibm.wala.types.TypeName; import com.ibm.wala.types.TypeReference; import com.ibm.wala.util.collections.HashMapFactory; /** * Some simple utilities used to manipulate Strings */ public class StringStuff { final private static HashMap<String, String> primitiveClassNames; static { primitiveClassNames = HashMapFactory.make(10); primitiveClassNames.put("int", "I"); primitiveClassNames.put("long", "J"); primitiveClassNames.put("short", "S"); primitiveClassNames.put("byte", "B"); primitiveClassNames.put("char", "C"); primitiveClassNames.put("double", "D"); primitiveClassNames.put("float", "F"); primitiveClassNames.put("boolean", "Z"); primitiveClassNames.put("void", "V"); } public static void padWithSpaces(StringBuffer b, int length) { if (b == null) { throw new IllegalArgumentException("b is null"); } if (b.length() < length) { for (int i = b.length(); i < length; i++) { b.append(" "); } } } /** * Translate a type from a deployment descriptor string into the internal JVM format * * eg. [[java/lang/String * * @throws IllegalArgumentException if dString is null */ public static String deployment2CanonicalTypeString(String dString) { if (dString == null) { throw new IllegalArgumentException("dString is null"); } dString = dString.replace('.', '/'); int arrayIndex = dString.indexOf("[]"); if (arrayIndex > -1) { String baseType = dString.substring(0, arrayIndex); int dim = (dString.length() - arrayIndex) / 2; baseType = deployment2CanonicalTypeString(baseType); StringBuffer result = new StringBuffer("["); for (int i = 1; i < dim; i++) { result.append("["); } result.append(baseType); return result.toString(); } else { if (primitiveClassNames.get(dString) != null) { return primitiveClassNames.get(dString); } else { return "L" + dString; } } } /** * Translate a type from a deployment descriptor string into the type expected for use in a method descriptor * * eg. [[Ljava.lang.String; * * @throws IllegalArgumentException if dString is null */ public static String deployment2CanonicalDescriptorTypeString(String dString) { if (dString == null) { throw new IllegalArgumentException("dString is null"); } dString = dString.replace('.', '/'); int arrayIndex = dString.indexOf("[]"); if (arrayIndex > -1) { String baseType = dString.substring(0, arrayIndex); int dim = (dString.length() - arrayIndex) / 2; baseType = deployment2CanonicalDescriptorTypeString(baseType); StringBuffer result = new StringBuffer("["); for (int i = 1; i < dim; i++) { result.append("["); } result.append(baseType); return result.toString(); } else { if (primitiveClassNames.get(dString) != null) { return primitiveClassNames.get(dString); } else { return "L" + dString + ";"; } } } public static final TypeName parseForReturnTypeName(String desc) throws IllegalArgumentException { return parseForReturnTypeName(Language.JAVA, ImmutableByteArray.make(desc)); } public static final TypeName parseForReturnTypeName(Language l, String desc) throws IllegalArgumentException { return parseForReturnTypeName(l, ImmutableByteArray.make(desc)); } /** * Parse method descriptor to obtain description of method's return type. TODO: tune this .. probably combine with * parseForParameters. * * @param b method descriptor - something like "(III)V" * @return type description * @throws IllegalArgumentException if b is null */ public static final TypeName parseForReturnTypeName(Language l, ImmutableByteArray b) throws IllegalArgumentException { if (b == null) { throw new IllegalArgumentException("b is null"); } if (b.length() <= 2) { throw new IllegalArgumentException("invalid descriptor: " + b); } if (b.get(0) != '(') { throw new IllegalArgumentException("invalid descriptor: " + b); } int i = 0; while (b.get(i++) != ')') ; if (b.length() < i + 1) { throw new IllegalArgumentException("invalid descriptor: " + b); } switch (b.get(i)) { case TypeReference.VoidTypeCode: return TypeReference.Void.getName(); case TypeReference.BooleanTypeCode: return TypeReference.Boolean.getName(); case TypeReference.ByteTypeCode: return TypeReference.Byte.getName(); case TypeReference.ShortTypeCode: return TypeReference.Short.getName(); case TypeReference.IntTypeCode: return TypeReference.Int.getName(); case TypeReference.LongTypeCode: return TypeReference.Long.getName(); case TypeReference.FloatTypeCode: return TypeReference.Float.getName(); case TypeReference.DoubleTypeCode: return TypeReference.Double.getName(); case TypeReference.CharTypeCode: return TypeReference.Char.getName(); case TypeReference.OtherPrimitiveTypeCode: if (b.get(b.length() - 1) == ';') { return l.lookupPrimitiveType(new String(b.substring(i + 1, b.length() - i - 2))); } else { return l.lookupPrimitiveType(new String(b.substring(i + 1, b.length() - i - 1))); } case TypeReference.ClassTypeCode: // fall through case TypeReference.ArrayTypeCode: if (b.get(b.length() - 1) == ';') { return TypeName.findOrCreate(b, i, b.length() - i - 1); } else { return TypeName.findOrCreate(b, i, b.length() - i); } default: throw new IllegalArgumentException("unexpected type in descriptor " + b); } } public static final TypeName[] parseForParameterNames(String descriptor) throws IllegalArgumentException { return parseForParameterNames(Language.JAVA, ImmutableByteArray.make(descriptor)); } public static final TypeName[] parseForParameterNames(Language l, String descriptor) throws IllegalArgumentException { return parseForParameterNames(l, ImmutableByteArray.make(descriptor)); } /** * Parse method descriptor to obtain descriptions of method's parameters. * * @return parameter descriptions, or null if there are no parameters * @throws IllegalArgumentException if b is null */ public static final TypeName[] parseForParameterNames(Language l, ImmutableByteArray b) throws IllegalArgumentException { if (b == null) { throw new IllegalArgumentException("b is null"); } if (b.length() <= 2) { throw new IllegalArgumentException("invalid descriptor: " + b); } if (b.get(0) != '(') { throw new IllegalArgumentException("invalid descriptor: " + b); } ArrayList<TypeName> sigs = new ArrayList<TypeName>(10); int i = 1; while (true) { switch (b.get(i++)) { case TypeReference.VoidTypeCode: sigs.add(TypeReference.VoidName); continue; case TypeReference.BooleanTypeCode: sigs.add(TypeReference.BooleanName); continue; case TypeReference.ByteTypeCode: sigs.add(TypeReference.ByteName); continue; case TypeReference.ShortTypeCode: sigs.add(TypeReference.ShortName); continue; case TypeReference.IntTypeCode: sigs.add(TypeReference.IntName); continue; case TypeReference.LongTypeCode: sigs.add(TypeReference.LongName); continue; case TypeReference.FloatTypeCode: sigs.add(TypeReference.FloatName); continue; case TypeReference.DoubleTypeCode: sigs.add(TypeReference.DoubleName); continue; case TypeReference.CharTypeCode: sigs.add(TypeReference.CharName); continue; case TypeReference.OtherPrimitiveTypeCode: { int off = i - 1; while (b.get(i++) != ';') ; sigs.add(l.lookupPrimitiveType(new String(b.substring(off + 1, i - off - 2)))); continue; } case TypeReference.ClassTypeCode: { int off = i - 1; while (b.get(i++) != ';') ; sigs.add(TypeName.findOrCreate(b, off, i - off - 1)); continue; } case TypeReference.ArrayTypeCode: case TypeReference.PointerTypeCode: case TypeReference.ReferenceTypeCode: { int off = i - 1; while (StringStuff.isTypeCodeChar(b, i)) { ++i; } TypeName T = null; byte c = b.get(i++); if (c == TypeReference.ClassTypeCode || c == TypeReference.OtherPrimitiveTypeCode) { while (b.get(i++) != ';') ; T = TypeName.findOrCreate(b, off, i - off - 1); } else { T = TypeName.findOrCreate(b, off, i - off); } sigs.add(T); continue; } case (byte) ')': // end of parameter list int size = sigs.size(); if (size == 0) { return null; } Iterator<TypeName> it = sigs.iterator(); TypeName[] result = new TypeName[size]; for (int j = 0; j < size; j++) { result[j] = it.next(); } return result; default: assert false : "bad descriptor " + b; } } } public static boolean isTypeCodeChar(ImmutableByteArray name, int i) { return name.b[i] == TypeReference.ArrayTypeCode || name.b[i] == TypeReference.PointerTypeCode || name.b[i] == TypeReference.ReferenceTypeCode; } /** * Given that name[start:start+length] is a Type name in JVM format, parse it for the package * * @return an ImmutableByteArray that represents the package, or null if it's the unnamed package * @throws IllegalArgumentException if name == null */ public static ImmutableByteArray parseForPackage(ImmutableByteArray name, int start, int length) throws IllegalArgumentException { try { if (name == null) { throw new IllegalArgumentException("name == null"); } int lastSlash = -1; for (int i = start; i < start + length; i++) { if (name.b[i] == '/') { lastSlash = i; } } if (lastSlash == -1) { return null; } short dim = 0; while (isTypeCodeChar(name, start+dim)) { dim++; } return new ImmutableByteArray(name.b, start + 1 + dim, lastSlash - start - 1 - dim); } catch (ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException("invalid name " + name + " start: " + start + " length: " + length); } } /** * Given that name[start:start+length] is a Type name in JVM format, parse it for the package * * @return an ImmutableByteArray that represents the package, or null if it's the unnamed package * @throws IllegalArgumentException if name is null */ public static ImmutableByteArray parseForPackage(ImmutableByteArray name) { if (name == null) { throw new IllegalArgumentException("name is null"); } return parseForPackage(name, 0, name.length()); } /** * Given that name[start:start+length] is a Type name in JVM format, strip the package and return the "package-free" class name * * TODO: inefficient; needs tuning. * * @return an ImmutableByteArray that represents the package, or null if it's the unnamed package * @throws IllegalArgumentException if name is null or malformed */ public static ImmutableByteArray parseForClass(ImmutableByteArray name, int start, int length) throws IllegalArgumentException { if (name == null) { throw new IllegalArgumentException("name is null"); } if (name.length() == 0) { throw new IllegalArgumentException("invalid class name: zero length"); } try { if (parseForPackage(name, start, length) == null) { while (isTypeCodeChar(name, start)) { start++; length--; } if (name.b[start] == 'L') { start++; length--; } return new ImmutableByteArray(name.b, start, length); } else { int lastSlash = 0; for (int i = start; i < start + length; i++) { if (name.b[i] == '/') { lastSlash = i; } } int L = length - (lastSlash - start + 1); return new ImmutableByteArray(name.b, lastSlash + 1, L); } } catch (ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException("Malformed name: " + name + " " + start + " " + length); } } /** * Given that name[start:start+length] is a Type name in JVM format, strip the package and return the "package-free" class name * * @return an ImmutableByteArray that represents the package, or null if it's the unnamed package * @throws IllegalArgumentException if name is null */ public static ImmutableByteArray parseForClass(ImmutableByteArray name) throws IllegalArgumentException { if (name == null) { throw new IllegalArgumentException("name is null"); } return parseForClass(name, 0, name.length()); } /** * Parse an array descriptor to obtain number of dimensions in corresponding array type. b: descriptor - something like * "[Ljava/lang/String;" or "[[I" * * @return dimensionality - something like "1" or "2" * @throws IllegalArgumentException if b == null */ public static int parseForArrayDimensionality(ImmutableByteArray b, int start, int length) throws IllegalArgumentException, IllegalArgumentException { if (b == null) { throw new IllegalArgumentException("b == null"); } try { int code = 0; for (int i = start; i < start + length; ++i) { if (isTypeCodeChar(b, i)) { code <<= ElementBits; switch (b.b[i]) { case ArrayTypeCode: code |= ArrayMask; break; case PointerTypeCode: code |= PointerMask; break; case ReferenceTypeCode: code |= ReferenceMask; break; default: throw new IllegalArgumentException("ill-formed array descriptor " + b); } } else { // type codes must be at the start of the descriptor; if we see something else, stop break; } } return code; } catch (ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException("ill-formed array descriptor " + b); } } /** * Parse an array descriptor to obtain number of dimensions in corresponding array type. b: descriptor - something like * "[Ljava/lang/String;" or "[[I" * * @return dimensionality - something like "Ljava/lang/String;" or "I" * @throws IllegalArgumentException if b is null */ public static ImmutableByteArray parseForInnermostArrayElementDescriptor(ImmutableByteArray b, int start, int length) { if (b == null) { throw new IllegalArgumentException("b is null"); } try { int i = start; for (; i < start + length; ++i) { if (! isTypeCodeChar(b, i)) { break; } } return new ImmutableByteArray(b.b, i, length - (i - start)); } catch (ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException("invalid element desciptor: " + b); } } /** * Parse an array descriptor to obtain number of dimensions in corresponding array type. b: descriptor - something like * "[Ljava/lang/String;" or "[[I" * * @return dimensionality - something like "Ljava/lang/String;" or "I" * @throws IllegalArgumentException if a is null */ public static ImmutableByteArray parseForInnermostArrayElementDescriptor(Atom a) { if (a == null) { throw new IllegalArgumentException("a is null"); } ImmutableByteArray b = new ImmutableByteArray(a.getValArray()); return parseForInnermostArrayElementDescriptor(b, 0, b.length()); } /** * @return true iff the class returned by parseForClass is primitive * @throws IllegalArgumentException if name is null */ public static boolean classIsPrimitive(ImmutableByteArray name, int start, int length) throws IllegalArgumentException { if (name == null) { throw new IllegalArgumentException("name is null"); } try { while (length > 0 && isTypeCodeChar(name, start)) { start++; length--; } if (start >= name.b.length) { throw new IllegalArgumentException("ill-formed type name: " + name); } return name.b[start] != 'L'; } catch (ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException(name.toString()); } } /** * @param methodSig something like "java_cup.lexer.advance()V" * @throws IllegalArgumentException if methodSig is null */ public static MethodReference makeMethodReference(String methodSig) throws IllegalArgumentException { return makeMethodReference(Language.JAVA, methodSig); } public static MethodReference makeMethodReference(Language l, String methodSig) throws IllegalArgumentException { if (methodSig == null) { throw new IllegalArgumentException("methodSig is null"); } if (methodSig.lastIndexOf('.') < 0) { throw new IllegalArgumentException("ill-formed sig " + methodSig); } String type = methodSig.substring(0, methodSig.lastIndexOf('.')); type = deployment2CanonicalTypeString(type); TypeReference t = TypeReference.findOrCreate(ClassLoaderReference.Application, type); String methodName = methodSig.substring(methodSig.lastIndexOf('.') + 1, methodSig.indexOf('(')); String desc = methodSig.substring(methodSig.indexOf('(')); return MethodReference.findOrCreate(l, t, methodName, desc); } /** * Convert a JVM encoded type name to a readable type name. * * @param jvmType a String containing a type name in JVM internal format. * @return the same type name in readable (source code) format. * @throws IllegalArgumentException if jvmType is null */ public static String jvmToReadableType(final String jvmType) throws IllegalArgumentException { if (jvmType == null) { throw new IllegalArgumentException("jvmType is null"); } StringBuffer readable = new StringBuffer(); // human readable version int numberOfDimensions = 0; // the number of array dimensions if (jvmType.length() == 0) { throw new IllegalArgumentException("ill-formed type : " + jvmType); } // cycle through prefixes of '[' char prefix = jvmType.charAt(0); while (prefix == '[') { numberOfDimensions++; prefix = jvmType.charAt(numberOfDimensions); } if (prefix == 'V') { readable.append("void"); } else if (prefix == 'B') { readable.append("byte"); } else if (prefix == 'C') { readable.append("char"); } else if (prefix == 'D') { readable.append("double"); } else if (prefix == 'F') { readable.append("float"); } else if (prefix == 'I') { readable.append("int"); } else if (prefix == 'J') { readable.append("long"); } else if (prefix == 'S') { readable.append("short"); } else if (prefix == 'Z') { readable.append("boolean"); } else if (prefix == 'L') { readable.append(jvmType.substring(numberOfDimensions + 1, // strip // all // leading // '[' & // 'L' jvmType.length()) // Trim off the trailing ';' ); // Convert to standard Java dot-notation readable = new StringBuffer(slashToDot(readable.toString())); readable = new StringBuffer(dollarToDot(readable.toString())); } // append trailing "[]" for each array dimension for (int i = 0; i < numberOfDimensions; ++i) { readable.append("[]"); } return readable.toString(); } /** * Convert a JVM encoded type name to a binary type name. This version does not call dollarToDot. * * @param jvmType a String containing a type name in JVM internal format. * @return the same type name in readable (source code) format. * @throws IllegalArgumentException if jvmType is null */ public static String jvmToBinaryName(String jvmType) throws IllegalArgumentException { if (jvmType == null) { throw new IllegalArgumentException("jvmType is null"); } StringBuffer readable = new StringBuffer(); // human readable version int numberOfDimensions = 0; // the number of array dimensions if (jvmType.length() == 0) { throw new IllegalArgumentException("ill-formed type : " + jvmType); } // cycle through prefixes of '[' char prefix = jvmType.charAt(0); while (prefix == '[') { numberOfDimensions++; prefix = jvmType.charAt(numberOfDimensions); } if (prefix == 'V') { readable.append("void"); } else if (prefix == 'B') { readable.append("byte"); } else if (prefix == 'C') { readable.append("char"); } else if (prefix == 'D') { readable.append("double"); } else if (prefix == 'F') { readable.append("float"); } else if (prefix == 'I') { readable.append("int"); } else if (prefix == 'J') { readable.append("long"); } else if (prefix == 'S') { readable.append("short"); } else if (prefix == 'Z') { readable.append("boolean"); } else if (prefix == 'L') { readable.append(jvmType.substring(numberOfDimensions + 1, // strip // all // leading // '[' & // 'L' jvmType.length()) // Trim off the trailing ';' ); // Convert to standard Java dot-notation readable = new StringBuffer(slashToDot(readable.toString())); } // append trailing "[]" for each array dimension for (int i = 0; i < numberOfDimensions; ++i) { readable.append("[]"); } return readable.toString(); } /** * Convert '/' to '.' in names. * * @return a String object obtained by replacing the forward slashes ('/') in the String passed as argument with ('.'). * @throws IllegalArgumentException if path is null */ public static String slashToDot(String path) { if (path == null) { throw new IllegalArgumentException("path is null"); } StringBuffer dotForm = new StringBuffer(path); // replace all '/' in the path with '.' for (int i = 0; i < dotForm.length(); ++i) { if (dotForm.charAt(i) == '/') { dotForm.setCharAt(i, '.'); // replace '/' with '.' } } return dotForm.toString(); } /** * Convert '$' to '.' in names. * * @param path a string object in which dollar signs('$') must be converted to dots ('.'). * @return a String object obtained by replacing the dollar signs ('S') in the String passed as argument with ('.'). * @throws IllegalArgumentException if path is null */ public static String dollarToDot(String path) { if (path == null) { throw new IllegalArgumentException("path is null"); } StringBuffer dotForm = new StringBuffer(path); // replace all '$' in the path with '.' for (int i = 0; i < dotForm.length(); ++i) { if (dotForm.charAt(i) == '$') { dotForm.setCharAt(i, '.'); // replace '$' with '.' } } return dotForm.toString(); } /** * Convert '.' to '$' in names. * * @param path String object in which dollar signs('$') must be converted to dots ('.'). * @return a String object obtained by replacing the dollar signs ('S') in the String passed as argument with ('.'). * @throws IllegalArgumentException if path is null */ public static String dotToDollar(String path) { if (path == null) { throw new IllegalArgumentException("path is null"); } StringBuffer dotForm = new StringBuffer(path); // replace all '.' in the path with '$' for (int i = 0; i < dotForm.length(); ++i) { if (dotForm.charAt(i) == '.') { dotForm.setCharAt(i, '$'); // replace '$' with '.' } } return dotForm.toString(); } /** * Return the right position of the string up to '.' or '/' stripping ';' */ public static String toBasename(String typeName) { int start = 0; int stop = typeName.length() - 1; if (typeName.contains(".")) { start = typeName.lastIndexOf("."); } else if (typeName.contains("/")) { start = typeName.lastIndexOf("/"); } if (typeName.endsWith(";")) { stop--; } return typeName.substring(start, stop); } }