com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-analysis-impl Show documentation
Show all versions of java-analysis-impl Show documentation
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