mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-30 10:20:15 +07:00
1. Lambda/method reference creation is pure 2. String concatenation is pure 3. Constructor which only modifies own fields (calls setters, etc.) is pure 4. Exception creation is pure 5. A few hardcoded native methods
246 lines
11 KiB
Java
246 lines
11 KiB
Java
/*
|
|
* Copyright 2000-2016 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.codeInsight.AnnotationUtil;
|
|
import com.intellij.codeInspection.bytecodeAnalysis.asm.LeakingParameters;
|
|
import com.intellij.codeInspection.bytecodeAnalysis.data.*;
|
|
import com.intellij.openapi.util.io.FileUtil;
|
|
import com.intellij.openapi.vfs.LocalFileSystem;
|
|
import com.intellij.openapi.vfs.VirtualFile;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.search.GlobalSearchScope;
|
|
import com.intellij.testFramework.PsiTestUtil;
|
|
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
|
|
import com.intellij.util.ArrayUtil;
|
|
import org.jetbrains.annotations.Contract;
|
|
import org.jetbrains.org.objectweb.asm.*;
|
|
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
|
|
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
|
|
import org.junit.Assert;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.lang.annotation.Annotation;
|
|
import java.lang.reflect.Constructor;
|
|
import java.security.MessageDigest;
|
|
import java.util.HashMap;
|
|
import java.util.stream.Stream;
|
|
|
|
/**
|
|
* @author lambdamix
|
|
*/
|
|
public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
|
public static final String ORG_JETBRAINS_ANNOTATIONS_CONTRACT = Contract.class.getName();
|
|
private final String myClassesProjectRelativePath = "/classes/" + Test01.class.getPackage().getName().replace('.', '/');
|
|
private JavaPsiFacade myJavaPsiFacade;
|
|
private ProjectBytecodeAnalysis myBytecodeAnalysisService;
|
|
private MessageDigest myMessageDigest;
|
|
|
|
|
|
@Override
|
|
protected void setUp() throws Exception {
|
|
super.setUp();
|
|
myJavaPsiFacade = JavaPsiFacade.getInstance(myModule.getProject());
|
|
myBytecodeAnalysisService = ProjectBytecodeAnalysis.getInstance(myModule.getProject());
|
|
myMessageDigest = MessageDigest.getInstance("MD5");
|
|
setUpDataClasses();
|
|
}
|
|
|
|
@Override
|
|
protected void tearDown() throws Exception {
|
|
myJavaPsiFacade = null;
|
|
myBytecodeAnalysisService = null;
|
|
myMessageDigest = null;
|
|
super.tearDown();
|
|
}
|
|
|
|
public void testInference() throws IOException {
|
|
checkAnnotations(Test01.class);
|
|
checkAnnotations(Test02.class);
|
|
checkAnnotations(Test03.class);
|
|
}
|
|
|
|
public void testConverter() throws IOException {
|
|
checkCompoundIds(Test01.class);
|
|
checkCompoundIds(TestConverterData.class);
|
|
checkCompoundIds(TestConverterData.StaticNestedClass.class);
|
|
checkCompoundIds(TestConverterData.InnerClass.class);
|
|
checkCompoundIds(TestConverterData.GenericStaticNestedClass.class);
|
|
checkCompoundIds(TestAnnotation.class);
|
|
}
|
|
|
|
public void testLeakingParametersAnalysis() throws IOException {
|
|
checkLeakingParameters(LeakingParametersData.class);
|
|
}
|
|
|
|
private static void checkLeakingParameters(Class<?> jClass) throws IOException {
|
|
final HashMap<Method, boolean[]> map = new HashMap<>();
|
|
|
|
// collecting leakedParameters
|
|
final ClassReader classReader = new ClassReader(jClass.getResourceAsStream("/" + jClass.getName().replace('.', '/') + ".class"));
|
|
classReader.accept(new ClassVisitor(Opcodes.API_VERSION) {
|
|
@Override
|
|
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
|
final MethodNode node = new MethodNode(Opcodes.API_VERSION, access, name, desc, signature, exceptions);
|
|
final Method method = new Method(classReader.getClassName(), name, desc);
|
|
return new MethodVisitor(Opcodes.API_VERSION, node) {
|
|
@Override
|
|
public void visitEnd() {
|
|
super.visitEnd();
|
|
try {
|
|
map.put(method, LeakingParameters.build(classReader.getClassName(), node, false).parameters);
|
|
}
|
|
catch (AnalyzerException ignore) {}
|
|
}
|
|
};
|
|
}
|
|
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
|
|
|
for (java.lang.reflect.Method jMethod : jClass.getDeclaredMethods()) {
|
|
Method method = new Method(Type.getType(jClass).getInternalName(), jMethod.getName(), Type.getMethodDescriptor(jMethod));
|
|
Annotation[][] annotations = jMethod.getParameterAnnotations();
|
|
for (int i = 0; i < annotations.length; i++) {
|
|
boolean isLeaking = false;
|
|
Annotation[] parameterAnnotations = annotations[i];
|
|
for (Annotation parameterAnnotation : parameterAnnotations) {
|
|
if (parameterAnnotation.annotationType() == ExpectLeaking.class) {
|
|
isLeaking = true;
|
|
}
|
|
}
|
|
assertEquals(method.toString() + " #" + i, isLeaking, map.get(method)[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void checkAnnotations(Class<?> javaClass) {
|
|
PsiClass psiClass = myJavaPsiFacade.findClass(javaClass.getName(), GlobalSearchScope.moduleWithLibrariesScope(myModule));
|
|
assertNotNull(psiClass);
|
|
|
|
for (java.lang.reflect.Method javaMethod : javaClass.getDeclaredMethods()) {
|
|
if(javaMethod.isSynthetic()) continue; // skip lambda runtime representation
|
|
PsiMethod psiMethod = psiClass.findMethodsByName(javaMethod.getName(), false)[0];
|
|
Annotation[][] annotations = javaMethod.getParameterAnnotations();
|
|
|
|
// not-null parameters
|
|
for (int i = 0; i < annotations.length; i++) {
|
|
Annotation[] parameterAnnotations = annotations[i];
|
|
PsiParameter psiParameter = psiMethod.getParameterList().getParameters()[i];
|
|
PsiAnnotation inferredAnnotation = myBytecodeAnalysisService.findInferredAnnotation(psiParameter, AnnotationUtil.NOT_NULL);
|
|
boolean expectNotNull = Stream.of(parameterAnnotations).anyMatch(anno -> anno.annotationType() == ExpectNotNull.class);
|
|
assertNullity(getMethodDisplayName(javaMethod) + "/arg#" + i, expectNotNull, inferredAnnotation);
|
|
}
|
|
|
|
// not-null result
|
|
ExpectNotNull expectedAnnotation = javaMethod.getAnnotation(ExpectNotNull.class);
|
|
PsiAnnotation actualAnnotation = myBytecodeAnalysisService.findInferredAnnotation(psiMethod, AnnotationUtil.NOT_NULL);
|
|
assertNullity(getMethodDisplayName(javaMethod), expectedAnnotation != null, actualAnnotation);
|
|
|
|
// contracts
|
|
ExpectContract expectedContract = javaMethod.getAnnotation(ExpectContract.class);
|
|
PsiAnnotation actualContract = myBytecodeAnalysisService.findInferredAnnotation(psiMethod, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
|
|
|
|
String expectedText = expectedContract == null ? "null" : expectedContract.toString();
|
|
String inferredText = actualContract == null ? "null" : actualContract.getText();
|
|
|
|
assertEquals(getMethodDisplayName(javaMethod) + ":" + expectedText + " <> " + inferredText,
|
|
expectedContract == null, actualContract == null);
|
|
|
|
if (expectedContract != null && actualContract != null) {
|
|
String expectedContractValue = expectedContract.value();
|
|
String actualContractValue = AnnotationUtil.getStringAttributeValue(actualContract, null);
|
|
assertEquals(getMethodDisplayName(javaMethod), expectedContractValue, actualContractValue);
|
|
|
|
boolean expectedPureValue = expectedContract.pure();
|
|
boolean actualPureValue = getPureAttribute(actualContract);
|
|
assertEquals(getMethodDisplayName(javaMethod), expectedPureValue, actualPureValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void assertNullity(String message, boolean expectedNotNull, PsiAnnotation inferredAnnotation) {
|
|
if(expectedNotNull && inferredAnnotation == null) {
|
|
fail(message+": @NotNull expected, but not inferred");
|
|
} else if(!expectedNotNull && inferredAnnotation != null) {
|
|
fail(message+": @NotNull inferred, but not expected");
|
|
}
|
|
}
|
|
|
|
private static String getMethodDisplayName(java.lang.reflect.Method javaMethod) {
|
|
return javaMethod.getDeclaringClass().getSimpleName()+"."+javaMethod.getName();
|
|
}
|
|
|
|
private static boolean getPureAttribute(PsiAnnotation annotation) {
|
|
Boolean pureValue = AnnotationUtil.getBooleanAttributeValue(annotation, "pure");
|
|
return pureValue != null && pureValue.booleanValue();
|
|
}
|
|
|
|
private void checkCompoundIds(Class<?> javaClass) throws IOException {
|
|
String javaClassName = javaClass.getCanonicalName();
|
|
PsiClass psiClass = myJavaPsiFacade.findClass(javaClassName, GlobalSearchScope.moduleWithLibrariesScope(myModule));
|
|
assertNotNull(psiClass);
|
|
|
|
for (java.lang.reflect.Method javaMethod : javaClass.getDeclaredMethods()) {
|
|
if(javaMethod.isSynthetic()) continue; // skip lambda runtime representation
|
|
Method method = new Method(Type.getType(javaClass).getInternalName(), javaMethod.getName(), Type.getMethodDescriptor(javaMethod));
|
|
boolean noKey = javaMethod.getAnnotation(ExpectNoPsiKey.class) != null;
|
|
PsiMethod[] methods = psiClass.findMethodsByName(javaMethod.getName(), false);
|
|
assertTrue("Must be single method: "+javaMethod, methods.length == 1);
|
|
PsiMethod psiMethod = methods[0];
|
|
checkCompoundId(method, psiMethod, noKey);
|
|
}
|
|
|
|
for (Constructor<?> constructor : javaClass.getDeclaredConstructors()) {
|
|
Method method = new Method(Type.getType(javaClass).getInternalName(), "<init>", Type.getConstructorDescriptor(constructor));
|
|
boolean noKey = constructor.getAnnotation(ExpectNoPsiKey.class) != null;
|
|
PsiMethod[] constructors = psiClass.getConstructors();
|
|
PsiMethod psiMethod = constructors[0];
|
|
checkCompoundId(method, psiMethod, noKey);
|
|
}
|
|
}
|
|
|
|
private void checkCompoundId(Method method, PsiMethod psiMethod, boolean noKey) throws IOException {
|
|
/*
|
|
System.out.println();
|
|
System.out.println(method.internalClassName);
|
|
System.out.println(method.methodName);
|
|
System.out.println(method.methodDesc);
|
|
*/
|
|
|
|
|
|
HKey psiKey = BytecodeAnalysisConverter.psiKey(psiMethod, Direction.Out, myMessageDigest);
|
|
if (noKey) {
|
|
assertTrue(null == psiKey);
|
|
return;
|
|
}
|
|
else {
|
|
assertFalse(null == psiKey);
|
|
}
|
|
HKey asmKey = BytecodeAnalysisConverter.asmKey(new Key(method, Direction.Out, true), myMessageDigest);
|
|
Assert.assertEquals(asmKey, psiKey);
|
|
}
|
|
|
|
private void setUpDataClasses() throws Exception {
|
|
File classesDir = new File(Test01.class.getResource("/" + Test01.class.getPackage().getName().replace('.', '/')).toURI());
|
|
File destDir = new File(myModule.getProject().getBaseDir().getPath() + myClassesProjectRelativePath);
|
|
FileUtil.copyDir(classesDir, destDir);
|
|
VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(destDir);
|
|
assertNotNull(vFile);
|
|
PsiTestUtil.addLibrary(myModule, "dataClasses", vFile.getPath(), new String[]{""}, ArrayUtil.EMPTY_STRING_ARRAY);
|
|
}
|
|
|
|
}
|