package net.jhorstmann.i18n.xgettext.asm; import org.objectweb.asm.tree.ClassNode; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.fedorahosted.tennera.jgettext.Message; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.LdcInsnNode; import java.util.List; import net.jhorstmann.i18n.tools.MessageBundle; import net.jhorstmann.i18n.tools.xgettext.MessageFunction; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.analysis.AnalyzerException; import org.objectweb.asm.tree.analysis.Interpreter; import org.objectweb.asm.tree.analysis.Value; import org.slf4j.LoggerFactory; import static org.objectweb.asm.Opcodes.*; import org.objectweb.asm.tree.InvokeDynamicInsnNode; public class ConstantTrackingInterpreter extends Interpreter { private static final Logger log = LoggerFactory.getLogger(ConstantTrackingInterpreter.class); private static final Value NULL_VALUE = new ConstantNullValue(); private static final Value INT_VALUE = new SimpleValue(Type.INT_TYPE); private static final Value LONG_VALUE = new SimpleValue(Type.LONG_TYPE); private static final Value FLOAT_VALUE = new SimpleValue(Type.FLOAT_TYPE); private static final Value DOUBLE_VALUE = new SimpleValue(Type.DOUBLE_TYPE); private static final Value REFERENCE_VALUE = new SimpleValue(Type.getObjectType("java/lang/Object")); private static final Value UNINITIALIZED_VALUE = new UninitializedValue(); private static final Value RETURN_ADDR_VALUE = new ReturnAddressValue(); private MessageBundle bundle; private Map<String, MessageFunction> functionByDesc; private ClassNode currentClass; private MethodNode currentMethod; private boolean srcRefPaths; public ConstantTrackingInterpreter( MessageBundle catalog, List<MessageFunction> functions, boolean srcRefPaths ) { super( 1 ); this.srcRefPaths = srcRefPaths; this.bundle = catalog; this.functionByDesc = new HashMap<String, MessageFunction>(); for (MessageFunction function : functions) { String key = function.getNamespace() + "." + function.getName() + function.getDescription(); log.debug("Adding function {}", key); if (functionByDesc.containsKey(key)) { log.error("Multiple definitions for MessageFunction {}" + key); } functionByDesc.put(key, function); } } public ClassNode getCurrentClass() { return currentClass; } public void setCurrentClass(ClassNode currentClass) { this.currentClass = currentClass; } public void setCurrentMethod(MethodNode methodNode) { this.currentMethod = methodNode; } public MethodNode getCurrentMethod() { return currentMethod; } public String getCurrentSourceReference() { String methodName = currentMethod == null ? null : (currentMethod.name + currentMethod.desc); if (currentClass == null) { return methodName; } else { return methodName == null ? currentClass.name : (currentClass.name + "." + methodName); } } @Override public Value newValue(Type type) { if (type == null) { return UNINITIALIZED_VALUE; } else { switch (type.getSort()) { case Type.VOID: return null; case Type.BOOLEAN: case Type.CHAR: case Type.BYTE: case Type.SHORT: case Type.INT: return INT_VALUE; case Type.FLOAT: return FLOAT_VALUE; case Type.LONG: return LONG_VALUE; case Type.DOUBLE: return DOUBLE_VALUE; case Type.ARRAY: return REFERENCE_VALUE; case Type.OBJECT: return new SimpleValue(type); default: throw new IllegalStateException("Unhandled type " + type); } } } @Override public Value newOperation(final AbstractInsnNode insn) throws AnalyzerException { int opcode = insn.getOpcode(); //System.out.println("newOperation(" + opcode + ")"); switch (opcode) { case ACONST_NULL: return NULL_VALUE; case ICONST_M1: case ICONST_0: case ICONST_1: case ICONST_2: case ICONST_3: case ICONST_4: case ICONST_5: return INT_VALUE; case LCONST_0: case LCONST_1: return LONG_VALUE; case FCONST_0: case FCONST_1: case FCONST_2: return FLOAT_VALUE; case DCONST_0: case DCONST_1: return DOUBLE_VALUE; case BIPUSH: case SIPUSH: return INT_VALUE; case LDC: Object cst = ((LdcInsnNode) insn).cst; if (cst instanceof String) { return new ConstantStringValue((String) cst); } else if (cst instanceof Type) { return new ConstantTypeValue((Type) cst); } else if (cst instanceof Integer) { return INT_VALUE; } else if (cst instanceof Long) { return LONG_VALUE; } else if (cst instanceof Float) { return FLOAT_VALUE; } else if (cst instanceof Double) { return DOUBLE_VALUE; } else { return newValue(Type.getType(cst.getClass())); } case JSR: return RETURN_ADDR_VALUE; case GETSTATIC: return newValue(Type.getType(((FieldInsnNode) insn).desc)); case NEW: return newValue(Type.getObjectType(((TypeInsnNode) insn).desc)); default: throw new IllegalStateException("Unhandled opcode " + opcode); } } @Override public Value copyOperation(AbstractInsnNode insn, Value value) throws AnalyzerException { return value; } @Override public Value unaryOperation(AbstractInsnNode insn, Value value) throws AnalyzerException { int opcode = insn.getOpcode(); //System.out.println("unaryOperation(" + opcode + ")"); switch (opcode) { case INEG: case IINC: case L2I: case F2I: case D2I: case I2B: case I2C: case I2S: return INT_VALUE; case FNEG: case I2F: case L2F: case D2F: return FLOAT_VALUE; case LNEG: case I2L: case F2L: case D2L: return LONG_VALUE; case DNEG: case I2D: case L2D: case F2D: return DOUBLE_VALUE; case GETFIELD: return newValue(Type.getType(((FieldInsnNode) insn).desc)); case NEWARRAY: case ANEWARRAY: return REFERENCE_VALUE; case ARRAYLENGTH: return INT_VALUE; case CHECKCAST: return newValue(Type.getObjectType(((TypeInsnNode) insn).desc)); case INSTANCEOF: return INT_VALUE; case IFEQ: case IFNE: case IFLT: case IFGE: case IFGT: case IFLE: case TABLESWITCH: case LOOKUPSWITCH: case IRETURN: case LRETURN: case FRETURN: case DRETURN: case ARETURN: case PUTSTATIC: case MONITORENTER: case MONITOREXIT: case IFNULL: case IFNONNULL: case ATHROW: return null; default: throw new IllegalStateException("Unhandled opcode " + opcode); } } @Override public Value binaryOperation(AbstractInsnNode insn, Value value1, Value value2) throws AnalyzerException { int opcode = insn.getOpcode(); switch (opcode) { case IALOAD: case BALOAD: case CALOAD: case SALOAD: case IADD: case ISUB: case IMUL: case IDIV: case IREM: case ISHL: case ISHR: case IUSHR: case IAND: case IOR: case IXOR: return INT_VALUE; case FALOAD: case FADD: case FSUB: case FMUL: case FDIV: case FREM: return FLOAT_VALUE; case LALOAD: case LADD: case LSUB: case LMUL: case LDIV: case LREM: case LSHL: case LSHR: case LUSHR: case LAND: case LOR: case LXOR: return LONG_VALUE; case DALOAD: case DADD: case DSUB: case DMUL: case DDIV: case DREM: return DOUBLE_VALUE; case AALOAD: return REFERENCE_VALUE; case LCMP: case FCMPL: case FCMPG: case DCMPL: case DCMPG: return INT_VALUE; case IF_ICMPEQ: case IF_ICMPNE: case IF_ICMPLT: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: case IF_ACMPEQ: case IF_ACMPNE: case PUTFIELD: return null; default: throw new IllegalStateException("Unhandled opcode " + opcode); } } @Override public Value ternaryOperation(AbstractInsnNode insn, Value value1, Value value2, Value value3) throws AnalyzerException { return null; } private static boolean equals(String str1, String str2) { return str1 == null ? str2 == null : str1.equals(str2); } private void extractMessage(MessageFunction function, String functionName, List values) { String sourceReference = getCurrentSourceReference(); int length = values.size(); int messageIdx = function.getMessageIndex(); int contextIdx = function.getContextIndex(); int pluralIdx = function.getPluralIndex(); String message = null; String context = null; String plural = null; if (messageIdx >= 0 && messageIdx < length) { AbstractValue value = (AbstractValue) values.get(messageIdx); if (value instanceof ConstantStringValue) { message = ((ConstantStringValue) value).getConstantValue(); } else { log.warn("Message id in parameter {} for function {} in class {} is not constant", new Object[]{messageIdx, functionName, sourceReference}); return; } } if (contextIdx >= 0 && contextIdx < length) { AbstractValue value = (AbstractValue) values.get(contextIdx); if (value instanceof ConstantStringValue) { context = ((ConstantStringValue) value).getConstantValue(); } else { log.warn("Message context in parameter {} for function {} in class {} is not constant", new Object[]{contextIdx, functionName, sourceReference}); return; } } if (pluralIdx >= 0 && pluralIdx < length) { AbstractValue value = (AbstractValue) values.get(pluralIdx); if (value instanceof ConstantStringValue) { plural = ((ConstantStringValue) value).getConstantValue(); } else { log.warn("Message plural in parameter {} for function {} in class {} is not constant", new Object[]{pluralIdx, functionName, sourceReference}); return; } } if (message != null) { Message msg = new Message(); msg.setMsgid(message); if (context != null) { msg.setMsgctxt(context); } if (plural != null) { msg.setMsgidPlural(plural); msg.addMsgstrPlural("", 0); } if (srcRefPaths && sourceReference != null) { msg.addSourceReference(sourceReference); } log.debug("Found message {}", msg); bundle.addMessage(msg); } } @Override public Value naryOperation(AbstractInsnNode insn, List values) throws AnalyzerException { int opcode = insn.getOpcode(); switch (opcode) { case INVOKEDYNAMIC: case MULTIANEWARRAY: return REFERENCE_VALUE; default: if (insn instanceof MethodInsnNode) { MethodInsnNode methodInsn = (MethodInsnNode) insn; String owner = methodInsn.owner; String name = methodInsn.name; String desc = methodInsn.desc; if (opcode == INVOKEVIRTUAL) { AbstractValue thisValue = (AbstractValue) values.get(0); if (thisValue instanceof ConstantTypeValue) { Type thisType = ((ConstantTypeValue) thisValue).getConstantType(); if ("java/lang/Class".equals(owner) && "()Ljava/lang/String;".equals(desc)) { if ("getName".equals(name)) { return new ConstantStringValue(thisType.getClassName()); } else if ("getSimpleName".equals(name)) { String className = thisType.getClassName(); int idx = Math.max(className.lastIndexOf("."), className.lastIndexOf('$')); return new ConstantStringValue(idx >= 0 ? className.substring(idx + 1) : className); } } } else if (thisValue instanceof ConstantStringValue) { String thisString = ((ConstantStringValue) thisValue).getConstantValue(); if ("java/lang/String".equals(owner)) { if ("concat".equals(name) && "(Ljava/lang/String;)Ljava/lang/String;".equals(desc)) { AbstractValue otherValue = (AbstractValue) values.get(1); if (otherValue instanceof ConstantStringValue) { String otherString = ((ConstantStringValue) otherValue).getConstantValue(); return new ConstantStringValue(thisString.concat(otherString)); } } } } } String key = owner + "." + name + desc; log.debug("Checking for functions matching {}", key); MessageFunction function = functionByDesc.get(key); if (function != null) { log.debug("Extracting messages from call to " + key); extractMessage(function, key, values); } return newValue(Type.getReturnType(methodInsn.desc)); } else { return REFERENCE_VALUE; } } } @Override public void returnOperation(AbstractInsnNode insn, Value value, Value expected) throws AnalyzerException { } @Override public Value merge(Value v, Value w) { if (v == null || !v.equals( w )) { return UNINITIALIZED_VALUE; } return v; } }