[tests] migrates bytecode analysis tests to light test case

... and moves test data out of source code
This commit is contained in:
Roman Shevchenko
2018-09-14 14:45:43 +02:00
parent eee623e8d2
commit e0589b585f
61 changed files with 598 additions and 927 deletions
@@ -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>
@@ -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="&quot;null,_,_-&gt;null&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.util.MapFactory MapFactory()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
@@ -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
@@ -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();
}
}
@@ -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 { }
@@ -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;
}
}
@@ -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 { }
@@ -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();
}
}
@@ -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,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() {
@@ -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);
}
}
@@ -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 "";
}
}
@@ -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;
}
}
@@ -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(&quot;null,_,_-&gt;null&quot;)</i>&nbsp;\n" +
"public static&nbsp;Throwable&nbsp;<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>&nbsp;\n" +
"public&nbsp;<b>String</b>(@NotNull&nbsp;String&nbsp;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("&nbsp;", " ").replace("&quot;", "'").replace("&gt;", ">"))
.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,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;
@@ -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;
}
}
@@ -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;
}
@@ -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 {
}
@@ -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 {
}
@@ -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 {
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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 {
}
@@ -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();
}
}
@@ -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;
}
}
}
@@ -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 "";
}
}
@@ -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();
}
}
@@ -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;
}
}