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

com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition java-analysis-impl library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.codeInspection.bytecodeAnalysis;

import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.ThreadLocalCachedValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis.LOG;
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;

/**
 * @author lambdamix
 */
public class BytecodeAnalysisConverter {

  // how many bytes are taken from class fqn digest
  public static final int CLASS_HASH_SIZE = 10;
  // how many bytes are taken from signature digest
  public static final int SIGNATURE_HASH_SIZE = 4;
  public static final int HASH_SIZE = CLASS_HASH_SIZE + SIGNATURE_HASH_SIZE;

  private static final ThreadLocalCachedValue HASHER_CACHE = new ThreadLocalCachedValue() {
    @Override
    public MessageDigest create() {
      try {
        return MessageDigest.getInstance("MD5");
      } catch (NoSuchAlgorithmException exception) {
        throw new RuntimeException(exception);
      }
    }

    @Override
    protected void init(MessageDigest value) {
      value.reset();
    }
  };

  public static MessageDigest getMessageDigest() throws NoSuchAlgorithmException {
    return HASHER_CACHE.getValue();
  }

  /**
   * Converts an equation over asm keys into equation over small hash keys.
   */
  @NotNull
  static DirectionResultPair convert(@NotNull Equation equation, @NotNull MessageDigest md) {
    ProgressManager.checkCanceled();

    Result rhs = equation.rhs;
    HResult hResult;
    if (rhs instanceof Final) {
      hResult = new HFinal(((Final)rhs).value);
    }
    else {
      Pending pending = (Pending)rhs;
      Set> sumOrigin = pending.sum;
      HComponent[] components = new HComponent[sumOrigin.size()];
      int componentI = 0;
      for (Product prod : sumOrigin) {
        HKey[] intProd = new HKey[prod.ids.size()];
        int idI = 0;
        for (Key key : prod.ids) {
          intProd[idI] = asmKey(key, md);
          idI++;
        }
        HComponent intIdComponent = new HComponent(prod.value, intProd);
        components[componentI] = intIdComponent;
        componentI++;
      }
      hResult = new HPending(components);
    }
    return new DirectionResultPair(mkDirectionKey(equation.id.direction), hResult);
  }

