mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[tests] migrates bytecode analysis tests to light test case
... and moves test data out of source code
This commit is contained in:
-5
@@ -2218,11 +2218,6 @@
|
||||
<item name='java.lang.String String(int[], int, int) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='java.lang.String String(java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="pure" val="true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.lang.String String(java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
-5
@@ -49,11 +49,6 @@
|
||||
<val name="pure" val="true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.velocity.util.ExceptionUtils java.lang.Throwable createWithCause(java.lang.Class, java.lang.String, java.lang.Throwable)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""null,_,_->null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.velocity.util.MapFactory MapFactory()'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="pure" val="true"/>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
# use Java 9+
|
||||
JAVAC="$(which javac)"
|
||||
|
||||
if [ ! -x "$JAVAC" ]; then
|
||||
echo "Java compiler not defined"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf classes
|
||||
mkdir classes
|
||||
|
||||
"$JAVAC" -g --release 8 -d classes src/Expect*.java
|
||||
|
||||
"$JAVAC" -g --release 8 -d classes -cp classes src/data/*.java
|
||||
rm -rf classes/bytecodeAnalysis/data/ExtTestConverterData*
|
||||
|
||||
"$JAVAC" -g --release 9 -d classes -cp classes src/java9/*.java
|
||||
|
||||
"$JAVAC" -g --release 8 -cp classes conflict/*.java
|
||||
BIN
Binary file not shown.
+27
@@ -0,0 +1,27 @@
|
||||
package bytecodeAnalysis.data;
|
||||
|
||||
// A class to test the clash of the same class in different source paths
|
||||
// See also ../src/data/TestConflict.java
|
||||
public class TestConflict {
|
||||
static native int throwInDataNativeInConflict();
|
||||
|
||||
static int nativeInDataThrowInConflict() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
static int throwBoth() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
void pureInDataSideEffectInConflict() {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
void sideEffectInDataPureInConflict() { }
|
||||
|
||||
void pureBoth() { }
|
||||
|
||||
void sideEffectBoth() {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,16 @@
|
||||
package bytecodeAnalysis;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface ExpectContract {
|
||||
String value() default "";
|
||||
boolean pure() default false;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package bytecodeAnalysis;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ExpectLeaking { }
|
||||
@@ -0,0 +1,10 @@
|
||||
package bytecodeAnalysis;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ExpectNoPsiKey { }
|
||||
@@ -0,0 +1,10 @@
|
||||
package bytecodeAnalysis;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ExpectNotNull { }
|
||||
+13
-20
@@ -1,8 +1,6 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.java.codeInspection.bytecodeAnalysis.data;
|
||||
package bytecodeAnalysis.data;
|
||||
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectContract;
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectNotNull;
|
||||
import bytecodeAnalysis.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
@@ -49,9 +47,7 @@ public class Test01 {
|
||||
}
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
static void v(Object o) {
|
||||
|
||||
}
|
||||
static void v(Object o) { }
|
||||
|
||||
@ExpectContract("null->null")
|
||||
static String toString1(Object o) {
|
||||
@@ -114,9 +110,10 @@ public class Test01 {
|
||||
|
||||
@ExpectNotNull
|
||||
public MySupplier lambdaBranching(@ExpectNotNull String s, String t, boolean b) {
|
||||
if(b) {
|
||||
if (b) {
|
||||
System.out.println(s);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
System.out.println(t);
|
||||
}
|
||||
return () -> s.trim();
|
||||
@@ -124,14 +121,14 @@ public class Test01 {
|
||||
|
||||
@ExpectContract(value="null,_->fail", pure = true)
|
||||
public static void assertNotNull(@ExpectNotNull Object obj, String message) {
|
||||
if(obj == null) {
|
||||
if (obj == null) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@ExpectContract(value="false,_,_->fail;true,_,_->true", pure = true)
|
||||
public static boolean assertTrue(boolean val, String message, int data) {
|
||||
if(!val) {
|
||||
if (!val) {
|
||||
throw new IllegalArgumentException(message+":"+data);
|
||||
}
|
||||
return val;
|
||||
@@ -139,7 +136,7 @@ public class Test01 {
|
||||
|
||||
@ExpectContract(value="true,_->fail;_,_->false", pure = true)
|
||||
public static boolean assertFalse(boolean val, String message) {
|
||||
if(val) {
|
||||
if (val) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
return false;
|
||||
@@ -246,7 +243,7 @@ public class Test01 {
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
void testThrow(@ExpectNotNull String s) {
|
||||
if(s.isEmpty()) {
|
||||
if (s.isEmpty()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
@@ -267,9 +264,7 @@ public class Test01 {
|
||||
Files.createDirectories(file.toPath());
|
||||
return true;
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
|
||||
}
|
||||
catch (IOException ignored) { }
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -279,9 +274,7 @@ public class Test01 {
|
||||
Files.createDirectories(file.toPath());
|
||||
return true;
|
||||
}
|
||||
catch (Throwable ignored) {
|
||||
|
||||
}
|
||||
catch (Throwable ignored) { }
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -291,4 +284,4 @@ public class Test01 {
|
||||
res[0] = null;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package bytecodeAnalysis.data;
|
||||
|
||||
import bytecodeAnalysis.*;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public final class Test02 {
|
||||
@ExpectContract(pure = true)
|
||||
@ExpectNotNull
|
||||
public String notNullString() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
@ExpectNotNull
|
||||
public String notNullStringDelegate() {
|
||||
return notNullString();
|
||||
}
|
||||
|
||||
public boolean getFlagVolatile(@ExpectNotNull Test01 test01) {
|
||||
return test01.volatileFlag;
|
||||
}
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
public boolean getFlagPlain(@ExpectNotNull Test01 test01) {
|
||||
return test01.plainFlag;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package bytecodeAnalysis.data;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface TestAnnotation { }
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package bytecodeAnalysis.data;
|
||||
|
||||
import bytecodeAnalysis.ExpectContract;
|
||||
|
||||
// A class to test the clash of the same class in different source paths
|
||||
// See also ../../conflict/TestConflict.java
|
||||
public class TestConflict {
|
||||
static int throwInDataNativeInConflict() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
static native int nativeInDataThrowInConflict();
|
||||
|
||||
@ExpectContract(value = "->fail", pure = true)
|
||||
static int throwBoth() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
void pureInDataSideEffectInConflict() { }
|
||||
|
||||
void sideEffectInDataPureInConflict() {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
void pureBoth() { }
|
||||
|
||||
void sideEffectBoth() {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
+11
-28
@@ -1,32 +1,14 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.java.codeInspection.bytecodeAnalysis.data;
|
||||
package bytecodeAnalysis.data;
|
||||
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter;
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectNoPsiKey;
|
||||
import bytecodeAnalysis.ExpectNoPsiKey;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class TestConverterData {
|
||||
|
||||
public static class StaticNestedClass {
|
||||
public StaticNestedClass(Object o) {
|
||||
public StaticNestedClass(Object o) { }
|
||||
|
||||
}
|
||||
public StaticNestedClass[] test01(StaticNestedClass[] ns, StaticNestedClass... ellipsis) {
|
||||
return ns;
|
||||
}
|
||||
@@ -34,7 +16,7 @@ public class TestConverterData {
|
||||
|
||||
public class InnerClass {
|
||||
// a reference to outer class should be inserted when translating PSI -> ASM
|
||||
public InnerClass(Object o) {}
|
||||
public InnerClass(Object o) { }
|
||||
|
||||
public InnerClass[] Inner2test01(InnerClass[] tests, InnerClass... ellipsis) {
|
||||
return tests;
|
||||
@@ -42,9 +24,8 @@ public class TestConverterData {
|
||||
}
|
||||
|
||||
public static class GenericStaticNestedClass<A> {
|
||||
public GenericStaticNestedClass(A a) {
|
||||
public GenericStaticNestedClass(A a) { }
|
||||
|
||||
}
|
||||
public GenericStaticNestedClass[] test01(GenericStaticNestedClass[] ns, GenericStaticNestedClass... ellipsis) {
|
||||
return ns;
|
||||
}
|
||||
@@ -54,7 +35,7 @@ public class TestConverterData {
|
||||
}
|
||||
|
||||
public class GenericInnerClass<B> {
|
||||
public GenericInnerClass(B b) {}
|
||||
public GenericInnerClass(B b) { }
|
||||
|
||||
public <C> GenericStaticNestedClass<A> test01(GenericInnerClass<C> c) {
|
||||
return GenericStaticNestedClass.this;
|
||||
@@ -62,11 +43,11 @@ public class TestConverterData {
|
||||
}
|
||||
}
|
||||
|
||||
public TestConverterData(int x) {}
|
||||
public TestConverterData(int x) { }
|
||||
|
||||
// BytecodeAnalysisConverter class is not in the project path, so translation from PSI is impossible
|
||||
// ExtConverterClass class is not in the class roots, so translation from PSI is impossible
|
||||
@ExpectNoPsiKey
|
||||
public BytecodeAnalysisConverter test01(BytecodeAnalysisConverter converter) {
|
||||
public ExtTestConverterData test01(ExtTestConverterData converter) {
|
||||
return converter;
|
||||
}
|
||||
|
||||
@@ -79,3 +60,5 @@ public class TestConverterData {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
class ExtTestConverterData { }
|
||||
@@ -0,0 +1,22 @@
|
||||
package bytecodeAnalysis.data;
|
||||
|
||||
import bytecodeAnalysis.ExpectContract;
|
||||
|
||||
public enum TestEnum {
|
||||
A, B, C;
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
public int getValue() {
|
||||
return ordinal()+1;
|
||||
}
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
public boolean isA() {
|
||||
switch (this) {
|
||||
case A:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-5
@@ -1,9 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
package com.intellij.java.codeInspection.bytecodeAnalysis.data;
|
||||
package bytecodeAnalysis.data;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class TestHashCollision {
|
||||
// signature hashes for these two methods collide: MD5("()V"+"test11044") and MD5("()V"+"test20917") have the same prefix: 3d802c48
|
||||
void test11044() {
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package bytecodeAnalysis.data;
|
||||
|
||||
import bytecodeAnalysis.ExpectLeaking;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class TestLeakingParametersData {
|
||||
int z;
|
||||
|
||||
void test01(@ExpectLeaking Object o1, @ExpectLeaking Object o2, @ExpectLeaking Object o3) {
|
||||
o1.toString();
|
||||
o2.toString();
|
||||
o3.toString();
|
||||
}
|
||||
|
||||
void test02(@ExpectLeaking TestLeakingParametersData d) {
|
||||
System.out.println(d.z);
|
||||
}
|
||||
|
||||
void test03(int i, @ExpectLeaking TestLeakingParametersData d) {
|
||||
System.out.println(d.z);
|
||||
}
|
||||
|
||||
void test04(long i, @ExpectLeaking TestLeakingParametersData d) {
|
||||
System.out.println(d.z);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package bytecodeAnalysis.data;
|
||||
|
||||
import bytecodeAnalysis.*;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class TestNonStable {
|
||||
public String asString1() {
|
||||
return asString();
|
||||
}
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
@ExpectNotNull
|
||||
public String asString() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package bytecodeAnalysis.java9;
|
||||
|
||||
import bytecodeAnalysis.*;
|
||||
|
||||
// Test that indified string concatenation is properly recognized
|
||||
// This file is precompiled via Java 9 compiler and class file is placed in the same directory
|
||||
public class TestStringConcat {
|
||||
@ExpectContract(pure = true)
|
||||
@ExpectNotNull
|
||||
String concat(String foo, String bar) {
|
||||
return foo+"!"+bar;
|
||||
}
|
||||
}
|
||||
+198
-292
@@ -1,6 +1,4 @@
|
||||
// Copyright 2000-2017 JetBrains s.r.o.
|
||||
// Use of this source code is governed by the Apache 2.0 license that can be
|
||||
// found in the LICENSE.txt file.
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.java.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
@@ -14,32 +12,24 @@ import com.intellij.openapi.application.ex.PathManagerEx;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.projectRoots.SdkModificator;
|
||||
import com.intellij.openapi.roots.AnnotationOrderRootType;
|
||||
import com.intellij.openapi.roots.ModifiableRootModel;
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil;
|
||||
import com.intellij.openapi.roots.*;
|
||||
import com.intellij.openapi.roots.libraries.Library;
|
||||
import com.intellij.openapi.roots.libraries.LibraryTable;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.registry.RegistryValue;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VfsUtilCore;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileVisitor;
|
||||
import com.intellij.project.IntelliJProjectConfiguration;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
import com.intellij.psi.xml.XmlFile;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.testFramework.IdeaTestUtil;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.PsiTestUtil;
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.AsynchConsumer;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
|
||||
import one.util.streamex.EntryStream;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -48,225 +38,230 @@ import org.jetbrains.java.decompiler.IdeaDecompiler;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestCase {
|
||||
public class BytecodeAnalysisIntegrationTest extends LightCodeInsightFixtureTestCase {
|
||||
private static final String ORG_JETBRAINS_ANNOTATIONS_CONTRACT = Contract.class.getName();
|
||||
private static final String INFERRED_TEST_METHOD =
|
||||
"org.apache.velocity.util.ExceptionUtils java.lang.Throwable createWithCause(java.lang.Class, java.lang.String, java.lang.Throwable)";
|
||||
private static final String EXTERNAL_TEST_METHOD = "java.lang.String String(java.lang.String)";
|
||||
|
||||
private MessageDigest myMessageDigest;
|
||||
private final List<String> myDiffs = new ArrayList<>();
|
||||
private boolean myNullableMethodRegistryValue;
|
||||
private VirtualFile myAnnotationsDir;
|
||||
private static final DefaultLightProjectDescriptor PROJECT_DESCRIPTOR = new DefaultLightProjectDescriptor() {
|
||||
@Override
|
||||
public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) {
|
||||
super.configureModule(module, model, contentEntry);
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
myMessageDigest = BytecodeAnalysisConverter.getMessageDigest();
|
||||
RegistryValue registryValue = Registry.get(ProjectBytecodeAnalysis.NULLABLE_METHOD);
|
||||
myNullableMethodRegistryValue = registryValue.asBoolean();
|
||||
registryValue.setValue(true);
|
||||
}
|
||||
PsiTestUtil.addProjectLibrary(model, "velocity", IntelliJProjectConfiguration.getProjectLibraryClassesRootPaths("Velocity"));
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
try {
|
||||
Registry.get(ProjectBytecodeAnalysis.NULLABLE_METHOD).setValue(myNullableMethodRegistryValue);
|
||||
}
|
||||
finally {
|
||||
super.tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
private void setUpLibraries() {
|
||||
ModuleRootModificationUtil.addModuleLibrary(myModule, "velocity",
|
||||
IntelliJProjectConfiguration.getProjectLibraryClassesRootUrls("Velocity"),
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
private void setUpExternalUpAnnotations() {
|
||||
String annotationsPath = PathManagerEx.getTestDataPath() + "/codeInspection/bytecodeAnalysis/annotations";
|
||||
myAnnotationsDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(annotationsPath);
|
||||
assertNotNull(myAnnotationsDir);
|
||||
|
||||
ModuleRootModificationUtil.updateModel(myModule, new AsynchConsumer<ModifiableRootModel>() {
|
||||
@Override
|
||||
public void finished() { }
|
||||
|
||||
@Override
|
||||
public void consume(ModifiableRootModel modifiableRootModel) {
|
||||
LibraryTable libraryTable = modifiableRootModel.getModuleLibraryTable();
|
||||
Library[] libs = libraryTable.getLibraries();
|
||||
for (Library library : libs) {
|
||||
Library.ModifiableModel libraryModel = library.getModifiableModel();
|
||||
libraryModel.addRoot(myAnnotationsDir, AnnotationOrderRootType.getInstance());
|
||||
libraryModel.commit();
|
||||
}
|
||||
Sdk sdk = modifiableRootModel.getSdk();
|
||||
if (sdk != null) {
|
||||
Sdk clone;
|
||||
try {
|
||||
clone = (Sdk)sdk.clone();
|
||||
}
|
||||
catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
SdkModificator sdkModificator = clone.getSdkModificator();
|
||||
sdkModificator.addRoot(myAnnotationsDir, AnnotationOrderRootType.getInstance());
|
||||
sdkModificator.commitChanges();
|
||||
modifiableRootModel.setSdk(clone);
|
||||
VirtualFile annotationsRoot = getAnnotationsRoot();
|
||||
for (OrderEntry entry : model.getOrderEntries()) {
|
||||
if (entry instanceof LibraryOrderEntry) {
|
||||
Library.ModifiableModel libModel = ((LibraryOrderEntry)entry).getLibrary().getModifiableModel();
|
||||
libModel.addRoot(annotationsRoot, AnnotationOrderRootType.getInstance());
|
||||
libModel.commit();
|
||||
}
|
||||
}
|
||||
});
|
||||
Sdk sdk = model.getSdk();
|
||||
if (sdk != null) {
|
||||
sdk = PsiTestUtil.addRootsToJdk(sdk, AnnotationOrderRootType.getInstance(), annotationsRoot);
|
||||
model.setSdk(sdk);
|
||||
}
|
||||
|
||||
VfsUtilCore.visitChildrenRecursively(myAnnotationsDir, new VirtualFileVisitor() {
|
||||
});
|
||||
myAnnotationsDir.refresh(false, true);
|
||||
Registry.get(ProjectBytecodeAnalysis.NULLABLE_METHOD).setValue(true, module);
|
||||
}
|
||||
};
|
||||
|
||||
private static VirtualFile getAnnotationsRoot() {
|
||||
String annotationsPath = PathManagerEx.getTestDataPath() + "/codeInspection/bytecodeAnalysis/annotations";
|
||||
VirtualFile annotationsDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(annotationsPath);
|
||||
assertNotNull(annotationsDir);
|
||||
return annotationsDir;
|
||||
}
|
||||
|
||||
private void openDecompiledClass(String name) {
|
||||
PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass(name, GlobalSearchScope.allScope(getProject()));
|
||||
assertNotNull(psiClass);
|
||||
myFixture.openFileInEditor(psiClass.getContainingFile().getVirtualFile());
|
||||
|
||||
String documentText = myFixture.getEditor().getDocument().getText();
|
||||
assertTrue(documentText, documentText.startsWith(IdeaDecompiler.BANNER));
|
||||
@NotNull
|
||||
@Override
|
||||
protected LightProjectDescriptor getProjectDescriptor() {
|
||||
return PROJECT_DESCRIPTOR;
|
||||
}
|
||||
|
||||
public void testInferredAnnoGutter() {
|
||||
setUpLibraries();
|
||||
openDecompiledClass("org.apache.velocity.util.ExceptionUtils");
|
||||
checkHasGutter("<html><i>Inferred</i> annotations available. Full signature:<p>\n" +
|
||||
"<i>@Contract("null,_,_->null")</i> \n" +
|
||||
"public static Throwable <b>createWithCause</b>(");
|
||||
checkHasGutter("org.apache.velocity.util.ExceptionUtils",
|
||||
"<html><i>Inferred</i> annotations available. Full signature:<p>\n" +
|
||||
"<i>@Contract('null,_,_->null')</i> \n" +
|
||||
"public static Throwable <b>createWithCause</b>(Class clazz,\n String message,\n Throwable cause)</html>");
|
||||
}
|
||||
|
||||
public void testExternalAnnoGutter() {
|
||||
ModuleRootModificationUtil.setModuleSdk(myModule, PsiTestUtil.addJdkAnnotations(IdeaTestUtil.getMockJdk17()));
|
||||
openDecompiledClass("java.lang.String");
|
||||
checkHasGutter("<html>External and <i>inferred</i> annotations available. Full signature:<p>\n" +
|
||||
"<i>@Contract(pure = true)</i> \n" +
|
||||
"public <b>String</b>(@NotNull String var1)</html>");
|
||||
checkHasGutter("java.lang.String",
|
||||
"<html>External and <i>inferred</i> annotations available. Full signature:<p>\n" +
|
||||
"<i>@Contract(pure = true)</i> \n" +
|
||||
"public <b>String</b>(@NotNull String var1)</html>");
|
||||
}
|
||||
|
||||
private void checkHasGutter(String expectedText) {
|
||||
Collection<String> gutters = ContainerUtil.mapNotNull(myFixture.findAllGutters(), GutterMark::getTooltipText);
|
||||
String contractMark = ContainerUtil.find(gutters, mark -> mark.contains(expectedText));
|
||||
assertNotNull(StringUtil.join(gutters, "\n"), contractMark);
|
||||
private void checkHasGutter(String className, String expectedText) {
|
||||
PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass(className, GlobalSearchScope.moduleWithLibrariesScope(myModule));
|
||||
assertNotNull(psiClass);
|
||||
myFixture.openFileInEditor(psiClass.getContainingFile().getVirtualFile());
|
||||
String documentText = myFixture.getEditor().getDocument().getText();
|
||||
assertThat(documentText).startsWith(IdeaDecompiler.BANNER);
|
||||
Set<String> gutters = myFixture.findAllGutters().stream()
|
||||
.map(GutterMark::getTooltipText)
|
||||
.filter(Objects::nonNull)
|
||||
.map(s -> s.replaceAll(" +", " ").replace(" ", " ").replace(""", "'").replace(">", ">"))
|
||||
.collect(Collectors.toSet());
|
||||
assertThat(gutters).contains(expectedText);
|
||||
}
|
||||
|
||||
public void testSdkAndLibAnnotations() {
|
||||
setUpLibraries();
|
||||
setUpExternalUpAnnotations();
|
||||
|
||||
PsiPackage rootPackage = JavaPsiFacade.getInstance(getProject()).findPackage("");
|
||||
assert rootPackage != null;
|
||||
assertNotNull(rootPackage);
|
||||
|
||||
GlobalSearchScope scope = GlobalSearchScope.allScope(getProject());
|
||||
JavaRecursiveElementVisitor visitor = new JavaRecursiveElementVisitor() {
|
||||
MessageDigest digest = BytecodeAnalysisConverter.getMessageDigest();
|
||||
List<String> diffs = new ArrayList<>();
|
||||
JavaRecursiveElementVisitor visitor = new PackageVisitor(GlobalSearchScope.moduleWithLibrariesScope(myModule)) {
|
||||
@Override
|
||||
public void visitPackage(PsiPackage aPackage) {
|
||||
// annotations are in class paths, but we are not interested in inferred annotations for them
|
||||
if ("org.intellij.lang.annotations".equals(aPackage.getQualifiedName())) {
|
||||
return;
|
||||
}
|
||||
for (PsiPackage subPackage : aPackage.getSubPackages(scope)) {
|
||||
visitPackage(subPackage);
|
||||
}
|
||||
for (PsiClass aClass : aPackage.getClasses(scope)) {
|
||||
protected void visitSubPackage(PsiPackage aPackage, PsiClass[] classes) {
|
||||
for (PsiClass aClass : classes) {
|
||||
for (PsiMethod method : aClass.getMethods()) {
|
||||
checkMethodAnnotations(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void checkMethodAnnotations(PsiMethod method) {
|
||||
if (ProjectBytecodeAnalysis.getInstance(getProject()).getKey(method, digest) == null) return;
|
||||
String methodKey = PsiFormatUtil.getExternalName(method, false, Integer.MAX_VALUE);
|
||||
if (INFERRED_TEST_METHOD.equals(methodKey)) return;
|
||||
|
||||
String externalNotNullMethodAnnotation = findExternalAnnotation(method, AnnotationUtil.NOT_NULL) == null ? "-" : "@NotNull";
|
||||
String inferredNotNullMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NOT_NULL) == null ? "-" : "@NotNull";
|
||||
if (!externalNotNullMethodAnnotation.equals(inferredNotNullMethodAnnotation)) {
|
||||
diffs.add(methodKey + ": " + externalNotNullMethodAnnotation + " != " + inferredNotNullMethodAnnotation + "\n");
|
||||
}
|
||||
String externalNullableMethodAnnotation = findExternalAnnotation(method, AnnotationUtil.NULLABLE) == null ? "-" : "@Nullable";
|
||||
String inferredNullableMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NULLABLE) == null ? "-" : "@Nullable";
|
||||
if (!externalNullableMethodAnnotation.equals(inferredNullableMethodAnnotation)) {
|
||||
diffs.add(methodKey + ": " + externalNullableMethodAnnotation + " != " + inferredNullableMethodAnnotation + "\n");
|
||||
}
|
||||
|
||||
for (PsiParameter parameter : method.getParameterList().getParameters()) {
|
||||
String parameterKey = PsiFormatUtil.getExternalName(parameter, false, Integer.MAX_VALUE);
|
||||
String externalNotNull = findExternalAnnotation(parameter, AnnotationUtil.NOT_NULL) == null ? "-" : "@NotNull";
|
||||
String inferredNotNull = findInferredAnnotation(parameter, AnnotationUtil.NOT_NULL) == null ? "-" : "@NotNull";
|
||||
if (!externalNotNull.equals(inferredNotNull)) {
|
||||
diffs.add(parameterKey + ": " + externalNotNull + " != " + inferredNotNull + "\n");
|
||||
}
|
||||
String externalNullable = findExternalAnnotation(parameter, AnnotationUtil.NULLABLE) == null ? "-" : "@Nullable";
|
||||
String inferredNullable = findInferredAnnotation(parameter, AnnotationUtil.NULLABLE) == null ? "-" : "@Nullable";
|
||||
if (!externalNullable.equals(inferredNullable)) {
|
||||
diffs.add(parameterKey + ": " + externalNullable + " != " + inferredNullable + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!EXTERNAL_TEST_METHOD.equals(methodKey)) {
|
||||
PsiAnnotation externalContractAnnotation = findExternalAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
|
||||
PsiAnnotation inferredContractAnnotation = findInferredAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
|
||||
String externalContractAnnotationText = externalContractAnnotation == null ? "-" : externalContractAnnotation.getText();
|
||||
String inferredContractAnnotationText = inferredContractAnnotation == null ? "-" : inferredContractAnnotation.getText();
|
||||
if (!externalContractAnnotationText.equals(inferredContractAnnotationText)) {
|
||||
diffs.add(methodKey + ": " + externalContractAnnotationText + " != " + inferredContractAnnotationText + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
rootPackage.accept(visitor);
|
||||
System.err.println(ClassDataIndexer.ourIndexSizeStatistics);
|
||||
assertEmpty(myDiffs);
|
||||
assertEmpty(diffs);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void _testExportInferredAnnotations() {
|
||||
exportInferredAnnotations();
|
||||
}
|
||||
|
||||
private void exportInferredAnnotations() {
|
||||
setUpLibraries();
|
||||
setUpExternalUpAnnotations();
|
||||
|
||||
PsiPackage rootPackage = JavaPsiFacade.getInstance(getProject()).findPackage("");
|
||||
assert rootPackage != null;
|
||||
assertNotNull(rootPackage);
|
||||
|
||||
GlobalSearchScope scope = GlobalSearchScope.allScope(getProject());
|
||||
JavaRecursiveElementVisitor visitor = new AnnotationExporter(scope);
|
||||
VirtualFile annotationsRoot = getAnnotationsRoot();
|
||||
JavaRecursiveElementVisitor visitor = new PackageVisitor(GlobalSearchScope.moduleWithLibrariesScope(myModule)) {
|
||||
@Override
|
||||
protected void visitSubPackage(PsiPackage aPackage, PsiClass[] classes) {
|
||||
System.out.println(aPackage.getQualifiedName());
|
||||
Map<String, Map<String, PsiNameValuePair[]>> annotations = new TreeMap<>();
|
||||
for (PsiClass aClass : classes) processClass(aClass, annotations);
|
||||
saveXmlForPackage(aPackage.getQualifiedName(), annotations, annotationsRoot);
|
||||
}
|
||||
|
||||
private void processClass(PsiClass aClass, Map<String, Map<String, PsiNameValuePair[]>> annotations) {
|
||||
for (PsiMethod method : aClass.getMethods()) annotateMethod(method, annotations);
|
||||
for (PsiClass innerClass : aClass.getInnerClasses()) processClass(innerClass, annotations);
|
||||
}
|
||||
|
||||
private void annotateMethod(PsiMethod method, Map<String, Map<String, PsiNameValuePair[]>> annotations) {
|
||||
String methodKey = PsiFormatUtil.getExternalName(method, false, Integer.MAX_VALUE);
|
||||
if (INFERRED_TEST_METHOD.equals(methodKey)) return;
|
||||
|
||||
if (!EXTERNAL_TEST_METHOD.equals(methodKey)) {
|
||||
PsiAnnotation inferredContractAnnotation = findInferredAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
|
||||
if (inferredContractAnnotation != null) {
|
||||
PsiNameValuePair[] attributes = inferredContractAnnotation.getParameterList().getAttributes();
|
||||
annotate(annotations, method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT, attributes);
|
||||
}
|
||||
}
|
||||
|
||||
PsiAnnotation inferredNotNullMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NOT_NULL);
|
||||
if (inferredNotNullMethodAnnotation != null) {
|
||||
annotate(annotations, method, AnnotationUtil.NOT_NULL, PsiNameValuePair.EMPTY_ARRAY);
|
||||
}
|
||||
PsiAnnotation inferredNullableMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NULLABLE);
|
||||
if (inferredNullableMethodAnnotation != null) {
|
||||
annotate(annotations, method, AnnotationUtil.NULLABLE, PsiNameValuePair.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
for (PsiParameter parameter : method.getParameterList().getParameters()) {
|
||||
PsiAnnotation inferredNotNull = findInferredAnnotation(parameter, AnnotationUtil.NOT_NULL);
|
||||
if (inferredNotNull != null) {
|
||||
annotate(annotations, parameter, AnnotationUtil.NOT_NULL, PsiNameValuePair.EMPTY_ARRAY);
|
||||
}
|
||||
PsiAnnotation inferredNullable = findInferredAnnotation(parameter, AnnotationUtil.NULLABLE);
|
||||
if (inferredNullable != null) {
|
||||
annotate(annotations, parameter, AnnotationUtil.NULLABLE, PsiNameValuePair.EMPTY_ARRAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void annotate(Map<String, Map<String, PsiNameValuePair[]>> annotations,
|
||||
PsiModifierListOwner owner,
|
||||
String annotationFQN,
|
||||
PsiNameValuePair[] attributes) {
|
||||
String key = PsiFormatUtil.getExternalName(owner, false, Integer.MAX_VALUE);
|
||||
annotations.computeIfAbsent(key, k -> new TreeMap<>()).put(annotationFQN, attributes);
|
||||
}
|
||||
|
||||
private void saveXmlForPackage(String packageName, Map<String, Map<String, PsiNameValuePair[]>> annotations, VirtualFile root) {
|
||||
if (annotations.isEmpty()) return;
|
||||
String xmlContent = EntryStream.of(annotations)
|
||||
.mapValues(map -> EntryStream.of(map).mapKeyValue(ExternalAnnotationsManagerImpl::createAnnotationTag).joining())
|
||||
.mapKeyValue((externalName, content) -> "<item name=\'" + StringUtil.escapeXml(externalName) + "\'>\n" + content.trim() + "\n</item>\n")
|
||||
.joining("", "<root>\n", "</root>");
|
||||
WriteCommandAction.runWriteCommandAction(getProject(), () -> {
|
||||
XmlFile xml = ExternalAnnotationsManagerImpl.createAnnotationsXml(root, packageName, getPsiManager());
|
||||
if (xml == null) throw new IllegalStateException("Unable to get XML for package " + packageName + "; root = " + root);
|
||||
xml.getVirtualFile().refresh(false, false);
|
||||
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getProject());
|
||||
Document document = documentManager.getDocument(xml);
|
||||
document.setText(xmlContent);
|
||||
documentManager.commitDocument(document);
|
||||
CodeStyleManager.getInstance(getProject()).reformat(xml);
|
||||
FileDocumentManager.getInstance().saveDocument(document);
|
||||
});
|
||||
}
|
||||
};
|
||||
rootPackage.accept(visitor);
|
||||
}
|
||||
|
||||
private void checkMethodAnnotations(PsiMethod method) {
|
||||
if (ProjectBytecodeAnalysis.getInstance(getProject()).getKey(method, myMessageDigest) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String methodKey = PsiFormatUtil.getExternalName(method, false, Integer.MAX_VALUE);
|
||||
|
||||
{
|
||||
// @NotNull method
|
||||
String externalNotNullMethodAnnotation = findExternalAnnotation(method, AnnotationUtil.NOT_NULL) == null ? "null" : "@NotNull";
|
||||
String inferredNotNullMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NOT_NULL) == null ? "null" : "@NotNull";
|
||||
|
||||
if (!externalNotNullMethodAnnotation.equals(inferredNotNullMethodAnnotation)) {
|
||||
myDiffs.add(methodKey + ": " + externalNotNullMethodAnnotation + " != " + inferredNotNullMethodAnnotation + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// @Nullable method
|
||||
String externalNullableMethodAnnotation = findExternalAnnotation(method, AnnotationUtil.NULLABLE) == null ? "null" : "@Nullable";
|
||||
String inferredNullableMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NULLABLE) == null ? "null" : "@Nullable";
|
||||
|
||||
if (!externalNullableMethodAnnotation.equals(inferredNullableMethodAnnotation)) {
|
||||
myDiffs.add(methodKey + ": " + externalNullableMethodAnnotation + " != " + inferredNullableMethodAnnotation + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
for (PsiParameter parameter : method.getParameterList().getParameters()) {
|
||||
String parameterKey = PsiFormatUtil.getExternalName(parameter, false, Integer.MAX_VALUE);
|
||||
|
||||
{
|
||||
// @NotNull parameter
|
||||
String externalNotNull = findExternalAnnotation(parameter, AnnotationUtil.NOT_NULL) == null ? "null" : "@NotNull";
|
||||
String inferredNotNull = findInferredAnnotation(parameter, AnnotationUtil.NOT_NULL) == null ? "null" : "@NotNull";
|
||||
if (!externalNotNull.equals(inferredNotNull)) {
|
||||
myDiffs.add(parameterKey + ": " + externalNotNull + " != " + inferredNotNull + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// @Nullable parameter
|
||||
String externalNullable = findExternalAnnotation(parameter, AnnotationUtil.NULLABLE) == null ? "null" : "@Nullable";
|
||||
String inferredNullable = findInferredAnnotation(parameter, AnnotationUtil.NULLABLE) == null ? "null" : "@Nullable";
|
||||
if (!externalNullable.equals(inferredNullable)) {
|
||||
myDiffs.add(parameterKey + ": " + externalNullable + " != " + inferredNullable + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @Contract
|
||||
PsiAnnotation externalContractAnnotation = findExternalAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
|
||||
PsiAnnotation inferredContractAnnotation = findInferredAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
|
||||
|
||||
String externalContractAnnotationText =
|
||||
externalContractAnnotation == null ? "null" : externalContractAnnotation.getText();
|
||||
String inferredContractAnnotationText =
|
||||
inferredContractAnnotation == null ? "null" : inferredContractAnnotation.getText();
|
||||
|
||||
if (!externalContractAnnotationText.equals(inferredContractAnnotationText)) {
|
||||
myDiffs.add(methodKey + ": " + externalContractAnnotationText + " != " + inferredContractAnnotationText + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiAnnotation findInferredAnnotation(PsiModifierListOwner owner, String fqn) {
|
||||
return ProjectBytecodeAnalysis.getInstance(getProject()).findInferredAnnotation(owner, fqn);
|
||||
@@ -274,115 +269,26 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC
|
||||
|
||||
@Nullable
|
||||
private PsiAnnotation findExternalAnnotation(PsiModifierListOwner owner, String fqn) {
|
||||
return ExternalAnnotationsManager.getInstance(myModule.getProject()).findExternalAnnotation(owner, fqn);
|
||||
return ExternalAnnotationsManager.getInstance(getProject()).findExternalAnnotation(owner, fqn);
|
||||
}
|
||||
|
||||
private class AnnotationExporter extends JavaRecursiveElementVisitor {
|
||||
private abstract static class PackageVisitor extends JavaRecursiveElementVisitor {
|
||||
private final GlobalSearchScope myScope;
|
||||
|
||||
AnnotationExporter(GlobalSearchScope scope) {myScope = scope;}
|
||||
PackageVisitor(GlobalSearchScope scope) {
|
||||
myScope = scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPackage(PsiPackage aPackage) {
|
||||
// annotations are in class paths, but we are not interested in inferred annotations for them
|
||||
if ("org.intellij.lang.annotations".equals(aPackage.getQualifiedName())) {
|
||||
return;
|
||||
}
|
||||
for (PsiPackage subPackage : aPackage.getSubPackages(myScope)) {
|
||||
visitPackage(subPackage);
|
||||
}
|
||||
Map<String, Map<String, PsiNameValuePair[]>> packageAnnotations = new TreeMap<>();
|
||||
for (PsiClass aClass : aPackage.getClasses(myScope)) {
|
||||
processClass(aClass, packageAnnotations);
|
||||
}
|
||||
saveXmlForPackage(myAnnotationsDir, aPackage, convertToXml(packageAnnotations));
|
||||
}
|
||||
|
||||
private void saveXmlForPackage(VirtualFile root, PsiPackage aPackage, XmlTag newContent) {
|
||||
XmlTag[] tags = newContent.getSubTags();
|
||||
if (tags.length == 0) return;
|
||||
WriteCommandAction.runWriteCommandAction(getProject(), () -> {
|
||||
XmlFile xml = ExternalAnnotationsManagerImpl.createAnnotationsXml(root, aPackage.getQualifiedName(), aPackage.getManager());
|
||||
if (xml == null) {
|
||||
throw new IllegalStateException("Unable to get XML for package " + aPackage.getQualifiedName() + "; root = " + root);
|
||||
if (!"org.intellij.lang.annotations".equals(aPackage.getQualifiedName())) {
|
||||
for (PsiPackage subPackage : aPackage.getSubPackages(myScope)) {
|
||||
visitPackage(subPackage);
|
||||
}
|
||||
XmlTag rootTag = xml.getRootTag();
|
||||
if (rootTag == null) {
|
||||
throw new IllegalStateException("No root tag in " + xml);
|
||||
}
|
||||
XmlTag[] existingItems = rootTag.getSubTags();
|
||||
if (existingItems.length > 0) {
|
||||
rootTag.deleteChildRange(ArrayUtil.getFirstElement(existingItems), ArrayUtil.getLastElement(existingItems));
|
||||
}
|
||||
rootTag.collapseIfEmpty();
|
||||
for (XmlTag item : tags) {
|
||||
rootTag.addSubTag(item, false);
|
||||
}
|
||||
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(xml.getProject());
|
||||
Document doc = documentManager.getDocument(xml);
|
||||
documentManager.doPostponedOperationsAndUnblockDocument(doc);
|
||||
FileDocumentManager.getInstance().saveDocument(doc);
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private XmlTag convertToXml(Map<String, Map<String, PsiNameValuePair[]>> annotations) {
|
||||
String xmlContent = EntryStream.of(annotations)
|
||||
.mapValues(map -> EntryStream.of(map).mapKeyValue(ExternalAnnotationsManagerImpl::createAnnotationTag).joining())
|
||||
.mapKeyValue((externalName, content) -> "<item name=\'" + StringUtil.escapeXml(externalName) + "\'>\n" + content + "</item>")
|
||||
.joining("", "<root>\n", "</root>\n");
|
||||
XmlElementFactory factory = XmlElementFactory.getInstance(getProject());
|
||||
return factory.createTagFromText(xmlContent);
|
||||
}
|
||||
|
||||
private void processClass(PsiClass aClass, Map<String, Map<String, PsiNameValuePair[]>> packageAnnotations) {
|
||||
for (PsiMethod method : aClass.getMethods()) {
|
||||
annotateMethod(method, packageAnnotations);
|
||||
}
|
||||
for (PsiClass innerClass : aClass.getInnerClasses()) {
|
||||
processClass(innerClass, packageAnnotations);
|
||||
visitSubPackage(aPackage, aPackage.getClasses(myScope));
|
||||
}
|
||||
}
|
||||
|
||||
private void annotateMethod(PsiMethod method, Map<String, Map<String, PsiNameValuePair[]>> packageAnnotations) {
|
||||
// @Contract
|
||||
PsiAnnotation inferredContractAnnotation = findInferredAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
|
||||
if (inferredContractAnnotation != null) {
|
||||
PsiNameValuePair[] attributes = inferredContractAnnotation.getParameterList().getAttributes();
|
||||
annotate(packageAnnotations, method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT, attributes);
|
||||
}
|
||||
|
||||
// @NotNull method
|
||||
PsiAnnotation inferredNotNullMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NOT_NULL);
|
||||
if (inferredNotNullMethodAnnotation != null) {
|
||||
annotate(packageAnnotations, method, AnnotationUtil.NOT_NULL, PsiNameValuePair.EMPTY_ARRAY);
|
||||
}
|
||||
// @Nullable method
|
||||
PsiAnnotation inferredNullableMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NULLABLE);
|
||||
if (inferredNullableMethodAnnotation != null) {
|
||||
annotate(packageAnnotations, method, AnnotationUtil.NULLABLE, PsiNameValuePair.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
for (PsiParameter parameter : method.getParameterList().getParameters()) {
|
||||
// @NotNull parameter
|
||||
PsiAnnotation inferredNotNull = findInferredAnnotation(parameter, AnnotationUtil.NOT_NULL);
|
||||
if (inferredNotNull != null) {
|
||||
annotate(packageAnnotations, parameter, AnnotationUtil.NOT_NULL, PsiNameValuePair.EMPTY_ARRAY);
|
||||
}
|
||||
// @Nullable parameter
|
||||
PsiAnnotation inferredNullable = findInferredAnnotation(parameter, AnnotationUtil.NULLABLE);
|
||||
if (inferredNullable != null) {
|
||||
annotate(packageAnnotations, parameter, AnnotationUtil.NULLABLE, PsiNameValuePair.EMPTY_ARRAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void annotate(Map<String, Map<String, PsiNameValuePair[]>> packageAnnotations,
|
||||
PsiModifierListOwner owner,
|
||||
String annotationFQN,
|
||||
PsiNameValuePair[] attributes) {
|
||||
packageAnnotations.computeIfAbsent(PsiFormatUtil.getExternalName(owner, false, Integer.MAX_VALUE),
|
||||
k -> new TreeMap<>()).put(annotationFQN, attributes);
|
||||
}
|
||||
protected abstract void visitSubPackage(PsiPackage aPackage, PsiClass[] classes);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-15
@@ -1,18 +1,4 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.
|
||||
*/
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.java.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
|
||||
+129
-164
@@ -1,253 +1,218 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.java.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.*;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.asm.LeakingParameters;
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.data.*;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.lang.jvm.JvmAnnotation;
|
||||
import com.intellij.lang.jvm.JvmParameter;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.roots.ContentEntry;
|
||||
import com.intellij.openapi.roots.ModifiableRootModel;
|
||||
import com.intellij.openapi.roots.ProjectFileIndex;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import com.intellij.psi.util.ClassUtil;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.PsiTestUtil;
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.org.objectweb.asm.*;
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader;
|
||||
import org.jetbrains.org.objectweb.asm.ClassVisitor;
|
||||
import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes;
|
||||
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.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
private static final String PACKAGE_PATH = Test01.class.getPackage().getName().replace('.', '/');
|
||||
public class BytecodeAnalysisTest extends LightCodeInsightFixtureTestCase {
|
||||
private static final String PACKAGE_NAME = "bytecodeAnalysis";
|
||||
private static final String EXPECT_NOT_NULL = PACKAGE_NAME + ".ExpectNotNull";
|
||||
private static final String EXPECT_CONTRACT = PACKAGE_NAME + ".ExpectContract";
|
||||
private static final String EXPECT_LEAKING = PACKAGE_NAME + ".ExpectLeaking";
|
||||
private static final String EXPECT_NO_PSI_KEY = PACKAGE_NAME + ".ExpectNoPsiKey";
|
||||
|
||||
private JavaPsiFacade myJavaPsiFacade;
|
||||
private ProjectBytecodeAnalysis myBytecodeAnalysisService;
|
||||
private MessageDigest myMessageDigest;
|
||||
private static final DefaultLightProjectDescriptor PROJECT_DESCRIPTOR = new DefaultLightProjectDescriptor() {
|
||||
@Override
|
||||
public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) {
|
||||
super.configureModule(module, model, contentEntry);
|
||||
String dataDir = JavaTestUtil.getJavaTestDataPath() + "/codeInspection/bytecodeAnalysis/data";
|
||||
PsiTestUtil.addProjectLibrary(model, "velocity", Arrays.asList(dataDir + "/classes", dataDir + "/conflict"));
|
||||
}
|
||||
};
|
||||
|
||||
@NotNull
|
||||
@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();
|
||||
protected LightProjectDescriptor getProjectDescriptor() {
|
||||
return PROJECT_DESCRIPTOR;
|
||||
}
|
||||
|
||||
public void testInference() {
|
||||
checkAnnotations(Test01.class.getName());
|
||||
checkAnnotations(Test02.class.getName());
|
||||
checkAnnotations(TestNonStable.class.getName());
|
||||
checkAnnotations(TestConflict.class.getName());
|
||||
checkAnnotations(TestEnum.class.getName());
|
||||
checkAnnotations("data.Test01");
|
||||
checkAnnotations("data.Test02");
|
||||
checkAnnotations("data.TestNonStable");
|
||||
checkAnnotations("data.TestConflict");
|
||||
checkAnnotations("data.TestEnum");
|
||||
}
|
||||
|
||||
public void testJava9Inference() {
|
||||
checkAnnotations(Test01.class.getPackage().getName()+".TestJava9");
|
||||
checkAnnotations("java9.TestStringConcat");
|
||||
}
|
||||
|
||||
public void testHashCollision() {
|
||||
checkAnnotations(TestHashCollision.class.getName());
|
||||
checkAnnotations("data.TestHashCollision");
|
||||
}
|
||||
|
||||
public void testConverter() {
|
||||
checkCompoundIds(Test01.class);
|
||||
checkCompoundIds(TestConverterData.class);
|
||||
checkCompoundIds(TestConverterData.StaticNestedClass.class);
|
||||
checkCompoundIds(TestConverterData.InnerClass.class);
|
||||
checkCompoundIds(TestConverterData.GenericStaticNestedClass.class);
|
||||
checkCompoundIds(TestAnnotation.class);
|
||||
public void testConverter() throws IOException {
|
||||
checkCompoundIds("data.Test01");
|
||||
checkCompoundIds("data.TestConverterData");
|
||||
checkCompoundIds("data.TestConverterData.StaticNestedClass");
|
||||
checkCompoundIds("data.TestConverterData.InnerClass");
|
||||
checkCompoundIds("data.TestConverterData.GenericStaticNestedClass");
|
||||
checkCompoundIds("data.TestAnnotation");
|
||||
}
|
||||
|
||||
public void testLeakingParametersAnalysis() throws IOException {
|
||||
final Map<Member, boolean[]> map = new HashMap<>();
|
||||
Map<String, boolean[]> map = new HashMap<>();
|
||||
|
||||
// collecting leakedParameters
|
||||
final Class<LeakingParametersData> jClass = LeakingParametersData.class;
|
||||
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 Member method = new Member(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);
|
||||
GlobalSearchScope scope = GlobalSearchScope.moduleWithLibrariesScope(myModule);
|
||||
PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass(PACKAGE_NAME + ".data.TestLeakingParametersData", scope);
|
||||
assertNotNull(psiClass);
|
||||
try (InputStream stream = getVirtualFile(psiClass).getInputStream()) {
|
||||
ClassReader reader = new ClassReader(stream);
|
||||
reader.accept(new ClassVisitor(Opcodes.API_VERSION) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
MethodNode node = new MethodNode(Opcodes.API_VERSION, access, name, desc, signature, exceptions);
|
||||
return new MethodVisitor(Opcodes.API_VERSION, node) {
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
try {
|
||||
map.put(name, LeakingParameters.build(reader.getClassName(), node, false).parameters);
|
||||
}
|
||||
catch (AnalyzerException ignore) { }
|
||||
}
|
||||
catch (AnalyzerException ignore) {}
|
||||
}
|
||||
};
|
||||
}
|
||||
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
};
|
||||
}
|
||||
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
}
|
||||
|
||||
for (java.lang.reflect.Method jMethod : jClass.getDeclaredMethods()) {
|
||||
Member method = new Member(Type.getType(jClass).getInternalName(), jMethod.getName(), Type.getMethodDescriptor(jMethod));
|
||||
Annotation[][] annotations = jMethod.getParameterAnnotations();
|
||||
for (int i = 0; i < annotations.length; i++) {
|
||||
for (PsiMethod method : psiClass.getMethods()) {
|
||||
JvmParameter[] parameters = method.getParameters();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
boolean isLeaking = false;
|
||||
Annotation[] parameterAnnotations = annotations[i];
|
||||
for (Annotation parameterAnnotation : parameterAnnotations) {
|
||||
if (parameterAnnotation.annotationType() == ExpectLeaking.class) {
|
||||
for (JvmAnnotation annotation : parameters[i].getAnnotations()) {
|
||||
if (EXPECT_LEAKING.equals(annotation.getQualifiedName())) {
|
||||
isLeaking = true;
|
||||
}
|
||||
}
|
||||
assertEquals(method + " #" + i, isLeaking, map.get(method)[i]);
|
||||
assertEquals(method + " #" + i, isLeaking, map.get(method.getName())[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAnnotations(String className) {
|
||||
PsiClass psiClass = myJavaPsiFacade.findClass(className, GlobalSearchScope.moduleWithLibrariesScope(myModule));
|
||||
GlobalSearchScope scope = GlobalSearchScope.moduleWithLibrariesScope(myModule);
|
||||
PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass(PACKAGE_NAME + '.' + className, scope);
|
||||
assertNotNull(psiClass);
|
||||
ProjectBytecodeAnalysis service = ProjectBytecodeAnalysis.getInstance(getProject());
|
||||
|
||||
for (PsiMethod method : psiClass.getMethods()) {
|
||||
PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
if (method.isConstructor() && parameters.length == 0) continue;
|
||||
|
||||
// not-null parameters
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
PsiAnnotation inferredAnnotation = myBytecodeAnalysisService.findInferredAnnotation(parameters[i], AnnotationUtil.NOT_NULL);
|
||||
boolean expectNotNull = AnnotationUtil.isAnnotated(parameters[i], ExpectNotNull.class.getName(), 0);
|
||||
assertNullity(getMethodDisplayName(method) + "/arg#" + i, expectNotNull, inferredAnnotation);
|
||||
boolean expectNotNull = AnnotationUtil.isAnnotated(parameters[i], EXPECT_NOT_NULL, 0);
|
||||
PsiAnnotation inferredAnnotation = service.findInferredAnnotation(parameters[i], AnnotationUtil.NOT_NULL);
|
||||
assertNullity(displayName(method) + "/arg#" + i, expectNotNull, inferredAnnotation);
|
||||
}
|
||||
|
||||
// not-null result
|
||||
boolean expectNotNull = AnnotationUtil.isAnnotated(method, ExpectNotNull.class.getName(), 0);
|
||||
PsiAnnotation actualAnnotation = myBytecodeAnalysisService.findInferredAnnotation(method, AnnotationUtil.NOT_NULL);
|
||||
assertNullity(getMethodDisplayName(method), expectNotNull, actualAnnotation);
|
||||
boolean expectNotNull = AnnotationUtil.isAnnotated(method, EXPECT_NOT_NULL, 0);
|
||||
PsiAnnotation actualAnnotation = service.findInferredAnnotation(method, AnnotationUtil.NOT_NULL);
|
||||
assertNullity(displayName(method), expectNotNull, actualAnnotation);
|
||||
|
||||
// contracts
|
||||
PsiAnnotation expectedContract = AnnotationUtil.findAnnotation(method, ExpectContract.class.getName());
|
||||
PsiAnnotation actualContract = myBytecodeAnalysisService.findInferredAnnotation(method, Contract.class.getName());
|
||||
|
||||
String expectedText = getContractText(expectedContract);
|
||||
String inferredText = getContractText(actualContract);
|
||||
|
||||
assertEquals(getMethodDisplayName(method) + ":" + expectedText + " <> " + inferredText,
|
||||
expectedText, inferredText);
|
||||
PsiAnnotation expectedContract = AnnotationUtil.findAnnotation(method, EXPECT_CONTRACT);
|
||||
PsiAnnotation actualContract = service.findInferredAnnotation(method, Contract.class.getName());
|
||||
String expectedText = contractText(expectedContract);
|
||||
String inferredText = contractText(actualContract);
|
||||
assertEquals(displayName(method) + ":" + expectedText + " <> " + inferredText, expectedText, inferredText);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getContractText(PsiAnnotation expectedContract) {
|
||||
return expectedContract == null ? "null" :
|
||||
expectedContract.getText().replaceFirst("^@.+Contract\\(", "@Contract(")
|
||||
.replace(" = ", "=")
|
||||
.replace(", ", ",");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getMethodDisplayName(PsiMethod method) {
|
||||
private static String displayName(PsiMethod method) {
|
||||
return method.getContainingClass().getQualifiedName() + "." + method.getName();
|
||||
}
|
||||
|
||||
private static String contractText(PsiAnnotation contract) {
|
||||
return contract == null ? "null" : contract.getText().replaceFirst("^@.+Contract\\(", "@Contract(").replace(" = ", "=").replace(", ", ",");
|
||||
}
|
||||
|
||||
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");
|
||||
if (expectedNotNull && inferredAnnotation == null) {
|
||||
fail(message + ": @NotNull expected, but not inferred");
|
||||
}
|
||||
else if (!expectedNotNull && inferredAnnotation != null) {
|
||||
fail(message + ": @NotNull inferred, but not expected");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCompoundIds(Class<?> javaClass) {
|
||||
String javaClassName = javaClass.getCanonicalName();
|
||||
PsiClass psiClass = myJavaPsiFacade.findClass(javaClassName, GlobalSearchScope.moduleWithLibrariesScope(myModule));
|
||||
private void checkCompoundIds(String className) throws IOException {
|
||||
GlobalSearchScope scope = GlobalSearchScope.moduleWithLibrariesScope(myModule);
|
||||
PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass(PACKAGE_NAME + '.' + className, scope);
|
||||
assertNotNull(psiClass);
|
||||
MessageDigest digest = BytecodeAnalysisConverter.getMessageDigest();
|
||||
|
||||
for (java.lang.reflect.Method javaMethod : javaClass.getDeclaredMethods()) {
|
||||
if(javaMethod.isSynthetic()) continue; // skip lambda runtime representation
|
||||
Member method = new Member(Type.getType(javaClass).getInternalName(), javaMethod.getName(), Type.getMethodDescriptor(javaMethod));
|
||||
boolean noKey = javaMethod.getAnnotation(ExpectNoPsiKey.class) != null;
|
||||
PsiMethod[] methods = psiClass.findMethodsByName(javaMethod.getName(), false);
|
||||
assertEquals("Must be single method: " + javaMethod, 1, methods.length);
|
||||
PsiMethod psiMethod = methods[0];
|
||||
checkCompoundId(method, psiMethod, noKey);
|
||||
}
|
||||
|
||||
for (Constructor<?> constructor : javaClass.getDeclaredConstructors()) {
|
||||
Member method = new Member(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);
|
||||
try (InputStream stream = getVirtualFile(psiClass).getInputStream()) {
|
||||
ClassReader reader = new ClassReader(stream);
|
||||
reader.accept(new ClassVisitor(Opcodes.API_VERSION) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
if ((access & Opcodes.ACC_SYNTHETIC) == 0) {
|
||||
Member method = new Member(reader.getClassName(), name, desc);
|
||||
PsiMethod[] psiMethods = "<init>".equals(name) ? psiClass.getConstructors() : psiClass.findMethodsByName(name, false);
|
||||
assertEquals("Must be single method: " + name, 1, psiMethods.length);
|
||||
PsiMethod psiMethod = psiMethods[0];
|
||||
boolean noKey = psiMethod.hasAnnotation(EXPECT_NO_PSI_KEY);
|
||||
checkCompoundId(method, psiMethod, noKey, digest);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCompoundId(Member method, PsiMethod psiMethod, boolean noKey) {
|
||||
/*
|
||||
System.out.println();
|
||||
System.out.println(method.internalClassName);
|
||||
System.out.println(method.methodName);
|
||||
System.out.println(method.methodDesc);
|
||||
*/
|
||||
|
||||
|
||||
private static void checkCompoundId(Member method, PsiMethod psiMethod, boolean noKey, MessageDigest digest) {
|
||||
EKey psiKey = BytecodeAnalysisConverter.psiKey(psiMethod, Direction.Out);
|
||||
if (noKey) {
|
||||
assertNull(psiKey);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
assertNotNull(psiKey);
|
||||
EKey asmKey = new EKey(method, Direction.Out, true);
|
||||
assertEquals(asmKey, psiKey);
|
||||
assertEquals(asmKey.hashed(digest), psiKey.hashed(digest));
|
||||
}
|
||||
EKey asmKey = new EKey(method, Direction.Out, true);
|
||||
Assert.assertEquals(asmKey, psiKey);
|
||||
Assert.assertEquals(asmKey.hashed(myMessageDigest), psiKey.hashed(myMessageDigest));
|
||||
}
|
||||
|
||||
private void setUpDataClasses() throws Exception {
|
||||
File classesDir = new File(Test01.class.getResource("/" + PACKAGE_PATH).toURI());
|
||||
String basePath = myModule.getProject().getBasePath();
|
||||
File destDir = new File(basePath + "/classes/" + PACKAGE_PATH);
|
||||
FileUtil.copyDir(classesDir, destDir);
|
||||
VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(destDir);
|
||||
assertNotNull(vFile);
|
||||
PsiTestUtil.addLibrary(myModule, "dataClasses", vFile.getPath(), new String[]{""}, ArrayUtil.EMPTY_STRING_ARRAY);
|
||||
|
||||
setUpPrecompiledDataClasses(basePath);
|
||||
}
|
||||
|
||||
private void setUpPrecompiledDataClasses(String basePath) throws IOException {
|
||||
File sourcePath = new File(PlatformTestUtil.getCommunityPath() + "/java/java-tests/testSrc/" + PACKAGE_PATH);
|
||||
File[] sourceFiles = new File(sourcePath.getParentFile(), "precompiledData").listFiles((dir, name) -> name.endsWith(".java"));
|
||||
assertNotNull(sourceFiles);
|
||||
File conflictOutput = new File(basePath + "/precompiled/"+PACKAGE_PATH);
|
||||
assertTrue(conflictOutput.mkdirs());
|
||||
for (File file : sourceFiles) {
|
||||
File precompiledFile = new File(file.getParentFile(), file.getName().replaceFirst(".java$", ".class"));
|
||||
if(!precompiledFile.exists()) {
|
||||
fail("Unable to find precompiled "+precompiledFile+" for source file "+file);
|
||||
}
|
||||
FileUtil.copy(file, new File(conflictOutput, file.getName()));
|
||||
FileUtil.copy(precompiledFile, new File(conflictOutput, precompiledFile.getName()));
|
||||
private static VirtualFile getVirtualFile(PsiClass psiClass) {
|
||||
VirtualFile file = psiClass.getContainingFile().getVirtualFile();
|
||||
if (psiClass.getParent() instanceof PsiClass) {
|
||||
VirtualFile root = ProjectFileIndex.getInstance(psiClass.getProject()).getClassRootForFile(file);
|
||||
file = root.findFileByRelativePath(ClassUtil.getJVMClassName(psiClass).replace('.', '/') + ".class");
|
||||
}
|
||||
|
||||
VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(conflictOutput);
|
||||
assertNotNull(vFile);
|
||||
PsiTestUtil.addLibrary(myModule, "precompiled", vFile.getPath(), new String[]{""}, ArrayUtil.EMPTY_STRING_ARRAY);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.java.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface ExpectContract {
|
||||
String value() default "";
|
||||
boolean pure() default false;
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.java.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ExpectLeaking {
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.java.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ExpectNoPsiKey {
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.java.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ExpectNotNull {
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.java.codeInspection.bytecodeAnalysis.data;
|
||||
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectLeaking;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class LeakingParametersData {
|
||||
int z;
|
||||
|
||||
void test01(@ExpectLeaking Object o1, @ExpectLeaking Object o2, @ExpectLeaking Object o3) {
|
||||
o1.toString();
|
||||
o2.toString();
|
||||
o3.toString();
|
||||
}
|
||||
|
||||
void test02(@ExpectLeaking LeakingParametersData d) {
|
||||
System.out.println(d.z);
|
||||
}
|
||||
|
||||
void test03(int i, @ExpectLeaking LeakingParametersData d) {
|
||||
System.out.println(d.z);
|
||||
}
|
||||
|
||||
void test04(long i, @ExpectLeaking LeakingParametersData d) {
|
||||
System.out.println(d.z);
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.java.codeInspection.bytecodeAnalysis.data;
|
||||
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectContract;
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectNotNull;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class Test02 {
|
||||
@ExpectContract(pure = true)
|
||||
@ExpectNotNull
|
||||
public String notNullString() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
@ExpectNotNull
|
||||
public String notNullStringDelegate() {
|
||||
return notNullString();
|
||||
}
|
||||
|
||||
public boolean getFlagVolatile(@ExpectNotNull Test01 test01) {
|
||||
return test01.volatileFlag;
|
||||
}
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
public boolean getFlagPlain(@ExpectNotNull Test01 test01) {
|
||||
return test01.plainFlag;
|
||||
}
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.java.codeInspection.bytecodeAnalysis.data;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface TestAnnotation {
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.java.codeInspection.bytecodeAnalysis.data;
|
||||
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectContract;
|
||||
|
||||
public class TestConflict {
|
||||
static int throwInDataNativeInConflict() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
static native int nativeInDataThrowInConflict();
|
||||
|
||||
@ExpectContract(value = "->fail", pure = true)
|
||||
static int throwBoth() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
void pureInDataSideEffectInConflict() {
|
||||
|
||||
}
|
||||
|
||||
void sideEffectInDataPureInConflict() {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
void pureBoth() {
|
||||
|
||||
}
|
||||
|
||||
void sideEffectBoth() {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
package com.intellij.java.codeInspection.bytecodeAnalysis.data;
|
||||
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectContract;
|
||||
|
||||
public enum TestEnum {
|
||||
A, B, C;
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
public int getValue() {
|
||||
return ordinal()+1;
|
||||
}
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
public boolean isA() {
|
||||
switch (this) {
|
||||
case A:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.java.codeInspection.bytecodeAnalysis.data;
|
||||
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectContract;
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectNotNull;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class TestNonStable {
|
||||
|
||||
public String asString1() {
|
||||
return asString();
|
||||
}
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
@ExpectNotNull
|
||||
public String asString() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
-45
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.java.codeInspection.bytecodeAnalysis.data;
|
||||
|
||||
// Precompiled class to test the clash of the same class in different source paths
|
||||
// To compile it just use javac TestConflict.java
|
||||
public class TestConflict {
|
||||
static native int throwInDataNativeInConflict();
|
||||
|
||||
static int nativeInDataThrowInConflict() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
static int throwBoth() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
void pureInDataSideEffectInConflict() {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
void sideEffectInDataPureInConflict() {
|
||||
}
|
||||
|
||||
void pureBoth() {
|
||||
|
||||
}
|
||||
|
||||
void sideEffectBoth() {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
-16
@@ -1,16 +0,0 @@
|
||||
package com.intellij.java.codeInspection.bytecodeAnalysis.data;
|
||||
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectContract;
|
||||
import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectNotNull;
|
||||
|
||||
// Test that indified string concatenation is properly recognized
|
||||
// This file is precompiled via Java 9 compiler and class file is placed in the same directory
|
||||
// Compilation command is
|
||||
// "C:\Program Files\Java\jdk-9.0.1\bin\javac.exe" -cp $PROJECT_DIR$\out\classes\test\intellij.java.tests TestJava9.java
|
||||
public class TestJava9 {
|
||||
@ExpectContract(pure = true)
|
||||
@ExpectNotNull
|
||||
String concat(String foo, String bar) {
|
||||
return foo+"!"+bar;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user