no gnu bytecode dependencies any more

git-svn-id: https://svn.code.sf.net/p/jode/code/trunk@137 379699f6-c40d-0410-875b-85095c16579e
stable
jochen 26 years ago
parent 0b95a6ce95
commit 074140743d
  1. 94
      jode/jode/bytecode/Opcodes.java
  2. 155
      jode/jode/decompiler/ClassAnalyzer.java
  3. 79
      jode/jode/decompiler/CodeAnalyzer.java
  4. 34
      jode/jode/decompiler/FieldAnalyzer.java
  5. 20
      jode/jode/decompiler/ImportHandler.java
  6. 45
      jode/jode/decompiler/LocalVariableTable.java
  7. 51
      jode/jode/decompiler/MethodAnalyzer.java
  8. 1
      jode/jode/expr/ConstructorOperator.java
  9. 1
      jode/jode/expr/GetFieldOperator.java
  10. 3
      jode/jode/expr/InvokeOperator.java
  11. 1
      jode/jode/expr/PutFieldOperator.java
  12. 9
      jode/jode/type/ArrayType.java
  13. 68
      jode/jode/type/ClassInterfacesType.java
  14. 18
      jode/jode/type/MethodType.java

@ -20,8 +20,7 @@
package jode; package jode;
import jode.flow.*; import jode.flow.*;
import java.io.*; import java.io.*;
import gnu.bytecode.CpoolRef; import jode.bytecode.ConstantPool;
import gnu.bytecode.CpoolClass;
/** /**
* This is an abstract class which creates flow blocks for the * This is an abstract class which creates flow blocks for the
@ -447,7 +446,8 @@ public abstract class Opcodes {
* @exception IOException if an read error occured. * @exception IOException if an read error occured.
* @exception ClassFormatError if an invalid opcode is detected. * @exception ClassFormatError if an invalid opcode is detected.
*/ */
public static StructuredBlock readOpcode(int addr, DataInputStream stream, public static StructuredBlock readOpcode(ConstantPool cpool,
int addr, DataInputStream stream,
CodeAnalyzer ca) CodeAnalyzer ca)
throws IOException, ClassFormatError throws IOException, ClassFormatError
{ {
@ -499,16 +499,16 @@ public abstract class Opcodes {
int index = stream.readUnsignedByte(); int index = stream.readUnsignedByte();
return createNormal return createNormal
(ca, addr, 2, new ConstOperator (ca, addr, 2, new ConstOperator
(ca.method.classAnalyzer.getConstantType(index), (cpool.getConstantType(index),
ca.method.classAnalyzer.getConstantString(index))); cpool.getConstantString(index)));
} }
case opc_ldc_w: case opc_ldc_w:
case opc_ldc2_w: { case opc_ldc2_w: {
int index = stream.readUnsignedShort(); int index = stream.readUnsignedShort();
return createNormal return createNormal
(ca, addr, 3, new ConstOperator (ca, addr, 3, new ConstOperator
(ca.method.classAnalyzer.getConstantType(index), (cpool.getConstantType(index),
ca.method.classAnalyzer.getConstantString(index))); cpool.getConstantString(index)));
} }
case opc_iload: case opc_lload: case opc_iload: case opc_lload:
case opc_fload: case opc_dload: case opc_aload: case opc_fload: case opc_dload: case opc_aload:
@ -719,63 +719,41 @@ public abstract class Opcodes {
(ca, addr, 1, new EmptyBlock(new Jump(-1))); (ca, addr, 1, new EmptyBlock(new Jump(-1)));
case opc_getstatic: case opc_getstatic:
case opc_getfield: { case opc_getfield: {
CpoolRef field = (CpoolRef)ca.method.classAnalyzer String[] ref = cpool.getRef(stream.readUnsignedShort());
.getConstant(stream.readUnsignedShort());
return createNormal return createNormal
(ca, addr, 3, new GetFieldOperator (ca, addr, 3, new GetFieldOperator
(ca, opcode == opc_getstatic, (ca, opcode == opc_getstatic,
Type.tClass(field.getCpoolClass().getName().getString()), Type.tClass(ref[0]), Type.tType(ref[2]), ref[1]));
Type.tType(field.getNameAndType().getType().getString()),
field.getNameAndType().getName().getString()));
} }
case opc_putstatic: case opc_putstatic:
case opc_putfield: { case opc_putfield: {
CpoolRef field = (CpoolRef)ca.method.classAnalyzer String[] ref = cpool.getRef(stream.readUnsignedShort());
.getConstant(stream.readUnsignedShort());
return createNormal return createNormal
(ca, addr, 3, new PutFieldOperator (ca, addr, 3, new PutFieldOperator
(ca, opcode == opc_putstatic, (ca, opcode == opc_putstatic,
Type.tClass(field.getCpoolClass().getName().getString()), Type.tClass(ref[0]), Type.tType(ref[2]), ref[1]));
Type.tType(field.getNameAndType().getType().getString()),
field.getNameAndType().getName().getString()));
} }
case opc_invokevirtual: case opc_invokevirtual:
case opc_invokespecial: case opc_invokespecial:
case opc_invokestatic : { case opc_invokestatic :
CpoolRef field = (CpoolRef)ca.method.classAnalyzer
.getConstant(stream.readUnsignedShort());
return createNormal
(ca, addr, 3, new InvokeOperator
(ca, opcode == opc_invokespecial,
Type.tClass(field.getCpoolClass()
.getName().getString()),
new MethodType(opcode == opc_invokestatic,
field.getNameAndType()
.getType().getString()),
field.getNameAndType().getName().getString()));
}
case opc_invokeinterface: { case opc_invokeinterface: {
CpoolRef field = (CpoolRef)ca.method.classAnalyzer.getConstant String[] ref = cpool.getRef(stream.readUnsignedShort());
(stream.readUnsignedShort());
StructuredBlock block = createNormal StructuredBlock block = createNormal
(ca, addr, 5, new InvokeOperator (ca, addr, opcode == opc_invokeinterface ? 5 : 3,
(ca, false, new InvokeOperator
Type.tClass(field.getCpoolClass() (ca, opcode == opc_invokespecial,
.getName().getString()), Type.tClass(ref[0]),
new MethodType(false, field.getNameAndType() new MethodType(opcode == opc_invokestatic, ref[2]),
.getType().getString()), ref[1]));
field.getNameAndType().getName().getString())); if (opcode == opc_invokeinterface)
int reserved = stream.readUnsignedShort(); stream.readUnsignedShort();
return block; return block;
} }
case opc_new: { case opc_new: {
CpoolClass cpcls = (CpoolClass) Type type = Type.tClassOrArray
ca.method.classAnalyzer.getConstant(stream.readUnsignedShort()); (cpool.getClassName(stream.readUnsignedShort()));
Type type = Type.tClassOrArray(cpcls.getName().getString());
type.useType(); type.useType();
return createNormal return createNormal(ca, addr, 3, new NewOperator(type));
(ca, addr, 3, new NewOperator(type));
} }
case opc_newarray: { case opc_newarray: {
Type type; Type type;
@ -796,10 +774,8 @@ public abstract class Opcodes {
(ca, addr, 2, new NewArrayOperator(Type.tArray(type), 1)); (ca, addr, 2, new NewArrayOperator(Type.tArray(type), 1));
} }
case opc_anewarray: { case opc_anewarray: {
CpoolClass cpcls = (CpoolClass) Type type = Type.tClassOrArray
ca.method.classAnalyzer.getConstant (cpool.getClassName(stream.readUnsignedShort()));
(stream.readUnsignedShort());
Type type = Type.tClassOrArray(cpcls.getName().getString());
type.useType(); type.useType();
return createNormal return createNormal
(ca, addr, 3, new NewArrayOperator(Type.tArray(type), 1)); (ca, addr, 3, new NewArrayOperator(Type.tArray(type), 1));
@ -813,19 +789,15 @@ public abstract class Opcodes {
new ThrowBlock(new NopOperator(Type.tUObject), new ThrowBlock(new NopOperator(Type.tUObject),
new Jump(-1))); new Jump(-1)));
case opc_checkcast: { case opc_checkcast: {
CpoolClass cpcls = (CpoolClass) Type type = Type.tClassOrArray
ca.method.classAnalyzer.getConstant (cpool.getClassName(stream.readUnsignedShort()));
(stream.readUnsignedShort());
Type type = Type.tClassOrArray(cpcls.getName().getString());
type.useType(); type.useType();
return createNormal return createNormal
(ca, addr, 3, new CheckCastOperator(type)); (ca, addr, 3, new CheckCastOperator(type));
} }
case opc_instanceof: { case opc_instanceof: {
CpoolClass cpcls = (CpoolClass) Type type = Type.tClassOrArray
ca.method.classAnalyzer.getConstant (cpool.getClassName(stream.readUnsignedShort()));
(stream.readUnsignedShort());
Type type = Type.tClassOrArray(cpcls.getName().getString());
type.useType(); type.useType();
return createNormal return createNormal
(ca, addr, 3, new InstanceOfOperator(type)); (ca, addr, 3, new InstanceOfOperator(type));
@ -877,10 +849,8 @@ public abstract class Opcodes {
} }
} }
case opc_multianewarray: { case opc_multianewarray: {
CpoolClass cpcls = (CpoolClass) Type type = Type.tClassOrArray
ca.method.classAnalyzer.getConstant (cpool.getClassName(stream.readUnsignedShort()));
(stream.readUnsignedShort());
Type type = Type.tClassOrArray(cpcls.getName().getString());
int dimension = stream.readUnsignedByte(); int dimension = stream.readUnsignedByte();
return createNormal return createNormal
(ca, addr, 4, (ca, addr, 4,

@ -18,32 +18,31 @@
*/ */
package jode; package jode;
import java.lang.reflect.Modifier; import jode.bytecode.ClassInfo;
import gnu.bytecode.ConstantPool; import jode.bytecode.FieldInfo;
import gnu.bytecode.CpoolEntry; import jode.bytecode.MethodInfo;
import gnu.bytecode.CpoolValue1; import jode.bytecode.ConstantPool;
import gnu.bytecode.CpoolValue2; import jode.bytecode.ClassFormatException;
import gnu.bytecode.CpoolString;
import jode.bytecode.ClassHierarchy;
import jode.flow.TransformConstructors; import jode.flow.TransformConstructors;
import java.lang.reflect.Modifier;
public class ClassAnalyzer implements Analyzer { public class ClassAnalyzer implements Analyzer {
JodeEnvironment env; JodeEnvironment env;
Analyzer[] analyzers; Analyzer[] analyzers;
MethodAnalyzer staticConstructor; MethodAnalyzer staticConstructor;
MethodAnalyzer[] constructors; MethodAnalyzer[] constructors;
ClassHierarchy clazz; ClassInfo clazz;
gnu.bytecode.ClassType classType;
ClassAnalyzer parent; ClassAnalyzer parent;
public ClassAnalyzer(ClassAnalyzer parent, ClassHierarchy clazz, public ClassAnalyzer(ClassAnalyzer parent, ClassInfo clazz,
JodeEnvironment env) JodeEnvironment env)
{ {
clazz.loadInfo(clazz.FULLINFO);
this.parent = parent; this.parent = parent;
this.clazz = clazz; this.clazz = clazz;
this.env = env; this.env = env;
this.classType = env.getClassType(clazz.getName());
} }
public boolean setFieldInitializer(String fieldName, Expression expr) { public boolean setFieldInitializer(String fieldName, Expression expr) {
@ -57,7 +56,7 @@ public class ClassAnalyzer implements Analyzer {
return false; return false;
} }
public ClassHierarchy getClazz() { public ClassInfo getClazz() {
return clazz; return clazz;
} }
@ -65,19 +64,27 @@ public class ClassAnalyzer implements Analyzer {
int numFields = 0; int numFields = 0;
int i = 0; int i = 0;
analyzers = new Analyzer[classType.getFieldCount() + FieldInfo[] fields = clazz.getFields();
classType.getMethodCount()]; MethodInfo[] methods = clazz.getMethods();
for (gnu.bytecode.Field field = classType.getFields(); if (fields == null) {
field != null; field = field.getNext()) { /* This means that the class could not be loaded.
analyzers[i] = new FieldAnalyzer(this, field, env); * give up.
*/
return;
}
analyzers = new Analyzer[fields.length +
methods.length];
for (int j=0; j < fields.length; j++) {
analyzers[i] = new FieldAnalyzer(this, fields[j], env);
analyzers[i++].analyze(); analyzers[i++].analyze();
} }
staticConstructor = null; staticConstructor = null;
java.util.Vector constrVector = new java.util.Vector(); java.util.Vector constrVector = new java.util.Vector();
for (gnu.bytecode.Method method = classType.getMethods(); for (int j=0; j < methods.length; j++) {
method != null; method = method.getNext()) { MethodAnalyzer analyzer =
MethodAnalyzer analyzer = new MethodAnalyzer(this, method, env); new MethodAnalyzer(this, methods[j], env);
analyzers[i++] = analyzer; analyzers[i++] = analyzer;
if (analyzer.isConstructor()) { if (analyzer.isConstructor()) {
@ -100,31 +107,33 @@ public class ClassAnalyzer implements Analyzer {
env.useClass(clazz.getName()); env.useClass(clazz.getName());
if (clazz.getSuperclass() != null) if (clazz.getSuperclass() != null)
env.useClass(clazz.getSuperclass().getName()); env.useClass(clazz.getSuperclass().getName());
ClassHierarchy[] interfaces = clazz.getInterfaces(); ClassInfo[] interfaces = clazz.getInterfaces();
for (int j=0; j< interfaces.length; j++) for (int j=0; j< interfaces.length; j++)
env.useClass(interfaces[j].getName()); env.useClass(interfaces[j].getName());
} }
public void dumpSource(TabbedPrintWriter writer) throws java.io.IOException public void dumpSource(TabbedPrintWriter writer) throws java.io.IOException
{ {
// if (cdef.getSource() != null) if (analyzers == null) {
// writer.println("/* Original source: "+cdef.getSource()+" */"); /* This means that the class could not be loaded.
* give up.
*/
return;
}
String modif = Modifier.toString(clazz.getModifiers() String modif = Modifier.toString(clazz.getModifiers()
& ~Modifier.SYNCHRONIZED); & ~Modifier.SYNCHRONIZED);
if (modif.length() > 0) if (modif.length() > 0)
writer.print(modif + " "); writer.print(modif + " ");
writer.print(clazz.isInterface() writer.print(clazz.isInterface()
? ""/*interface is in modif*/ ? ""/*interface is in modif*/ : "class ");
: "class ");
writer.println(env.classString(clazz.getName())); writer.println(env.classString(clazz.getName()));
writer.tab(); writer.tab();
ClassHierarchy superClazz = clazz.getSuperclass(); ClassInfo superClazz = clazz.getSuperclass();
if (superClazz != null && if (superClazz != null &&
superClazz != ClassHierarchy.javaLangObject) { superClazz != ClassInfo.javaLangObject) {
writer.println("extends "+env.classString(superClazz.getName())); writer.println("extends "+env.classString(superClazz.getName()));
} }
ClassHierarchy[] interfaces = clazz.getInterfaces(); ClassInfo[] interfaces = clazz.getInterfaces();
if (interfaces.length > 0) { if (interfaces.length > 0) {
writer.print(clazz.isInterface() ? "extends " : "implements "); writer.print(clazz.isInterface() ? "extends " : "implements ");
for (int i=0; i < interfaces.length; i++) { for (int i=0; i < interfaces.length; i++) {
@ -144,91 +153,8 @@ public class ClassAnalyzer implements Analyzer {
writer.println("}"); writer.println("}");
} }
public CpoolEntry getConstant(int i) { public ConstantPool getConstantPool() {
return classType.getConstant(i); return clazz.getConstantPool();
}
public Type getConstantType(int i)
throws ClassFormatError
{
CpoolEntry constant = getConstant(i);
switch(constant.getTag()) {
case ConstantPool.INTEGER: {
int value = ((CpoolValue1)constant).getValue();
return ((value < Short.MIN_VALUE || value > Character.MAX_VALUE)
? Type.tInt
: (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE)
? Type.tRange(Type.tInt, Type.tChar)
: Type.tUInt);
}
case ConstantPool.FLOAT : return Type.tFloat ;
case ConstantPool.LONG : return Type.tLong ;
case ConstantPool.DOUBLE : return Type.tDouble;
case ConstantPool.STRING : return Type.tString;
default:
throw new ClassFormatError("invalid constant type: "
+ constant.getTag());
}
}
private static String quoted(String str) {
StringBuffer result = new StringBuffer("\"");
for (int i=0; i< str.length(); i++) {
char c;
switch (c = str.charAt(i)) {
case '\0':
result.append("\\0");
break;
case '\t':
result.append("\\t");
break;
case '\n':
result.append("\\n");
break;
case '\r':
result.append("\\r");
break;
case '\\':
result.append("\\\\");
break;
case '\"':
result.append("\\\"");
break;
default:
if (c < 32) {
String oct = Integer.toOctalString(c);
result.append("\\000".substring(0, 4-oct.length()))
.append(oct);
} else if (c >= 32 && c < 127)
result.append(str.charAt(i));
else {
String hex = Integer.toHexString(c);
result.append("\\u0000".substring(0, 6-hex.length()))
.append(hex);
}
}
}
return result.append("\"").toString();
}
public String getConstantString(int i)
{
CpoolEntry constant = classType.getConstant(i);
switch (constant.getTag()) {
case ConstantPool.INTEGER:
return Integer.toString(((CpoolValue1)constant).getValue());
case ConstantPool.FLOAT:
return Float.toString
(Float.intBitsToFloat(((CpoolValue1)constant).getValue()));
case ConstantPool.LONG:
return Long.toString(((CpoolValue2)constant).getValue());
case ConstantPool.DOUBLE:
return Double.toString
(Double.longBitsToDouble(((CpoolValue2)constant).getValue()));
case ConstantPool.STRING:
return quoted(((CpoolString)constant).getString().getString());
}
throw new AssertError("unknown constant type");
} }
public String getTypeString(Type type) { public String getTypeString(Type type) {
@ -239,4 +165,3 @@ public class ClassAnalyzer implements Analyzer {
return type.toString() + " " + name; return type.toString() + " " + name;
} }
} }

@ -18,7 +18,10 @@
*/ */
package jode; package jode;
import jode.bytecode.ClassHierarchy; import jode.bytecode.ClassInfo;
import jode.bytecode.ConstantPool;
import jode.bytecode.AttributeInfo;
import jode.bytecode.CodeInfo;
import jode.flow.FlowBlock; import jode.flow.FlowBlock;
import jode.flow.TransformExceptionHandlers; import jode.flow.TransformExceptionHandlers;
@ -29,16 +32,10 @@ import java.io.DataInputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import gnu.bytecode.CodeAttr;
import gnu.bytecode.Attribute;
import gnu.bytecode.LocalVarsAttr;
import gnu.bytecode.CpoolClass;
import gnu.bytecode.Spy;
public class CodeAnalyzer implements Analyzer { public class CodeAnalyzer implements Analyzer {
FlowBlock methodHeader; FlowBlock methodHeader;
CodeAttr code; CodeInfo code;
MethodAnalyzer method; MethodAnalyzer method;
public JodeEnvironment env; public JodeEnvironment env;
@ -52,15 +49,14 @@ public class CodeAnalyzer implements Analyzer {
*/ */
public MethodAnalyzer getMethod() {return method;} public MethodAnalyzer getMethod() {return method;}
public CodeAnalyzer(MethodAnalyzer ma, CodeAttr bc, JodeEnvironment e) public CodeAnalyzer(MethodAnalyzer ma, CodeInfo bc, JodeEnvironment e)
throws ClassFormatError throws ClassFormatError
{ {
code = bc; code = bc;
method = ma; method = ma;
env = e; env = e;
LocalVarsAttr attr = AttributeInfo attr = code.findAttribute("LocalVariableTable");
(LocalVarsAttr) Attribute.get(bc, "LocalVariableTable");
if (attr != null) if (attr != null)
lvt = new LocalVariableTable(bc.getMaxLocals(), lvt = new LocalVariableTable(bc.getMaxLocals(),
method.classAnalyzer, attr); method.classAnalyzer, attr);
@ -81,9 +77,10 @@ public class CodeAnalyzer implements Analyzer {
* @param code The code array. * @param code The code array.
* @param handlers The exception handlers. * @param handlers The exception handlers.
*/ */
void readCode(byte[] code, short[] handlers) void readCode(byte[] code, int[] handlers)
throws ClassFormatError throws ClassFormatError
{ {
ConstantPool cpool = method.classAnalyzer.getConstantPool();
byte[] flags = new byte[code.length]; byte[] flags = new byte[code.length];
int[] lengths = new int[code.length]; int[] lengths = new int[code.length];
try { try {
@ -101,7 +98,8 @@ public class CodeAnalyzer implements Analyzer {
flags[succs[i]] |= PREDECESSORS; flags[succs[i]] |= PREDECESSORS;
} }
} catch (IOException ex) { } catch (IOException ex) {
throw new ClassFormatError(ex.toString()); ex.printStackTrace();
throw new ClassFormatError(ex.getMessage());
} }
for (int i=0; i<handlers.length; i += 4) { for (int i=0; i<handlers.length; i += 4) {
int start = handlers[i + 0]; int start = handlers[i + 0];
@ -114,6 +112,7 @@ public class CodeAnalyzer implements Analyzer {
FlowBlock[] instr = new FlowBlock[code.length]; FlowBlock[] instr = new FlowBlock[code.length];
int returnCount; int returnCount;
TransformExceptionHandlers excHandlers;
try { try {
DataInputStream stream = DataInputStream stream =
new DataInputStream(new ByteArrayInputStream(code)); new DataInputStream(new ByteArrayInputStream(code));
@ -127,7 +126,7 @@ public class CodeAnalyzer implements Analyzer {
FlowBlock lastBlock = null; FlowBlock lastBlock = null;
for (int addr = 0; addr < code.length; ) { for (int addr = 0; addr < code.length; ) {
jode.flow.StructuredBlock block jode.flow.StructuredBlock block
= Opcodes.readOpcode(addr, stream, this); = Opcodes.readOpcode(cpool, addr, stream, this);
if (jode.Decompiler.isVerbose && addr > mark) { if (jode.Decompiler.isVerbose && addr > mark) {
System.err.print('.'); System.err.print('.');
@ -148,36 +147,34 @@ public class CodeAnalyzer implements Analyzer {
} }
addr += lengths[addr]; addr += lengths[addr];
} }
} catch (IOException ex) {
throw new ClassFormatError(ex.toString());
}
for (int addr=0; addr<instr.length; ) { for (int addr=0; addr<instr.length; ) {
instr[addr].resolveJumps(instr); instr[addr].resolveJumps(instr);
addr = instr[addr].getNextAddr(); addr = instr[addr].getNextAddr();
}
TransformExceptionHandlers excHandlers
= new TransformExceptionHandlers(instr);
for (int i=0; i<handlers.length; i += 4) {
Type type = null;
int start = handlers[i + 0];
int end = handlers[i + 1];
int handler = handlers[i + 2];
if (start < 0) start += 65536;
if (end < 0) end += 65536;
if (handler < 0) handler += 65536;
if (handlers[i + 3 ] != 0) {
CpoolClass cpcls = (CpoolClass)
method.classAnalyzer.getConstant(handlers[i + 3]);
type = Type.tClass(cpcls.getName().getString());
} }
excHandlers.addHandler(start, end, handler, type); excHandlers = new TransformExceptionHandlers(instr);
for (int i=0; i<handlers.length; i += 4) {
Type type = null;
int start = handlers[i + 0];
int end = handlers[i + 1];
int handler = handlers[i + 2];
if (start < 0) start += 65536;
if (end < 0) end += 65536;
if (handler < 0) handler += 65536;
if (handlers[i + 3 ] != 0)
type = Type.tClass(cpool.getClassName(handlers[i + 3]));
excHandlers.addHandler(start, end, handler, type);
}
} catch (IOException ex) {
ex.printStackTrace();
throw new ClassFormatError(ex.getMessage());
} }
if (Decompiler.isVerbose) if (Decompiler.isVerbose)
System.err.print('-'); System.err.print('-');
excHandlers.analyze(); excHandlers.analyze();
methodHeader = instr[0]; methodHeader = instr[0];
methodHeader.analyze(); methodHeader.analyze();
@ -186,7 +183,7 @@ public class CodeAnalyzer implements Analyzer {
public void analyze() public void analyze()
{ {
byte[] codeArray = code.getCode(); byte[] codeArray = code.getCode();
short[] handlers = Spy.getExceptionHandlers(code); int[] handlers = code.getExceptionHandlers();
readCode(codeArray, handlers); readCode(codeArray, handlers);
Enumeration enum = allLocals.elements(); Enumeration enum = allLocals.elements();
while (enum.hasMoreElements()) { while (enum.hasMoreElements()) {
@ -238,7 +235,7 @@ public class CodeAnalyzer implements Analyzer {
return method.classAnalyzer.getTypeString(type); return method.classAnalyzer.getTypeString(type);
} }
public ClassHierarchy getClazz() { public ClassInfo getClazz() {
return method.classAnalyzer.clazz; return method.classAnalyzer.clazz;
} }
} }

@ -19,9 +19,9 @@
package jode; package jode;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import gnu.bytecode.Attribute; import jode.bytecode.FieldInfo;
import gnu.bytecode.MiscAttr; import jode.bytecode.AttributeInfo;
import gnu.bytecode.Spy; import jode.bytecode.ClassFormatException;
public class FieldAnalyzer implements Analyzer { public class FieldAnalyzer implements Analyzer {
ClassAnalyzer clazz; ClassAnalyzer clazz;
@ -31,30 +31,34 @@ public class FieldAnalyzer implements Analyzer {
String fieldName; String fieldName;
Expression constant; Expression constant;
public FieldAnalyzer(ClassAnalyzer cla, gnu.bytecode.Field fd, public FieldAnalyzer(ClassAnalyzer cla, FieldInfo fd,
JodeEnvironment e) JodeEnvironment e)
{ {
clazz = cla; clazz = cla;
env = e; env = e;
modifiers = Spy.getModifiers(fd); modifiers = fd.getModifiers();
type = Type.tType(fd.getSignature()); type = fd.getType();
fieldName = fd.getName(); fieldName = fd.getName();
constant = null; constant = null;
Attribute attribute = AttributeInfo attribute = fd.findAttribute("ConstantValue");
Attribute.get(clazz.classType.getField(fieldName),
"ConstantValue");
if (attribute != null) { if (attribute != null) {
try { try {
int index = Spy.getAttributeStream((MiscAttr)attribute) byte[] contents = attribute.getContents();
.readUnsignedShort(); if (contents.length != 2)
throw new AssertError("ConstantValue attribute"
+ " has wrong length");
int index = (contents[0] & 0xff) << 8 | (contents[1] & 0xff);
constant = new ConstOperator constant = new ConstOperator
(type.intersection(cla.getConstantType(index)), (type.intersection(cla.getConstantPool()
cla.getConstantString(index)); .getConstantType(index)),
cla.getConstantPool().getConstantString(index));
constant.makeInitializer(); constant.makeInitializer();
} catch (java.io.IOException ex) { } catch (ClassFormatException ex) {
throw new AssertError("attribute too small"); ex.printStackTrace();
throw new AssertError("ClassFormatException");
} }
} }
} }

@ -19,8 +19,7 @@
package jode; package jode;
import java.util.*; import java.util.*;
import jode.bytecode.ClassHierarchy; import jode.bytecode.ClassInfo;
import gnu.bytecode.ClassType;
public class JodeEnvironment { public class JodeEnvironment {
Hashtable imports; Hashtable imports;
@ -34,24 +33,13 @@ public class JodeEnvironment {
JodeEnvironment(String path) { JodeEnvironment(String path) {
classPath = new SearchPath(path); classPath = new SearchPath(path);
ClassHierarchy.setClassPath(classPath); ClassInfo.setClassPath(classPath);
Type.setEnvironment(this); Type.setEnvironment(this);
imports = new Hashtable(); imports = new Hashtable();
/* java.lang is always imported */ /* java.lang is always imported */
imports.put("java.lang.*", new Integer(Integer.MAX_VALUE)); imports.put("java.lang.*", new Integer(Integer.MAX_VALUE));
} }
public gnu.bytecode.ClassType getClassType(String clazzName) {
try {
return gnu.bytecode.ClassFileInput.readClassType
(classPath.getFile(clazzName.replace('.', '/')
+".class"));
} catch (java.io.IOException ex) {
ex.printStackTrace();
throw new RuntimeException("Class not found.");
}
}
/** /**
* Checks if the className conflicts with a class imported from * Checks if the className conflicts with a class imported from
* another package and must be fully qualified therefore. * another package and must be fully qualified therefore.
@ -172,9 +160,9 @@ public class JodeEnvironment {
public void doClass(String className) public void doClass(String className)
{ {
ClassHierarchy clazz; ClassInfo clazz;
try { try {
clazz = ClassHierarchy.forName(className); clazz = ClassInfo.forName(className);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
System.err.println("`"+className+"' is not a class name"); System.err.println("`"+className+"' is not a class name");
return; return;

@ -18,35 +18,40 @@
*/ */
package jode; package jode;
import java.util.Enumeration; import jode.bytecode.AttributeInfo;
import gnu.bytecode.LocalVarsAttr; import jode.bytecode.ConstantPool;
import gnu.bytecode.Variable; import java.io.*;
import gnu.bytecode.Spy;
public class LocalVariableTable { public class LocalVariableTable {
LocalVariableRangeList[] locals; LocalVariableRangeList[] locals;
public LocalVariableTable(int size, public LocalVariableTable(int size,
ClassAnalyzer cla, LocalVarsAttr attr) { ClassAnalyzer cla, AttributeInfo attr) {
locals = new LocalVariableRangeList[size]; locals = new LocalVariableRangeList[size];
for (int i=0; i<size; i++) for (int i=0; i<size; i++)
locals[i] = new LocalVariableRangeList(i); locals[i] = new LocalVariableRangeList(i);
Enumeration vars = attr.allVars(); ConstantPool constantPool = cla.getConstantPool();
while (vars.hasMoreElements()) {
Variable var = (Variable) vars.nextElement(); DataInputStream stream = new DataInputStream
(new ByteArrayInputStream(attr.getContents()));
int start = Spy.getStartPC(var); try {
int end = Spy.getEndPC(var); int count = stream.readUnsignedShort();
int slot = Spy.getSlot(var); for (int i=0; i < count; i++) {
String name = var.getName(); int start = stream.readUnsignedShort();
Type type = Type.tType(var.getType().getSignature()); int end = start + stream.readUnsignedShort();
locals[slot].addLocal(start, end-start, name, type); String name = constantPool.getUTF8(stream.readUnsignedShort());
if (Decompiler.showLVT) Type type = Type.tType(constantPool.getUTF8(stream.readUnsignedShort()));
System.err.println(name + ": " + type int slot = stream.readUnsignedShort();
+" range "+start+" - "+end locals[slot].addLocal(start, end-start, name, type);
+" slot "+slot); if (Decompiler.showLVT)
System.err.println(name + ": " + type
+" range "+start+" - "+end
+" slot "+slot);
}
} catch (IOException ex) {
ex.printStackTrace();
} }
} }

@ -18,13 +18,12 @@
*/ */
package jode; package jode;
import jode.bytecode.MethodInfo;
import jode.bytecode.AttributeInfo;
import jode.bytecode.CodeInfo;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import gnu.bytecode.Attribute; import java.io.*;
import gnu.bytecode.CodeAttr;
import gnu.bytecode.CpoolClass;
import gnu.bytecode.Method;
import gnu.bytecode.MiscAttr;
import gnu.bytecode.Spy;
public class MethodAnalyzer implements Analyzer { public class MethodAnalyzer implements Analyzer {
JodeEnvironment env; JodeEnvironment env;
@ -36,37 +35,45 @@ public class MethodAnalyzer implements Analyzer {
MethodType methodType; MethodType methodType;
Type[] exceptions; Type[] exceptions;
public MethodAnalyzer(ClassAnalyzer cla, Method mdef, public MethodAnalyzer(ClassAnalyzer cla, MethodInfo minfo,
JodeEnvironment env) { JodeEnvironment env) {
this.classAnalyzer = cla; this.classAnalyzer = cla;
this.env = env; this.env = env;
this.modifiers = Spy.getModifiers(mdef); this.modifiers = minfo.getModifiers();
this.methodType = new MethodType(mdef.getStaticFlag(), this.methodType = minfo.getType();
mdef.getSignature()); this.methodName = minfo.getName();
this.methodName = mdef.getName();
this.isConstructor = this.isConstructor =
methodName.equals("<init>") || methodName.equals("<clinit>"); methodName.equals("<init>") || methodName.equals("<clinit>");
Attribute codeattr = Attribute.get(mdef, "Code"); AttributeInfo codeattr = minfo.findAttribute("Code");
if (codeattr != null && codeattr instanceof CodeAttr) if (codeattr != null) {
code = new CodeAnalyzer(this, (CodeAttr) codeattr, env); DataInputStream stream = new DataInputStream
(new ByteArrayInputStream(codeattr.getContents()));
CodeInfo codeinfo = new CodeInfo();
try {
codeinfo.read(classAnalyzer.getConstantPool(), stream);
code = new CodeAnalyzer(this, codeinfo, env);
} catch (IOException ex) {
ex.printStackTrace();
code = null;
}
}
Attribute excattr = Attribute.get(mdef, "Exceptions"); AttributeInfo excattr = minfo.findAttribute("Exceptions");
if (excattr == null) { if (excattr == null) {
exceptions = new Type[0]; exceptions = new Type[0];
} else { } else {
java.io.DataInputStream stream = Spy.getAttributeStream DataInputStream stream = new DataInputStream
((MiscAttr) excattr); (new ByteArrayInputStream(excattr.getContents()));
try { try {
int throwCount = stream.readUnsignedShort(); int throwCount = stream.readUnsignedShort();
this.exceptions = new Type[throwCount]; this.exceptions = new Type[throwCount];
for (int t=0; t< throwCount; t++) { for (int t=0; t< throwCount; t++) {
int idx = stream.readUnsignedShort(); int idx = stream.readUnsignedShort();
CpoolClass cpcls = (CpoolClass) exceptions[t] = Type.tClass(classAnalyzer.getConstantPool()
classAnalyzer.getConstant(idx); .getClassName(idx));
exceptions[t] = Type.tClass(cpcls.getName().getString());
} }
} catch (java.io.IOException ex) { } catch (IOException ex) {
throw new AssertError("exception attribute too long?"); throw new AssertError("exception attribute too long?");
} }
} }
@ -133,7 +140,7 @@ public class MethodAnalyzer implements Analyzer {
} }
public void dumpSource(TabbedPrintWriter writer) public void dumpSource(TabbedPrintWriter writer)
throws java.io.IOException throws IOException
{ {
if (Decompiler.immediateOutput && code != null) { if (Decompiler.immediateOutput && code != null) {
// We do the code.analyze() here, to get // We do the code.analyze() here, to get

@ -18,7 +18,6 @@
*/ */
package jode; package jode;
import gnu.bytecode.CpoolRef;
public class ConstructorOperator extends Operator { public class ConstructorOperator extends Operator {
MethodType methodType; MethodType methodType;

@ -18,7 +18,6 @@
*/ */
package jode; package jode;
import gnu.bytecode.CpoolRef;
public class GetFieldOperator extends Operator { public class GetFieldOperator extends Operator {
boolean staticFlag; boolean staticFlag;

@ -18,8 +18,7 @@
*/ */
package jode; package jode;
import gnu.bytecode.CpoolRef; import jode.bytecode.ClassInfo;
import jode.bytecode.ClassHierarchy;
public final class InvokeOperator extends Operator { public final class InvokeOperator extends Operator {
CodeAnalyzer codeAnalyzer; CodeAnalyzer codeAnalyzer;

@ -18,7 +18,6 @@
*/ */
package jode; package jode;
import gnu.bytecode.CpoolRef;
public class PutFieldOperator extends StoreInstruction { public class PutFieldOperator extends StoreInstruction {
CodeAnalyzer codeAnalyzer; CodeAnalyzer codeAnalyzer;

@ -116,8 +116,15 @@ public class ArrayType extends Type {
return elementType.toString()+"[]"; return elementType.toString()+"[]";
} }
private static String pluralize(String singular) {
return singular +
((singular.endsWith("s") || singular.endsWith("x")
|| singular.endsWith("sh") || singular.endsWith("ch"))
? "es" : "s");
}
public String getDefaultName() { public String getDefaultName() {
return "arr_"+elementType.getDefaultName(); return pluralize(elementType.getDefaultName());
} }
public boolean equals(Object o) { public boolean equals(Object o) {

@ -17,7 +17,7 @@
* $Id$ * $Id$
*/ */
package jode; package jode;
import jode.bytecode.ClassHierarchy; import jode.bytecode.ClassInfo;
import java.util.Vector; import java.util.Vector;
import java.util.Stack; import java.util.Stack;
@ -35,45 +35,45 @@ import java.util.Stack;
* @author Jochen Hoenicke */ * @author Jochen Hoenicke */
public class ClassInterfacesType extends Type { public class ClassInterfacesType extends Type {
ClassHierarchy clazz; ClassInfo clazz;
ClassHierarchy ifaces[]; ClassInfo ifaces[];
public ClassHierarchy getClazz() { public ClassInfo getClazz() {
return clazz != null ? clazz : ClassHierarchy.javaLangObject; return clazz != null ? clazz : ClassInfo.javaLangObject;
} }
public ClassInterfacesType(String clazzName) { public ClassInterfacesType(String clazzName) {
super(TC_CLASS); super(TC_CLASS);
ClassHierarchy clazz = ClassHierarchy.forName(clazzName); ClassInfo clazz = ClassInfo.forName(clazzName);
if (clazz.isInterface()) { if (clazz.isInterface()) {
this.clazz = null; this.clazz = null;
ifaces = new ClassHierarchy[] {clazz}; ifaces = new ClassInfo[] {clazz};
} else { } else {
this.clazz = this.clazz =
(clazz == ClassHierarchy.javaLangObject) ? null : clazz; (clazz == ClassInfo.javaLangObject) ? null : clazz;
ifaces = new ClassHierarchy[0]; ifaces = new ClassInfo[0];
} }
} }
public ClassInterfacesType(ClassHierarchy clazz) { public ClassInterfacesType(ClassInfo clazz) {
super(TC_CLASS); super(TC_CLASS);
if (clazz.isInterface()) { if (clazz.isInterface()) {
this.clazz = null; this.clazz = null;
ifaces = new ClassHierarchy[] { clazz }; ifaces = new ClassInfo[] { clazz };
} else { } else {
this.clazz = this.clazz =
(clazz == ClassHierarchy.javaLangObject) ? null : clazz; (clazz == ClassInfo.javaLangObject) ? null : clazz;
ifaces = new ClassHierarchy[0]; ifaces = new ClassInfo[0];
} }
} }
public ClassInterfacesType(ClassHierarchy clazz, ClassHierarchy[] ifaces) { public ClassInterfacesType(ClassInfo clazz, ClassInfo[] ifaces) {
super(TC_CLASS); super(TC_CLASS);
this.clazz = clazz; this.clazz = clazz;
this.ifaces = ifaces; this.ifaces = ifaces;
} }
private static Type create(ClassHierarchy clazz, ClassHierarchy[] ifaces) { private static Type create(ClassInfo clazz, ClassInfo[] ifaces) {
/* Make sure that every {java.lang.Object} equals tObject */ /* Make sure that every {java.lang.Object} equals tObject */
if (ifaces.length == 0 && clazz == null) if (ifaces.length == 0 && clazz == null)
return tObject; return tObject;
@ -125,7 +125,7 @@ public class ClassInterfacesType extends Type {
* classes/interfaces that implement all bottom.ifaces. * classes/interfaces that implement all bottom.ifaces.
*/ */
ClassHierarchy clazz = this.clazz; ClassInfo clazz = this.clazz;
if (clazz != null) { if (clazz != null) {
for (int i=0; i < bottom.ifaces.length; i++) { for (int i=0; i < bottom.ifaces.length; i++) {
if (!bottom.ifaces[i].implementedBy(clazz)) { if (!bottom.ifaces[i].implementedBy(clazz)) {
@ -145,7 +145,7 @@ public class ClassInterfacesType extends Type {
} }
} }
ClassHierarchy[] ifaces = new ClassHierarchy[this.ifaces.length]; ClassInfo[] ifaces = new ClassInfo[this.ifaces.length];
int count = 0; int count = 0;
big_loop: big_loop:
for (int j=0; j < this.ifaces.length; j++) { for (int j=0; j < this.ifaces.length; j++) {
@ -157,7 +157,7 @@ public class ClassInterfacesType extends Type {
} }
if (count < ifaces.length) { if (count < ifaces.length) {
ClassHierarchy[] shortIfaces = new ClassHierarchy[count]; ClassInfo[] shortIfaces = new ClassInfo[count];
System.arraycopy(ifaces, 0, shortIfaces, 0, count); System.arraycopy(ifaces, 0, shortIfaces, 0, count);
ifaces = shortIfaces; ifaces = shortIfaces;
} else if (clazz == this.clazz) } else if (clazz == this.clazz)
@ -166,10 +166,10 @@ public class ClassInterfacesType extends Type {
} }
} }
private boolean implementsAllIfaces(ClassHierarchy[] otherIfaces) { private boolean implementsAllIfaces(ClassInfo[] otherIfaces) {
big: big:
for (int i=0; i < otherIfaces.length; i++) { for (int i=0; i < otherIfaces.length; i++) {
ClassHierarchy iface = otherIfaces[i]; ClassInfo iface = otherIfaces[i];
if (clazz != null && iface.implementedBy(clazz)) if (clazz != null && iface.implementedBy(clazz))
continue big; continue big;
for (int j=0; j < this.ifaces.length; j++) { for (int j=0; j < this.ifaces.length; j++) {
@ -197,7 +197,7 @@ public class ClassInterfacesType extends Type {
return tError; return tError;
ClassInterfacesType other = (ClassInterfacesType) type; ClassInterfacesType other = (ClassInterfacesType) type;
ClassHierarchy clazz; ClassInfo clazz;
/* First determine the clazz, one of the two classes must be a sub /* First determine the clazz, one of the two classes must be a sub
* class of the other or null. * class of the other or null.
@ -231,7 +231,7 @@ public class ClassInterfacesType extends Type {
Vector ifaces = new Vector(); Vector ifaces = new Vector();
big_loop_this: big_loop_this:
for (int i=0; i< this.ifaces.length; i++) { for (int i=0; i< this.ifaces.length; i++) {
ClassHierarchy iface = this.ifaces[i]; ClassInfo iface = this.ifaces[i];
if (clazz != null && iface.implementedBy(clazz)) { if (clazz != null && iface.implementedBy(clazz)) {
continue big_loop_this; continue big_loop_this;
} }
@ -248,12 +248,12 @@ public class ClassInterfacesType extends Type {
} }
big_loop_other: big_loop_other:
for (int i=0; i< other.ifaces.length; i++) { for (int i=0; i< other.ifaces.length; i++) {
ClassHierarchy iface = other.ifaces[i]; ClassInfo iface = other.ifaces[i];
if (clazz != null && iface.implementedBy(clazz)) { if (clazz != null && iface.implementedBy(clazz)) {
continue big_loop_other; continue big_loop_other;
} }
for (int j=0; j<ifaces.size(); j++) { for (int j=0; j<ifaces.size(); j++) {
if (iface.implementedBy((ClassHierarchy) if (iface.implementedBy((ClassInfo)
ifaces.elementAt(j))) { ifaces.elementAt(j))) {
continue big_loop_other; continue big_loop_other;
} }
@ -265,7 +265,7 @@ public class ClassInterfacesType extends Type {
ifaces.addElement(iface); ifaces.addElement(iface);
} }
ClassHierarchy[] ifaceArray = new ClassHierarchy[ifaces.size()]; ClassInfo[] ifaceArray = new ClassInfo[ifaces.size()];
ifaces.copyInto(ifaceArray); ifaces.copyInto(ifaceArray);
return create(clazz, ifaceArray); return create(clazz, ifaceArray);
} }
@ -285,7 +285,7 @@ public class ClassInterfacesType extends Type {
if (code != TC_CLASS) if (code != TC_CLASS)
return tError; return tError;
ClassInterfacesType other = (ClassInterfacesType) type; ClassInterfacesType other = (ClassInterfacesType) type;
ClassHierarchy clazz; ClassInfo clazz;
/* First the easy part, determine the clazz */ /* First the easy part, determine the clazz */
if (this.clazz == null || other.clazz == null) if (this.clazz == null || other.clazz == null)
@ -298,7 +298,7 @@ public class ClassInterfacesType extends Type {
break; break;
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }
if (clazz == ClassHierarchy.javaLangObject) if (clazz == ClassInfo.javaLangObject)
clazz = null; clazz = null;
} }
@ -317,9 +317,9 @@ public class ClassInterfacesType extends Type {
Stack allIfaces = new Stack(); Stack allIfaces = new Stack();
if (this.clazz != null) { if (this.clazz != null) {
ClassHierarchy c = this.clazz; ClassInfo c = this.clazz;
while (clazz != c) { while (clazz != c) {
ClassHierarchy clazzIfaces[] = c.getInterfaces(); ClassInfo clazzIfaces[] = c.getInterfaces();
for (int i=0; i<clazzIfaces.length; i++) for (int i=0; i<clazzIfaces.length; i++)
allIfaces.push(clazzIfaces[i]); allIfaces.push(clazzIfaces[i]);
c = c.getSuperclass(); c = c.getSuperclass();
@ -337,7 +337,7 @@ public class ClassInterfacesType extends Type {
*/ */
iface_loop: iface_loop:
while (!allIfaces.isEmpty()) { while (!allIfaces.isEmpty()) {
ClassHierarchy iface = (ClassHierarchy) allIfaces.pop(); ClassInfo iface = (ClassInfo) allIfaces.pop();
if ((clazz != null && iface.implementedBy(clazz)) if ((clazz != null && iface.implementedBy(clazz))
|| ifaces.contains(iface)) || ifaces.contains(iface))
/* We can skip this, as clazz or ifaces already imply it. /* We can skip this, as clazz or ifaces already imply it.
@ -358,12 +358,12 @@ public class ClassInterfacesType extends Type {
/* This interface is not implemented by any of the other /* This interface is not implemented by any of the other
* ifaces. Try its parent interfaces now. * ifaces. Try its parent interfaces now.
*/ */
ClassHierarchy clazzIfaces[] = iface.getInterfaces(); ClassInfo clazzIfaces[] = iface.getInterfaces();
for (int i=0; i<clazzIfaces.length; i++) for (int i=0; i<clazzIfaces.length; i++)
allIfaces.push(clazzIfaces[i]); allIfaces.push(clazzIfaces[i]);
} }
ClassHierarchy[] ifaceArray = new ClassHierarchy[ifaces.size()]; ClassInfo[] ifaceArray = new ClassInfo[ifaces.size()];
ifaces.copyInto(ifaceArray); ifaces.copyInto(ifaceArray);
return create(clazz, ifaceArray); return create(clazz, ifaceArray);
} }
@ -411,13 +411,13 @@ public class ClassInterfacesType extends Type {
} }
public String getDefaultName() { public String getDefaultName() {
ClassHierarchy type; ClassInfo type;
if (clazz != null) if (clazz != null)
type = clazz; type = clazz;
else if (ifaces.length > 0) else if (ifaces.length > 0)
type = ifaces[0]; type = ifaces[0];
else else
type = ClassHierarchy.javaLangObject; type = ClassInfo.javaLangObject;
String name = type.getName(); String name = type.getName();
int dot = name.lastIndexOf('.'); int dot = name.lastIndexOf('.');
if (dot >= 0) if (dot >= 0)

@ -24,12 +24,14 @@ package jode;
* @author Jochen Hoenicke * @author Jochen Hoenicke
*/ */
public class MethodType { public class MethodType {
final String signature;
final Type[] parameterTypes; final Type[] parameterTypes;
final Type returnType; final Type returnType;
final boolean staticFlag; final boolean staticFlag;
public MethodType(boolean isStatic, String signature) { public MethodType(boolean isStatic, String signature) {
this.staticFlag = isStatic; this.staticFlag = isStatic;
this.signature = signature;
int index = 1, types = 0; int index = 1, types = 0;
while (signature.charAt(index) != ')') { while (signature.charAt(index) != ')') {
types++; types++;
@ -68,16 +70,14 @@ public class MethodType {
return returnType; return returnType;
} }
public String toString() {
return signature;
}
public boolean equals(Object o) { public boolean equals(Object o) {
MethodType mt; MethodType mt;
if (!(o instanceof InvokeOperator) return ((o instanceof InvokeOperator)
|| !returnType.equals((mt = (MethodType)o).returnType) && signature.equals((mt = (MethodType)o).signature)
|| staticFlag != mt.staticFlag && staticFlag == mt.staticFlag);
|| parameterTypes.length != mt.parameterTypes.length)
return false;
for (int i=0; i<parameterTypes.length; i++)
if (!parameterTypes[i].equals(mt.parameterTypes))
return false;
return true;
} }
} }

Loading…
Cancel
Save