  /**
   * Converts an asm method key to a small hash key (HKey)
   */
  @NotNull
  public static HKey asmKey(@NotNull Key key, @NotNull MessageDigest md) {
    byte[] classDigest = md.digest(key.method.internalClassName.getBytes());
    md.update(key.method.methodName.getBytes());
    md.update(key.method.methodDesc.getBytes());
    byte[] sigDigest = md.digest();
    byte[] digest = new byte[HASH_SIZE];
    System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE);
    System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE);
    return new HKey(digest, mkDirectionKey(key.direction), key.stable);
  }

  /**
   * Converts a Psi method to a small hash key (HKey).
   * Returns null if conversion is impossible (something is not resolvable).
   */
  @Nullable
  public static HKey psiKey(@NotNull PsiMethod psiMethod, @NotNull Direction direction, @NotNull MessageDigest md) {
    final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false);
    if (psiClass == null) {
      return null;
    }
    byte[] classDigest = psiClassDigest(psiClass, md);
    if (classDigest == null) {
      return null;
    }
    byte[] sigDigest = methodDigest(psiMethod, md);
    if (sigDigest == null) {
      return null;
    }
    byte[] digest = new byte[HASH_SIZE];
    System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE);
    System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE);
    return new HKey(digest, mkDirectionKey(direction), true);
  }

  @Nullable
  private static byte[] psiClassDigest(@NotNull PsiClass psiClass, @NotNull MessageDigest md) {
    String descriptor = descriptor(psiClass, 0, false);
    if (descriptor == null) {
      return null;
    }
    return md.digest(descriptor.getBytes());
  }

  @Nullable
  private static byte[] methodDigest(@NotNull PsiMethod psiMethod, @NotNull MessageDigest md) {
    String descriptor = descriptor(psiMethod);
    if (descriptor == null) {
      return null;
    }
    return md.digest(descriptor.getBytes());
  }

  @Nullable
  private static String descriptor(@NotNull PsiMethod psiMethod) {
    StringBuilder sb = new StringBuilder();
    final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false);
    if (psiClass == null) {
      return null;
    }
    PsiClass outerClass = psiClass.getContainingClass();
    boolean isInnerClassConstructor = psiMethod.isConstructor() && (outerClass != null) && !psiClass.hasModifierProperty(PsiModifier.STATIC);
    PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
    PsiType returnType = psiMethod.getReturnType();

    sb.append(returnType == null ? "" : psiMethod.getName());
    sb.append('(');

    String desc;

    if (isInnerClassConstructor) {
      desc = descriptor(outerClass, 0, true);
      if (desc == null) {
        return null;
      }
      sb.append(desc);
    }
    for (PsiParameter parameter : parameters) {
      desc = descriptor(parameter.getType());
      if (desc == null) {
        return null;
      }
      sb.append(desc);
    }
    sb.append(')');
    if (returnType == null) {
      sb.append('V');
    } else {
      desc = descriptor(returnType);
      if (desc == null) {
        return null;
      } else {
        sb.append(desc);
      }
    }
    return sb.toString();
  }

  @Nullable
  private static String descriptor(@NotNull PsiClass psiClass, int dimensions, boolean full) {
    PsiFile containingFile = psiClass.getContainingFile();
    if (!(containingFile instanceof PsiClassOwner)) {
      LOG.debug("containingFile was not resolved for " + psiClass.getQualifiedName());
      return null;
    }
    PsiClassOwner psiFile = (PsiClassOwner)containingFile;
    String packageName = psiFile.getPackageName();
    String qname = psiClass.getQualifiedName();
    if (qname == null) {
      return null;
    }
    String className;
    if (packageName.length() > 0) {
      className = qname.substring(packageName.length() + 1).replace('.', '$');
    } else {
      className = qname.replace('.', '$');
    }
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < dimensions; i++) {
      sb.append('[');
    }
    if (full) {
      sb.append('L');
    }
    if (packageName.length() > 0) {
      sb.append(packageName.replace('.', '/'));
      sb.append('/');
    }
    sb.append(className);
    if (full) {
      sb.append(';');
    }
    return sb.toString();
  }

  @Nullable
  private static String descriptor(@NotNull PsiType psiType) {
    int dimensions = 0;
    psiType = TypeConversionUtil.erasure(psiType);
    if (psiType instanceof PsiArrayType) {
      PsiArrayType arrayType = (PsiArrayType)psiType;
      psiType = arrayType.getDeepComponentType();
      dimensions = arrayType.getArrayDimensions();
    }

    if (psiType instanceof PsiClassType) {
      PsiClass psiClass = ((PsiClassType)psiType).resolve();
      if (psiClass != null) {
        return descriptor(psiClass, dimensions, true);
      }
      else {
        LOG.debug("resolve was null for " + ((PsiClassType)psiType).getClassName());
        return null;
      }
    }
    else if (psiType instanceof PsiPrimitiveType) {
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < dimensions; i++) {
         sb.append('[');
      }
      if (PsiType.VOID.equals(psiType)) {
        sb.append('V');
      }
      else if (PsiType.BOOLEAN.equals(psiType)) {
        sb.append('Z');
      }
      else if (PsiType.CHAR.equals(psiType)) {
        sb.append('C');
      }
      else if (PsiType.BYTE.equals(psiType)) {
        sb.append('B');
      }
      else if (PsiType.SHORT.equals(psiType)) {
        sb.append('S');
      }
      else if (PsiType.INT.equals(psiType)) {
        sb.append('I');
      }
      else if (PsiType.FLOAT.equals(psiType)) {
        sb.append('F');
      }
      else if (PsiType.LONG.equals(psiType)) {
        sb.append('J');
      }
      else if (PsiType.DOUBLE.equals(psiType)) {
        sb.append('D');
      }
      return sb.toString();
    }
    return null;
  }


  /**
   * Converts Direction object to int.
   *
   * 0 - Out
   * 1 - NullableOut
   * 2 - Pure
   *
   * 3 - 0-th NOT_NULL
   * 4 - 0-th NULLABLE
   * ...
   *
   * 11 - 1-st NOT_NULL
   * 12 - 1-st NULLABLE
   *
   * @param dir direction of analysis
   * @return unique int for direction
   */
  static int mkDirectionKey(Direction dir) {
    if (dir == Out) {
      return 0;
    }
    else if (dir == NullableOut) {
      return 1;
    }
    else if (dir == Pure) {
      return 2;
    }
    else if (dir instanceof In) {
      In in = (In)dir;
      // nullity mask is 0/1
      return 3 + 8 * in.paramId() + in.nullityMask;
    }
    else {
      // valueId is [1-5]
      InOut inOut = (InOut)dir;
      return 3 + 8 * inOut.paramId() + 2 + inOut.valueId();
    }
  }

  /**
   * Converts int to Direction object.
   *
   * @param  directionKey int representation of direction
   * @return Direction object
   * @see    #mkDirectionKey(Direction)
   */
  @NotNull
  private static Direction extractDirection(int directionKey) {
    if (directionKey == 0) {
      return Out;
    }
    else if (directionKey == 1) {
      return NullableOut;
    }
    else if (directionKey == 2) {
      return Pure;
    }
    else {
      int paramKey = directionKey - 3;
      int paramId = paramKey / 8;
      // shifting first 3 values - now we have key [0 - 7]
      int subDirectionId = paramKey % 8;
      // 0 - 1 - @NotNull, @Nullable, parameter
      if (subDirectionId <= 1) {
        return new In(paramId, subDirectionId);
      }
      else {
        int valueId = subDirectionId - 2;
        return new InOut(paramId, Value.values()[valueId]);
      }
    }
  }

  /**
   * Given a PSI method and its primary HKey enumerate all contract keys for it.
   *
   * @param psiMethod psi method
   * @param primaryKey primary stable keys
   * @return corresponding (stable!) keys
   */
  @NotNull
  public static ArrayList mkInOutKeys(@NotNull PsiMethod psiMethod, @NotNull HKey primaryKey) {
    PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
    ArrayList keys = new ArrayList(parameters.length * 2 + 2);
    keys.add(primaryKey);
    keys.add(primaryKey.updateDirection(mkDirectionKey(Pure)));
    for (int i = 0; i < parameters.length; i++) {
      if (!(parameters[i].getType() instanceof PsiPrimitiveType)) {
        keys.add(primaryKey.updateDirection(mkDirectionKey(new InOut(i, Value.NotNull))));
        keys.add(primaryKey.updateDirection(mkDirectionKey(new InOut(i, Value.Null))));
      }
    }
    return keys;
  }

  /**
   * Given `solution` of all dependencies of a method with the `methodKey`, converts this solution into annotations.
   *
   * @param solution solution of equations
   * @param methodAnnotations annotations to which corresponding solutions should be added
   * @param methodKey a primary key of a method being analyzed. not it is stable
   * @param arity arity of this method (hint for constructing @Contract annotations)
   */
  public static void addMethodAnnotations(@NotNull HashMap solution, @NotNull MethodAnnotations methodAnnotations, @NotNull HKey methodKey, int arity) {
    List contractClauses = new ArrayList(arity * 2);
    Set notNulls = methodAnnotations.notNulls;
    Set pures = methodAnnotations.pures;
    Map contracts = methodAnnotations.contractsValues;

    for (Map.Entry entry : solution.entrySet()) {
      // NB: keys from Psi are always stable, so we need to stabilize keys from equations
      Value value = entry.getValue();
      if (value == Value.Top || value == Value.Bot) {
        continue;
      }
      HKey key = entry.getKey().mkStable();
      Direction direction = extractDirection(key.dirKey);
      HKey baseKey = key.mkBase();
      if (!methodKey.equals(baseKey)) {
        continue;
      }
      if (value == Value.NotNull && direction == Out) {
        notNulls.add(methodKey);
      }
      else if (value == Value.Pure && direction == Pure) {
        pures.add(methodKey);
      }
      else if (direction instanceof InOut) {
        contractClauses.add(contractElement(arity, (InOut)direction, value));
      }
    }

    if (!notNulls.contains(methodKey) && !contractClauses.isEmpty()) {
      // no contract clauses for @NotNull methods
      Collections.sort(contractClauses);
      StringBuilder sb = new StringBuilder("\"");
      StringUtil.join(contractClauses, ";", sb);
      sb.append('"');
      contracts.put(methodKey, sb.toString().intern());
    }

  }

  private static String contractValueString(@NotNull Value v) {
    switch (v) {
      case False: return "false";
      case True: return "true";
      case NotNull: return "!null";
      case Null: return "null";
      default: return "_";
    }
  }

  private static String contractElement(int arity, InOut inOut, Value value) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < arity; i++) {
      Value currentValue = Value.Top;
      if (i == inOut.paramIndex) {
        currentValue = inOut.inValue;
      }
      if (i > 0) {
        sb.append(',');
      }
      sb.append(contractValueString(currentValue));
    }
    sb.append("->");
    sb.append(contractValueString(value));
    return sb.toString();
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy