[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

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,10 @@
package bytecodeAnalysis;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author lambdamix
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface ExpectLeaking { }

View File

@@ -0,0 +1,10 @@
package bytecodeAnalysis;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author lambdamix
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface ExpectNoPsiKey { }

View File

@@ -0,0 +1,10 @@
package bytecodeAnalysis;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author lambdamix
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface ExpectNotNull { }

View File

@@ -0,0 +1,287 @@
package bytecodeAnalysis.data;
import bytecodeAnalysis.*;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Array;
import java.nio.file.Files;
/**
* @author lambdamix
*/
@SuppressWarnings({"unused", "IOResourceOpenedButNotSafelyClosed"})
public class Test01 {
@ExpectNotNull
@ExpectContract(pure = true)
public static MySupplier methodReference(@ExpectNotNull String s) {
return s::trim;
}
boolean plainFlag;
volatile boolean volatileFlag;
static void f(@ExpectNotNull Object o1, @ExpectNotNull Object o2) {
if (o1 == null) throw new NullPointerException();
else s(o2, o2);
}
static void g(@ExpectNotNull Object o, boolean b) {
if (b) f(o, o);
else s(o, o);
}
static void s(@ExpectNotNull Object o1, Object o2) {
t(o1);
v(o2);
}
static void t(@ExpectNotNull Object o) {
use(o);
}
private static String use(@ExpectNotNull Object o) {
System.out.println(o);
return o.toString();
}
@ExpectContract(pure = true)
static void v(Object o) { }
@ExpectContract("null->null")
static String toString1(Object o) {
return o == null ? null : use(o);
}
@ExpectContract("null->!null")
static String toString2(Object o) {
return o == null ? "null" : use(o);
}
@ExpectContract(pure = true)
@ExpectNotNull
static String constantString() {
return "s";
}
@ExpectContract(value = "_->param1", pure = true)
static String idString(String s) {
return s;
}
@ExpectContract(value = "->this", pure = true)
@ExpectNotNull
public Test01 getThis() {
return this;
}
@ExpectContract(value = "->new", pure = true)
@ExpectNotNull
protected Test01 createRoot() {
return new Test01();
}
@ExpectContract(value = "!null->false;null->true", pure = true)
static boolean isNull(Object o) {
return o == null;
}
@ExpectContract(value = "!null->true;null->false", pure = true)
static boolean isNotNull(Object o) {
return !isNull(o);
}
interface MySupplier {
String get();
}
@ExpectNotNull
@ExpectContract(pure = true)
public static MySupplier lambda(@ExpectNotNull String s) {
return () -> s.trim();
}
@ExpectNotNull
@ExpectContract(pure = true)
public MySupplier lambdaNonStatic(@ExpectNotNull String s) {
return () -> getThis().hashCode() + s.trim();
}
@ExpectNotNull
public MySupplier lambdaBranching(@ExpectNotNull String s, String t, boolean b) {
if (b) {
System.out.println(s);
}
else {
System.out.println(t);
}
return () -> s.trim();
}
@ExpectContract(value="null,_->fail", pure = true)
public static void assertNotNull(@ExpectNotNull Object obj, String message) {
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) {
throw new IllegalArgumentException(message+":"+data);
}
return val;
}
@ExpectContract(value="true,_->fail;_,_->false", pure = true)
public static boolean assertFalse(boolean val, String message) {
if (val) {
throw new IllegalArgumentException(message);
}
return false;
}
@ExpectNotNull
@ExpectContract(value = "_,_,_->new", pure = true)
public static long[] copyOfRange(@ExpectNotNull long[] arr, int from, int to) {
int diff = to - from;
if (diff < 0) {
throw new IllegalArgumentException("Invalid arguments: " + from + '>' + to);
}
long[] copy = new long[diff];
System.arraycopy(arr, from, copy, 0, Math.min(arr.length - from, diff));
return copy;
}
@ExpectNotNull
@ExpectContract(value="_->new", pure = true)
public static long[] copyAndModify(@ExpectNotNull long[] input) {
long[] copy = copyOfRange(input, 0, input.length);
copy[0] = 1;
set(copy);
return copy;
}
private static void set(@ExpectNotNull long[] copy) {
copy[1] = 2;
}
@ExpectContract(value="_,_,_,_->new", pure = true)
public static <I, O> O[] copyOfRangeObject(@ExpectNotNull I[] arr, int from, int to, @ExpectNotNull Class<? extends O[]> newType) {
int diff = to - from;
if (diff < 0) {
throw new IllegalArgumentException("Invalid arguments: " + from + '>' + to);
}
@SuppressWarnings("unchecked")
O[] copy = (O[]) Array.newInstance(newType.getComponentType(), diff);
//noinspection SuspiciousSystemArraycopy
System.arraycopy(arr, from, copy, 0, Math.min(arr.length - from, diff));
return copy;
}
@ExpectContract(value = "_->fail", pure = true)
public static void callAlwaysFail(int x) {
alwaysFail();
}
@ExpectContract(value = "_->fail", pure = true)
public static void callAlwaysFailRef(String x) {
callAlwaysFailTwoRefs(x, null);
}
@ExpectContract(value = "_,_->fail", pure = true)
public static void callAlwaysFailTwoRefs(String x, String y) {
alwaysFail();
}
@ExpectContract(value = "->fail", pure = true)
private static void alwaysFail() {
throw new UnsupportedOperationException();
}
@ExpectContract(value = "!null->null;null->!null", pure = true)
static String invert(String x) {
return x == null ? "empty" : null;
}
@ExpectContract(value = "false->true;true->false", pure = true)
static boolean invertBool(boolean x) {
return !x;
}
@ExpectContract(value = "_,true->true;null,_->true", pure=true)
boolean checkTrueFail(Object a, boolean b) {
if (a == null) return true;
if (b) throw new RuntimeException();
return b;
}
@ExpectNotNull
String getStringNoTry(@ExpectNotNull String s) throws IOException {
return String.valueOf(new FileReader(s.trim()).read());
}
String getStringTry(String s) {
try {
return String.valueOf(new FileReader(s.trim()).read());
}
catch (IOException ex) {
return null;
}
}
@ExpectContract("null->null")
String getStringTryNPECatched(String s) {
try {
return String.valueOf(new FileReader(s.trim()).read());
}
catch (Exception ex) {
return null;
}
}
@ExpectContract(pure = true)
void testThrow(@ExpectNotNull String s) {
if (s.isEmpty()) {
throw new IllegalArgumentException();
}
}
@ExpectContract("_->param1")
String testCatchReturn(String s) {
try {
Integer.parseInt(s);
}
catch (NumberFormatException ex) {
System.out.println("exception!");
}
return s;
}
boolean testCatchBool(File file) {
try {
Files.createDirectories(file.toPath());
return true;
}
catch (IOException ignored) { }
return false;
}
@ExpectContract("null->false")
boolean testCatchBool2(File file) {
try {
Files.createDirectories(file.toPath());
return true;
}
catch (Throwable ignored) { }
return false;
}
@ExpectContract(value = "_->new", pure = true)
String[] replaceFirstWithNull(@ExpectNotNull String[] arr) {
String[] res = arr.clone();
res[0] = null;
return res;
}
}

