All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jetbrains.pluginverifier.verifiers.CodeAnalysisUtil.kt Maven / Gradle / Ivy

Go to download

Core classes of JetBrains Plugin Verifier with verification rules, general usage detection and bytecode verification engine

There is a newer version: 1.380
Show newest version
/*
 * Copyright 2000-2020 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
 */

package com.jetbrains.pluginverifier.verifiers

import com.jetbrains.pluginverifier.verifiers.resolution.Field
import com.jetbrains.pluginverifier.verifiers.resolution.Method
import com.jetbrains.pluginverifier.verifiers.resolution.MethodAsm
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.*
import org.objectweb.asm.tree.analysis.Analyzer
import org.objectweb.asm.tree.analysis.Frame
import org.objectweb.asm.tree.analysis.SourceInterpreter
import org.objectweb.asm.tree.analysis.SourceValue
import java.util.*

fun analyzeMethodFrames(method: Method): List>? =
  if (method is MethodAsm) {
    Analyzer(SourceInterpreter()).analyze(method.containingClassFile.name, method.asmNode).toList()
  } else {
    null
  }

fun Frame.getOnStack(index: Int): SourceValue? =
  getStack(stackSize - 1 - index)

class CodeAnalysis {

  private val inVisitMethods: Deque = LinkedList()

  private val inVisitFields: Deque = LinkedList()

  fun evaluateConstantString(
    analyzedMethod: Method,
    instructionIndex: Int,
    onStackIndex: Int
  ): String? {
    val frames = analyzeMethodFrames(analyzedMethod) ?: return null
    val frame = frames.getOrNull(instructionIndex) ?: return null
    val sourceValue = frame.getOnStack(onStackIndex) ?: return null
    return evaluateConstantString(analyzedMethod, frames, sourceValue)
  }

  fun evaluateConstantString(
    analyzedMethod: Method,
    frames: List>,
    sourceValue: SourceValue?
  ): String? {
    val sourceInstructions = sourceValue?.insns ?: return null
    if (sourceInstructions.size == 1) {
      val producer = sourceInstructions.first()
      return evaluateInstructionConstantString(producer, analyzedMethod, frames)
    }
    return null
  }


  private fun evaluateInstructionConstantString(
    instruction: AbstractInsnNode,
    analyzedMethod: Method,
    frames: List>
  ): String? {
    when (instruction) {
      is LdcInsnNode -> {
        if (instruction.cst is String) {
          return instruction.cst as String
        }
      }
      is MethodInsnNode -> {
        if (instruction.owner == "java/lang/StringBuilder" && instruction.name == "toString") {
          val instructions = analyzedMethod.instructions
          val toStringIndex = instructions.indexOf(instruction)
          if (toStringIndex == -1) {
            return null
          }
          val initIndex = instructions.take(toStringIndex).indexOfLast {
            it is MethodInsnNode && it.name == "" && it.owner == "java/lang/StringBuilder"
          }
          if (initIndex == -1) {
            return null
          }
          val stringBuildingInstructions = instructions.subList(initIndex + 1, toStringIndex)
          return evaluateConcatenatedStringValue(frames, analyzedMethod, stringBuildingInstructions)
        } else if (instruction.owner == analyzedMethod.containingClassFile.name) {
          val selfMethod = analyzedMethod.containingClassFile.methods.find {
            it.name == instruction.name && it.descriptor == instruction.desc
          }
          if (selfMethod != null) {
            val cantBeOverridden = selfMethod.isStatic || selfMethod.isPrivate || selfMethod.isFinal
            if (cantBeOverridden) {
              return evaluateConstantFunctionValue(selfMethod)
            }
          }
        }
      }
      is FieldInsnNode -> {
        if (instruction.owner == analyzedMethod.containingClassFile.name) {
          val fieldNode = analyzedMethod.containingClassFile.fields.find {
            it.name == instruction.name && it.descriptor == instruction.desc
          } ?: return null
          return evaluateConstantFieldValue(fieldNode)
        }
      }
    }
    return null
  }


