code: package com.github.sulir.runtimesearch.agent.analyzer;
  
  import com.github.sulir.runtimesearch.agent.transformer.ClassTransformer;
  import com.github.sulir.runtimesearch.agent.transformer.ObjectType;
  import org.objectweb.asm.ConstantDynamic;
  import org.objectweb.asm.Opcodes;
  import org.objectweb.asm.Type;
  import org.objectweb.asm.tree.*;
  import org.objectweb.asm.tree.analysis.AnalyzerException;
  import org.objectweb.asm.tree.analysis.BasicInterpreter;
  import org.objectweb.asm.tree.analysis.BasicValue;
  import org.objectweb.asm.tree.analysis.Interpreter;
  
  import java.util.List;
  
  /**
   * A bytecode interpreter similar to {@link BasicInterpreter}, but focused on the recognition of string/non-string
   * values.
   */
  public class IsStringInterpreter extends Interpreter<StringValue> implements Opcodes {
      private final BasicInterpreter basicInterpreter = new BasicInterpreter();
  
      public IsStringInterpreter() {
          super(ClassTransformer.ASM_VERSION);
      }
  
      @Override
      public StringValue newValue(Type type) {
          if (type == null)
              return StringValue.OTHER;
  
          switch (type.getSort()) {
              case Type.VOID:
                  return null;
              case Type.LONG:
              case Type.DOUBLE:
                  return StringValue.LONG_OR_DOUBLE;
              case Type.OBJECT:
                  if (new ObjectType(type).canBeString())
                      return StringValue.MAYBE_STRING;
                  else
                      return StringValue.OTHER;
              case Type.ARRAY:
                  if (newValue(type.getElementType()) == StringValue.MAYBE_STRING)
                      return StringValue.newMaybeStringArray(type.getDimensions());
                  else
                      return StringValue.OTHER;
              default:
                  return StringValue.OTHER;
          }
      }
  
      @Override
      public StringValue newOperation(AbstractInsnNode insn) {
          switch (insn.getOpcode()) {
              case LCONST_0:
              case LCONST_1:
              case DCONST_0:
              case DCONST_1:
                  return StringValue.LONG_OR_DOUBLE;
              case LDC:
                  Object constant = ((LdcInsnNode) insn).cst;
                  if (constant instanceof Long || constant instanceof Double)
                      return StringValue.LONG_OR_DOUBLE;
                  else if (constant instanceof String)
                      return StringValue.MAYBE_STRING;
                  else if (constant instanceof ConstantDynamic)
                      return newValue(Type.getType(((ConstantDynamic) constant).getDescriptor()));
                  else
                      return StringValue.OTHER;
              case GETSTATIC:
                  return newValue(Type.getType(((FieldInsnNode) insn).desc));
              case NEW:
                  String internalName = ((TypeInsnNode) insn).desc;
                  if (internalName.equals(ObjectType.STRING))
                      return StringValue.MAYBE_STRING;
                  else
                      return StringValue.OTHER;
              default:
                  return StringValue.OTHER;
          }
      }
  
      @Override
      public StringValue copyOperation(AbstractInsnNode insn, StringValue value) {
          return value;
      }
  
      @Override
      public StringValue unaryOperation(AbstractInsnNode insn, StringValue value) throws AnalyzerException {
          switch (insn.getOpcode()) {
              case GETFIELD:
                  return newValue(Type.getType(((FieldInsnNode) insn).desc));
              case NEWARRAY:
                  return StringValue.OTHER;
              case ANEWARRAY:
                  Type elementType = Type.getObjectType(((TypeInsnNode) insn).desc);
                  return newValue(Type.getType("[" + elementType.getDescriptor()));
              case CHECKCAST:
                  return newValue(Type.getObjectType(((TypeInsnNode) insn).desc));
              default:
                  BasicValue basicValue = basicInterpreter.unaryOperation(insn, null);
                  return StringValue.fromBasicValue(basicValue);
          }
      }
  
      @Override
      public StringValue binaryOperation(AbstractInsnNode insn, StringValue value1, StringValue value2)
              throws AnalyzerException {
          if (insn.getOpcode() == AALOAD) {
              return value1.removeDimension();
          } else {
              BasicValue basicValue = basicInterpreter.binaryOperation(insn, null, null);
              return StringValue.fromBasicValue(basicValue);
          }
      }
  
      @Override
      public StringValue ternaryOperation(AbstractInsnNode insn, StringValue value1, StringValue value2,
                                          StringValue value3) {
          return null;
      }
  
      @Override
      public StringValue naryOperation(AbstractInsnNode insn, List<? extends StringValue> values) {
          switch (insn.getOpcode()) {
              case MULTIANEWARRAY:
                  String descriptor = ((MultiANewArrayInsnNode) insn).desc;
                  return newValue(Type.getType(descriptor));
              case INVOKEDYNAMIC:
                  descriptor = ((InvokeDynamicInsnNode) insn).desc;
                  return newValue(Type.getReturnType(descriptor));
              default:
                  descriptor = ((MethodInsnNode) insn).desc;
                  return newValue(Type.getReturnType(descriptor));
          }
      }
  
      @Override
      public void returnOperation(AbstractInsnNode insn, StringValue value, StringValue expected) {
  
      }
  
      @Override
      public StringValue merge(StringValue value1, StringValue value2) {
          return value1.merge(value2);
      }
  }
  