View File

@@ -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;
}
}

View File

@@ -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 { }

View File

@@ -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();
}
}

View File

@@ -0,0 +1,64 @@
package bytecodeAnalysis.data;
import bytecodeAnalysis.ExpectNoPsiKey;
/**
* @author lambdamix
*/
public class TestConverterData {
public static class StaticNestedClass {
public StaticNestedClass(Object o) { }
public StaticNestedClass[] test01(StaticNestedClass[] ns, StaticNestedClass... ellipsis) {
return ns;
}
}
public class InnerClass {
// a reference to outer class should be inserted when translating PSI -> ASM
public InnerClass(Object o) { }
public InnerClass[] Inner2test01(InnerClass[] tests, InnerClass... ellipsis) {
return tests;
}
}
public static class GenericStaticNestedClass<A> {
public GenericStaticNestedClass(A a) { }
public GenericStaticNestedClass[] test01(GenericStaticNestedClass[] ns, GenericStaticNestedClass... ellipsis) {
return ns;
}
public GenericStaticNestedClass<A>[] test02(GenericStaticNestedClass<A>[] ns, GenericStaticNestedClass<A>... ellipsis) {
return ns;
}
public class GenericInnerClass<B> {
public GenericInnerClass(B b) { }
public <C> GenericStaticNestedClass<A> test01(GenericInnerClass<C> c) {
return GenericStaticNestedClass.this;
}
}
}
public TestConverterData(int x) { }
// ExtConverterClass class is not in the class roots, so translation from PSI is impossible
@ExpectNoPsiKey
public ExtTestConverterData test01(ExtTestConverterData converter) {
return converter;
}
@TestAnnotation
public TestConverterData[] test02(@TestAnnotation TestConverterData[] tests) {
return tests;
}
public boolean[] test03(boolean[] b) {
return b;
}
}
class ExtTestConverterData { }

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,12 @@
package bytecodeAnalysis.data;
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() {
// Though purity can be inferred for this method, due to collision we erase inference result
}
void test20917() {
System.out.println("non-pure");
}
}

View File

@@ -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);
}
}

View File

@@ -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 "";
}
}

View File

@@ -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;
}
}