  fun evaluateConstantFunctionValue(method: Method): String? {
    if (method.isAbstract || method.descriptor != "()Ljava/lang/String;") {
      return null
    }
    if (inVisitMethods.any { it.name == method.name && it.descriptor == method.descriptor && it.containingClassFile.name == method.containingClassFile.name }) {
      return null
    }
    inVisitMethods.addLast(method)
    try {
      val instructions = method.instructions.dropLastWhile { it is LabelNode || it is LineNumberNode }
      if (instructions.isEmpty()) {
        return null
      }
      val lastInstruction = instructions.last()
      if (lastInstruction !is InsnNode || lastInstruction.opcode != Opcodes.ARETURN) {
        return null
      }
      if (instructions.count { it is InsnNode && Opcodes.IRETURN <= it.opcode && it.opcode <= Opcodes.RETURN } > 1) {
        return null
      }
      return evaluateConstantString(method, instructions.size - 1, 0)
    } finally {
      inVisitMethods.removeLast()
    }
  }

  private fun evaluateConstantFieldValue(field: Field): String? {
    if (!field.isStatic || !field.isFinal || field.descriptor != "Ljava/lang/String;") {
      return null
    }

    if (field.initialValue is String) {
      return field.initialValue as String
    }

    if (inVisitFields.any { it.name == field.name && it.descriptor == field.descriptor && it.containingClassFile.name == field.containingClassFile.name }) {
      return null
    }
    inVisitFields.addLast(field)
    try {
      val classFile = field.containingClassFile
      val classInitializer = classFile.methods.find { it.name == "" } ?: return null
      val frames = analyzeMethodFrames(classInitializer) ?: return null

      val instructions = classInitializer.instructions
      val predicate: (AbstractInsnNode) -> Boolean = {
        it is FieldInsnNode
          && it.opcode == Opcodes.PUTSTATIC
          && it.owner == classFile.name
          && it.name == field.name
          && it.desc == field.descriptor
      }
      if (instructions.count(predicate) != 1) {
        return null
      }
      val putStaticInstructionIndex = instructions.indexOfLast(predicate)
      val frame = frames.getOrNull(putStaticInstructionIndex) ?: return null
      return evaluateConstantString(classInitializer, frames, frame.getOnStack(0))
    } finally {
      inVisitFields.removeLast()
    }
  }


  /**
   * Analyzes bytecode corresponding to String built with StringBuilder:
   * ```
   * LDC "One"
   * INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
   * ALOAD 0
   * INVOKEVIRTUAL some/SomeClass.someConstantFunction ()Ljava/lang/String;
   * INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
   * GETSTATIC     some/SomeClass.SOME_STATIC_FINAL_CONSTANT : Ljava/lang/String;
   * INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
   * ```
   *
   * It firstly splits the instructions by `StringBuilder.append()`:
   * ```
   * LDC         "One"
   * INVOKE      someConstantFunction()
   * GETSTATIC   SOME_STATIC_FINAL_CONSTANT
   * ```
   *
   * Then constructs the result: "One" + someConstantFunction() + SOME_STATIC_FINAL_CONSTANT
   */
  private fun evaluateConcatenatedStringValue(
    frames: List>,
    analyzedMethod: Method,
    stringBuildingInstructions: List
  ): String? {

    val isAppendInstruction = { ins: AbstractInsnNode ->
      ins is MethodInsnNode && ins.name == "append" && ins.owner == "java/lang/StringBuilder"
    }

    fun evaluateAppendInstructions(instructions: List): String? {
      if (instructions.isEmpty()) {
        return null
      }
      if (instructions.size == 1) {
        val single = instructions.single()
        return evaluateInstructionConstantString(single, analyzedMethod, frames)
      }
      if (instructions.size == 2) {
        val first = instructions.first()
        val second = instructions.last()
        if (first is VarInsnNode && first.`var` == 0 && !analyzedMethod.isStatic) {
          return evaluateInstructionConstantString(second, analyzedMethod, frames)
        }
      }
      return null
    }

    val stringResult = StringBuilder()
    val nonAppendInstructionParts = stringBuildingInstructions.splitByPredicate(isAppendInstruction)
    for (appendInstructionsPart in nonAppendInstructionParts) {
      val string = evaluateAppendInstructions(appendInstructionsPart)
        ?: return null
      stringResult.append(string)
    }

    return stringResult.toString()
  }

  private fun  List.splitByPredicate(predicate: (T) -> Boolean): List> {
    val result = arrayListOf>()
    val current = arrayListOf()
    for (t in this) {
      if (predicate(t)) {
        result += current.toList()
        current.clear()
      } else {
        current += t
      }
    }
    return result
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy