mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -1141,7 +1141,7 @@ public class BuildManager implements Disposable {
|
||||
}
|
||||
|
||||
if (StreamProviderKt.isExternalStorageEnabled()) {
|
||||
cmdLine.addParameter("-Dexternal.project.config=" + ProjectUtil.getExternalConfigurationDir(project));
|
||||
cmdLine.addParameter("-D" + GlobalOptions.EXTERNAL_PROJECT_CONFIG + "=" + ProjectUtil.getExternalConfigurationDir(project));
|
||||
}
|
||||
|
||||
final String shouldGenerateIndex = System.getProperty(GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION);
|
||||
|
||||
+6
-6
@@ -69,8 +69,8 @@ class AbstractValues {
|
||||
}
|
||||
}
|
||||
static final class CallResultValue extends BasicValue {
|
||||
final Set<Key> inters;
|
||||
CallResultValue(Type tp, Set<Key> inters) {
|
||||
final Set<EKey> inters;
|
||||
CallResultValue(Type tp, Set<EKey> inters) {
|
||||
super(tp);
|
||||
this.inters = inters;
|
||||
}
|
||||
@@ -161,8 +161,8 @@ class AbstractValues {
|
||||
static boolean equiv(BasicValue curr, BasicValue prev) {
|
||||
if (curr.getClass() == prev.getClass()) {
|
||||
if (curr instanceof CallResultValue && prev instanceof CallResultValue) {
|
||||
Set<Key> keys1 = ((CallResultValue)prev).inters;
|
||||
Set<Key> keys2 = ((CallResultValue)curr).inters;
|
||||
Set<EKey> keys1 = ((CallResultValue)prev).inters;
|
||||
Set<EKey> keys2 = ((CallResultValue)curr).inters;
|
||||
return keys1.equals(keys2);
|
||||
}
|
||||
else return true;
|
||||
@@ -220,7 +220,7 @@ abstract class Analysis<Res> {
|
||||
final DFSTree dfsTree;
|
||||
|
||||
final protected List<State>[] computed;
|
||||
final Key aKey;
|
||||
final EKey aKey;
|
||||
|
||||
Res earlyResult;
|
||||
|
||||
@@ -231,7 +231,7 @@ abstract class Analysis<Res> {
|
||||
methodNode = controlFlow.methodNode;
|
||||
method = new Method(controlFlow.className, methodNode.name, methodNode.desc);
|
||||
dfsTree = richControlFlow.dfsTree;
|
||||
aKey = new Key(method, direction, stable);
|
||||
aKey = new EKey(method, direction, stable);
|
||||
computed = (List<State>[]) new List[controlFlow.transitions.length];
|
||||
}
|
||||
|
||||
|
||||
+26
-127
@@ -17,9 +17,7 @@ package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint;
|
||||
import com.intellij.codeInspection.dataFlow.StandardMethodContract;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.util.ThreadLocalCachedValue;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
@@ -39,12 +37,6 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys
|
||||
*/
|
||||
public class BytecodeAnalysisConverter {
|
||||
|
||||
// how many bytes are taken from class fqn digest
|
||||
public static final int CLASS_HASH_SIZE = 10;
|
||||
// how many bytes are taken from signature digest
|
||||
public static final int SIGNATURE_HASH_SIZE = 4;
|
||||
public static final int HASH_SIZE = CLASS_HASH_SIZE + SIGNATURE_HASH_SIZE;
|
||||
|
||||
private static final ThreadLocalCachedValue<MessageDigest> HASHER_CACHE = new ThreadLocalCachedValue<MessageDigest>() {
|
||||
@Override
|
||||
public MessageDigest create() {
|
||||
@@ -66,118 +58,26 @@ public class BytecodeAnalysisConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an equation over asm keys into equation over small hash keys.
|
||||
*/
|
||||
@NotNull
|
||||
static DirectionResultPair convert(@NotNull Equation equation, @NotNull MessageDigest md) {
|
||||
ProgressManager.checkCanceled();
|
||||
|
||||
Result rhs = equation.rhs;
|
||||
HResult hResult;
|
||||
if (rhs instanceof Final) {
|
||||
hResult = new HFinal(((Final)rhs).value);
|
||||
}
|
||||
else if (rhs instanceof Pending) {
|
||||
Pending pending = (Pending)rhs;
|
||||
Set<Product> sumOrigin = pending.sum;
|
||||
HComponent[] components = new HComponent[sumOrigin.size()];
|
||||
int componentI = 0;
|
||||
for (Product prod : sumOrigin) {
|
||||
HKey[] intProd = new HKey[prod.ids.size()];
|
||||
int idI = 0;
|
||||
for (Key key : prod.ids) {
|
||||
intProd[idI] = asmKey(key, md);
|
||||
idI++;
|
||||
}
|
||||
HComponent intIdComponent = new HComponent(prod.value, intProd);
|
||||
components[componentI] = intIdComponent;
|
||||
componentI++;
|
||||
}
|
||||
hResult = new HPending(components);
|
||||
} else {
|
||||
Effects wrapper = (Effects)rhs;
|
||||
Set<EffectQuantum> effects = wrapper.effects;
|
||||
Set<HEffectQuantum> hEffects = new HashSet<>();
|
||||
for (EffectQuantum effect : effects) {
|
||||
if (effect == EffectQuantum.TopEffectQuantum) {
|
||||
hEffects.add(HEffectQuantum.TopEffectQuantum);
|
||||
}
|
||||
else if (effect == EffectQuantum.ThisChangeQuantum) {
|
||||
hEffects.add(HEffectQuantum.ThisChangeQuantum);
|
||||
}
|
||||
else if (effect instanceof EffectQuantum.ParamChangeQuantum) {
|
||||
EffectQuantum.ParamChangeQuantum paramChangeQuantum = (EffectQuantum.ParamChangeQuantum)effect;
|
||||
hEffects.add(new HEffectQuantum.ParamChangeQuantum(paramChangeQuantum.n));
|
||||
}
|
||||
else if (effect instanceof EffectQuantum.CallQuantum) {
|
||||
EffectQuantum.CallQuantum callQuantum = (EffectQuantum.CallQuantum)effect;
|
||||
hEffects.add(new HEffectQuantum.CallQuantum(asmKey(callQuantum.key, md), callQuantum.data, callQuantum.isStatic));
|
||||
}
|
||||
}
|
||||
hResult = new HEffects(hEffects);
|
||||
}
|
||||
return new DirectionResultPair(equation.id.direction.asInt(), hResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an asm method key to a small hash key (HKey)
|
||||
*/
|
||||
@NotNull
|
||||
public static HKey asmKey(@NotNull Key key, @NotNull MessageDigest md) {
|
||||
byte[] classDigest = md.digest(key.method.internalClassName.getBytes(CharsetToolkit.UTF8_CHARSET));
|
||||
md.update(key.method.methodName.getBytes(CharsetToolkit.UTF8_CHARSET));
|
||||
md.update(key.method.methodDesc.getBytes(CharsetToolkit.UTF8_CHARSET));
|
||||
byte[] sigDigest = md.digest();
|
||||
byte[] digest = new byte[HASH_SIZE];
|
||||
System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE);
|
||||
System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE);
|
||||
return new HKey(digest, key.direction.asInt(), key.stable, key.negated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Psi method to a small hash key (HKey).
|
||||
* Converts a Psi method to a hashed EKey.
|
||||
* Returns null if conversion is impossible (something is not resolvable).
|
||||
*/
|
||||
@Nullable
|
||||
public static HKey psiKey(@NotNull PsiMethod psiMethod, @NotNull Direction direction, @NotNull MessageDigest md) {
|
||||
final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false);
|
||||
public static EKey psiKey(@NotNull PsiMethod psiMethod, @NotNull Direction direction, @NotNull MessageDigest md) {
|
||||
final PsiClass psiClass = psiMethod.getContainingClass();
|
||||
if (psiClass == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] classDigest = psiClassDigest(psiClass, md);
|
||||
if (classDigest == null) {
|
||||
String className = descriptor(psiClass, 0, false);
|
||||
String methodSig = methodSignature(psiMethod);
|
||||
if (className == null || methodSig == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] sigDigest = methodDigest(psiMethod, md);
|
||||
if (sigDigest == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] digest = new byte[HASH_SIZE];
|
||||
System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE);
|
||||
System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE);
|
||||
return new HKey(digest, direction.asInt(), true, false);
|
||||
String methodName = psiMethod.getReturnType() == null ? "<init>" : psiMethod.getName();
|
||||
return new EKey(new Method(className, methodName, methodSig).hashed(md), direction, true, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] psiClassDigest(@NotNull PsiClass psiClass, @NotNull MessageDigest md) {
|
||||
String descriptor = descriptor(psiClass, 0, false);
|
||||
if (descriptor == null) {
|
||||
return null;
|
||||
}
|
||||
return md.digest(descriptor.getBytes(CharsetToolkit.UTF8_CHARSET));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] methodDigest(@NotNull PsiMethod psiMethod, @NotNull MessageDigest md) {
|
||||
String descriptor = descriptor(psiMethod);
|
||||
if (descriptor == null) {
|
||||
return null;
|
||||
}
|
||||
return md.digest(descriptor.getBytes(CharsetToolkit.UTF8_CHARSET));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String descriptor(@NotNull PsiMethod psiMethod) {
|
||||
private static String methodSignature(@NotNull PsiMethod psiMethod) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false);
|
||||
if (psiClass == null) {
|
||||
@@ -188,7 +88,6 @@ public class BytecodeAnalysisConverter {
|
||||
PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
|
||||
PsiType returnType = psiMethod.getReturnType();
|
||||
|
||||
sb.append(returnType == null ? "<init>" : psiMethod.getName());
|
||||
sb.append('(');
|
||||
|
||||
String desc;
|
||||
@@ -317,16 +216,16 @@ public class BytecodeAnalysisConverter {
|
||||
|
||||
|
||||
/**
|
||||
* Given a PSI method and its primary HKey enumerate all contract keys for it.
|
||||
* Given a PSI method and its primary Key enumerate all contract keys for it.
|
||||
*
|
||||
* @param psiMethod psi method
|
||||
* @param primaryKey primary stable keys
|
||||
* @return corresponding (stable!) keys
|
||||
*/
|
||||
@NotNull
|
||||
public static ArrayList<HKey> mkInOutKeys(@NotNull PsiMethod psiMethod, @NotNull HKey primaryKey) {
|
||||
public static ArrayList<EKey> mkInOutKeys(@NotNull PsiMethod psiMethod, @NotNull EKey primaryKey) {
|
||||
PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
|
||||
ArrayList<HKey> keys = new ArrayList<>(parameters.length * 2 + 2);
|
||||
ArrayList<EKey> keys = new ArrayList<>(parameters.length * 2 + 2);
|
||||
keys.add(primaryKey);
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
if (!(parameters[i].getType() instanceof PsiPrimitiveType)) {
|
||||
@@ -352,21 +251,21 @@ public class BytecodeAnalysisConverter {
|
||||
* @param methodKey a primary key of a method being analyzed. not it is stable
|
||||
* @param arity arity of this method (hint for constructing @Contract annotations)
|
||||
*/
|
||||
public static void addMethodAnnotations(@NotNull Map<HKey, Value> solution, @NotNull MethodAnnotations methodAnnotations, @NotNull HKey methodKey, int arity) {
|
||||
public static void addMethodAnnotations(@NotNull Map<EKey, Value> solution, @NotNull MethodAnnotations methodAnnotations, @NotNull EKey methodKey, int arity) {
|
||||
List<StandardMethodContract> contractClauses = new ArrayList<>();
|
||||
Set<HKey> notNulls = methodAnnotations.notNulls;
|
||||
Set<HKey> pures = methodAnnotations.pures;
|
||||
Map<HKey, String> contracts = methodAnnotations.contractsValues;
|
||||
Set<EKey> notNulls = methodAnnotations.notNulls;
|
||||
Set<EKey> pures = methodAnnotations.pures;
|
||||
Map<EKey, String> contracts = methodAnnotations.contractsValues;
|
||||
|
||||
for (Map.Entry<HKey, Value> entry : solution.entrySet()) {
|
||||
for (Map.Entry<EKey, Value> entry : solution.entrySet()) {
|
||||
// NB: keys from Psi are always stable, so we need to stabilize keys from equations
|
||||
Value value = entry.getValue();
|
||||
if (value == Value.Top || value == Value.Bot || (value == Value.Fail && !pures.contains(methodKey))) {
|
||||
continue;
|
||||
}
|
||||
HKey key = entry.getKey().mkStable();
|
||||
EKey key = entry.getKey().mkStable();
|
||||
Direction direction = key.getDirection();
|
||||
HKey baseKey = key.mkBase();
|
||||
EKey baseKey = key.mkBase();
|
||||
if (!methodKey.equals(baseKey)) {
|
||||
continue;
|
||||
}
|
||||
@@ -433,18 +332,18 @@ public class BytecodeAnalysisConverter {
|
||||
return contractClauses;
|
||||
}
|
||||
|
||||
public static void addEffectAnnotations(Map<HKey, Set<HEffectQuantum>> puritySolutions,
|
||||
public static void addEffectAnnotations(Map<EKey, Set<EffectQuantum>> puritySolutions,
|
||||
MethodAnnotations result,
|
||||
HKey methodKey,
|
||||
EKey methodKey,
|
||||
boolean constructor) {
|
||||
for (Map.Entry<HKey, Set<HEffectQuantum>> entry : puritySolutions.entrySet()) {
|
||||
Set<HEffectQuantum> effects = entry.getValue();
|
||||
HKey key = entry.getKey().mkStable();
|
||||
HKey baseKey = key.mkBase();
|
||||
for (Map.Entry<EKey, Set<EffectQuantum>> entry : puritySolutions.entrySet()) {
|
||||
Set<EffectQuantum> effects = entry.getValue();
|
||||
EKey key = entry.getKey().mkStable();
|
||||
EKey baseKey = key.mkBase();
|
||||
if (!methodKey.equals(baseKey)) {
|
||||
continue;
|
||||
}
|
||||
if (effects.isEmpty() || (constructor && effects.size() == 1 && effects.contains(HEffectQuantum.ThisChangeQuantum))) {
|
||||
if (effects.isEmpty() || (constructor && effects.size() == 1 && effects.contains(EffectQuantum.ThisChangeQuantum))) {
|
||||
// Pure constructor is allowed to change "this" object as this is a new object anyways
|
||||
result.pures.add(methodKey);
|
||||
}
|
||||
|
||||
+63
-62
@@ -49,21 +49,21 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
|
||||
private static final ID<Bytes, Void> NAME = ID.create("bytecodeAnalysis");
|
||||
public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
|
||||
private static final ID<HMethod, Void> NAME = ID.create("bytecodeAnalysis");
|
||||
private static final HKeyDescriptor KEY_DESCRIPTOR = new HKeyDescriptor();
|
||||
private static final VirtualFileGist<Map<Bytes, HEquations>> ourGist = GistManager.getInstance().newVirtualFileGist(
|
||||
"BytecodeAnalysisIndex", 5, new HEquationsExternalizer(), new ClassDataIndexer());
|
||||
private static final VirtualFileGist<Map<HMethod, Equations>> ourGist = GistManager.getInstance().newVirtualFileGist(
|
||||
"BytecodeAnalysisIndex", 7, new EquationsExternalizer(), new ClassDataIndexer());
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ID<Bytes, Void> getName() {
|
||||
public ID<HMethod, Void> getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DataIndexer<Bytes, Void, FileContent> getIndexer() {
|
||||
public DataIndexer<HMethod, Void, FileContent> getIndexer() {
|
||||
return inputData -> {
|
||||
try {
|
||||
return collectKeys(inputData.getContent());
|
||||
@@ -81,14 +81,14 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Map<Bytes, Void> collectKeys(byte[] content) throws NoSuchAlgorithmException {
|
||||
HashMap<Bytes, Void> map = new HashMap<>();
|
||||
private static Map<HMethod, Void> collectKeys(byte[] content) throws NoSuchAlgorithmException {
|
||||
HashMap<HMethod, Void> map = new HashMap<>();
|
||||
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
|
||||
new ClassReader(content).accept(new KeyedMethodVisitor() {
|
||||
@Nullable
|
||||
@Override
|
||||
MethodVisitor visitMethod(MethodNode node, Key key) {
|
||||
map.put(ClassDataIndexer.compressKey(md, key), null);
|
||||
MethodVisitor visitMethod(MethodNode node, Method method, EKey key) {
|
||||
map.put(method.hashed(md), null);
|
||||
return null;
|
||||
}
|
||||
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
@@ -97,7 +97,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public KeyDescriptor<Bytes> getKeyDescriptor() {
|
||||
public KeyDescriptor<HMethod> getKeyDescriptor() {
|
||||
return KEY_DESCRIPTOR;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static List<HEquations> getEquations(GlobalSearchScope scope, Bytes key) {
|
||||
static List<Equations> getEquations(GlobalSearchScope scope, HMethod key) {
|
||||
Project project = ProjectManager.getInstance().getDefaultProject(); // the data is project-independent
|
||||
return ContainerUtil.mapNotNull(FileBasedIndex.getInstance().getContainingFiles(NAME, key, scope),
|
||||
file -> ourGist.getFileData(project, file).get(key));
|
||||
@@ -132,37 +132,37 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
|
||||
/**
|
||||
* Externalizer for primary method keys.
|
||||
*/
|
||||
private static class HKeyDescriptor implements KeyDescriptor<Bytes>, DifferentSerializableBytesImplyNonEqualityPolicy {
|
||||
private static class HKeyDescriptor implements KeyDescriptor<HMethod>, DifferentSerializableBytesImplyNonEqualityPolicy {
|
||||
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, Bytes value) throws IOException {
|
||||
out.write(value.bytes);
|
||||
public void save(@NotNull DataOutput out, HMethod value) throws IOException {
|
||||
out.write(value.myBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bytes read(@NotNull DataInput in) throws IOException {
|
||||
byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE];
|
||||
public HMethod read(@NotNull DataInput in) throws IOException {
|
||||
byte[] bytes = new byte[HMethod.HASH_SIZE];
|
||||
in.readFully(bytes);
|
||||
return new Bytes(bytes);
|
||||
return new HMethod(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHashCode(Bytes value) {
|
||||
return Arrays.hashCode(value.bytes);
|
||||
public int getHashCode(HMethod value) {
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(Bytes val1, Bytes val2) {
|
||||
return Arrays.equals(val1.bytes, val2.bytes);
|
||||
public boolean isEqual(HMethod val1, HMethod val2) {
|
||||
return val1.equals(val2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Externalizer for compressed equations.
|
||||
*/
|
||||
public static class HEquationsExternalizer implements DataExternalizer<Map<Bytes, HEquations>> {
|
||||
public static class EquationsExternalizer implements DataExternalizer<Map<HMethod, Equations>> {
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, Map<Bytes, HEquations> value) throws IOException {
|
||||
public void save(@NotNull DataOutput out, Map<HMethod, Equations> value) throws IOException {
|
||||
DataInputOutputUtilRt.writeSeq(out, value.entrySet(), entry -> {
|
||||
KEY_DESCRIPTOR.save(out, entry.getKey());
|
||||
saveEquations(out, entry.getValue());
|
||||
@@ -170,53 +170,54 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Bytes, HEquations> read(@NotNull DataInput in) throws IOException {
|
||||
public Map<HMethod, Equations> read(@NotNull DataInput in) throws IOException {
|
||||
return DataInputOutputUtilRt.readSeq(in, () -> Pair.create(KEY_DESCRIPTOR.read(in), readEquations(in))).
|
||||
stream().collect(Collectors.toMap(p -> p.getFirst(), p -> p.getSecond()));
|
||||
}
|
||||
|
||||
private static void saveEquations(@NotNull DataOutput out, HEquations eqs) throws IOException {
|
||||
private static void saveEquations(@NotNull DataOutput out, Equations eqs) throws IOException {
|
||||
out.writeBoolean(eqs.stable);
|
||||
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
|
||||
DataInputOutputUtil.writeINT(out, eqs.results.size());
|
||||
for (DirectionResultPair pair : eqs.results) {
|
||||
DataInputOutputUtil.writeINT(out, pair.directionKey);
|
||||
HResult rhs = pair.hResult;
|
||||
if (rhs instanceof HFinal) {
|
||||
HFinal finalResult = (HFinal)rhs;
|
||||
Result rhs = pair.hResult;
|
||||
if (rhs instanceof Final) {
|
||||
Final finalResult = (Final)rhs;
|
||||
out.writeBoolean(true); // final flag
|
||||
DataInputOutputUtil.writeINT(out, finalResult.value.ordinal());
|
||||
}
|
||||
else if (rhs instanceof HPending) {
|
||||
HPending pendResult = (HPending)rhs;
|
||||
else if (rhs instanceof Pending) {
|
||||
Pending pendResult = (Pending)rhs;
|
||||
out.writeBoolean(false); // pending flag
|
||||
DataInputOutputUtil.writeINT(out, pendResult.delta.length);
|
||||
|
||||
for (HComponent component : pendResult.delta) {
|
||||
for (Component component : pendResult.delta) {
|
||||
DataInputOutputUtil.writeINT(out, component.value.ordinal());
|
||||
HKey[] ids = component.ids;
|
||||
EKey[] ids = component.ids;
|
||||
DataInputOutputUtil.writeINT(out, ids.length);
|
||||
for (HKey hKey : ids) {
|
||||
out.write(hKey.key);
|
||||
for (EKey hKey : ids) {
|
||||
out.write(hKey.method.hashed(md).myBytes);
|
||||
int rawDirKey = hKey.negated ? -hKey.dirKey : hKey.dirKey;
|
||||
DataInputOutputUtil.writeINT(out, rawDirKey);
|
||||
out.writeBoolean(hKey.stable);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (rhs instanceof HEffects) {
|
||||
HEffects effects = (HEffects)rhs;
|
||||
else if (rhs instanceof Effects) {
|
||||
Effects effects = (Effects)rhs;
|
||||
DataInputOutputUtil.writeINT(out, effects.effects.size());
|
||||
for (HEffectQuantum effect : effects.effects) {
|
||||
if (effect == HEffectQuantum.TopEffectQuantum) {
|
||||
for (EffectQuantum effect : effects.effects) {
|
||||
if (effect == EffectQuantum.TopEffectQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, -1);
|
||||
}
|
||||
else if (effect == HEffectQuantum.ThisChangeQuantum) {
|
||||
else if (effect == EffectQuantum.ThisChangeQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, -2);
|
||||
}
|
||||
else if (effect instanceof HEffectQuantum.CallQuantum) {
|
||||
else if (effect instanceof EffectQuantum.CallQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, -3);
|
||||
HEffectQuantum.CallQuantum callQuantum = (HEffectQuantum.CallQuantum)effect;
|
||||
out.write(callQuantum.key.key);
|
||||
EffectQuantum.CallQuantum callQuantum = (EffectQuantum.CallQuantum)effect;
|
||||
out.write(callQuantum.key.method.hashed(md).myBytes);
|
||||
DataInputOutputUtil.writeINT(out, callQuantum.key.dirKey);
|
||||
out.writeBoolean(callQuantum.key.stable);
|
||||
out.writeBoolean(callQuantum.isStatic);
|
||||
@@ -242,15 +243,15 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (effect instanceof HEffectQuantum.ParamChangeQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, ((HEffectQuantum.ParamChangeQuantum)effect).n);
|
||||
else if (effect instanceof EffectQuantum.ParamChangeQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, ((EffectQuantum.ParamChangeQuantum)effect).n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static HEquations readEquations(@NotNull DataInput in) throws IOException {
|
||||
private static Equations readEquations(@NotNull DataInput in) throws IOException {
|
||||
boolean stable = in.readBoolean();
|
||||
int size = DataInputOutputUtil.readINT(in);
|
||||
ArrayList<DirectionResultPair> results = new ArrayList<>(size);
|
||||
@@ -258,22 +259,22 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
|
||||
int directionKey = DataInputOutputUtil.readINT(in);
|
||||
Direction direction = Direction.fromInt(directionKey);
|
||||
if (direction == Direction.Pure) {
|
||||
Set<HEffectQuantum> effects = new HashSet<>();
|
||||
Set<EffectQuantum> effects = new HashSet<>();
|
||||
int effectsSize = DataInputOutputUtil.readINT(in);
|
||||
for (int i = 0; i < effectsSize; i++) {
|
||||
int effectMask = DataInputOutputUtil.readINT(in);
|
||||
if (effectMask == -1) {
|
||||
effects.add(HEffectQuantum.TopEffectQuantum);
|
||||
effects.add(EffectQuantum.TopEffectQuantum);
|
||||
}
|
||||
else if (effectMask == -2) {
|
||||
effects.add(HEffectQuantum.ThisChangeQuantum);
|
||||
effects.add(EffectQuantum.ThisChangeQuantum);
|
||||
}
|
||||
else if (effectMask == -3){
|
||||
byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE];
|
||||
byte[] bytes = new byte[HMethod.HASH_SIZE];
|
||||
in.readFully(bytes);
|
||||
int rawDirKey = DataInputOutputUtil.readINT(in);
|
||||
boolean isStable = in.readBoolean();
|
||||
HKey key = new HKey(bytes, Math.abs(rawDirKey), isStable, false);
|
||||
EKey key = new EKey(new HMethod(bytes), Math.abs(rawDirKey), isStable, false);
|
||||
boolean isStatic = in.readBoolean();
|
||||
int dataLength = DataInputOutputUtil.readINT(in);
|
||||
DataValue[] data = new DataValue[dataLength];
|
||||
@@ -298,43 +299,43 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
|
||||
data[di] = new DataValue.ParameterDataValue(dataI);
|
||||
}
|
||||
}
|
||||
effects.add(new HEffectQuantum.CallQuantum(key, data, isStatic));
|
||||
effects.add(new EffectQuantum.CallQuantum(key, data, isStatic));
|
||||
}
|
||||
else {
|
||||
effects.add(new HEffectQuantum.ParamChangeQuantum(effectMask));
|
||||
effects.add(new EffectQuantum.ParamChangeQuantum(effectMask));
|
||||
}
|
||||
}
|
||||
results.add(new DirectionResultPair(directionKey, new HEffects(effects)));
|
||||
results.add(new DirectionResultPair(directionKey, new Effects(effects)));
|
||||
}
|
||||
else {
|
||||
boolean isFinal = in.readBoolean(); // flag
|
||||
if (isFinal) {
|
||||
int ordinal = DataInputOutputUtil.readINT(in);
|
||||
Value value = Value.values()[ordinal];
|
||||
results.add(new DirectionResultPair(directionKey, new HFinal(value)));
|
||||
results.add(new DirectionResultPair(directionKey, new Final(value)));
|
||||
}
|
||||
else {
|
||||
int sumLength = DataInputOutputUtil.readINT(in);
|
||||
HComponent[] components = new HComponent[sumLength];
|
||||
Component[] components = new Component[sumLength];
|
||||
|
||||
for (int i = 0; i < sumLength; i++) {
|
||||
int ordinal = DataInputOutputUtil.readINT(in);
|
||||
Value value = Value.values()[ordinal];
|
||||
int componentSize = DataInputOutputUtil.readINT(in);
|
||||
HKey[] ids = new HKey[componentSize];
|
||||
EKey[] ids = new EKey[componentSize];
|
||||
for (int j = 0; j < componentSize; j++) {
|
||||
byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE];
|
||||
byte[] bytes = new byte[HMethod.HASH_SIZE];
|
||||
in.readFully(bytes);
|
||||
int rawDirKey = DataInputOutputUtil.readINT(in);
|
||||
ids[j] = new HKey(bytes, Math.abs(rawDirKey), in.readBoolean(), rawDirKey < 0);
|
||||
ids[j] = new EKey(new HMethod(bytes), Direction.fromInt(Math.abs(rawDirKey)), in.readBoolean(), rawDirKey < 0);
|
||||
}
|
||||
components[i] = new HComponent(value, ids);
|
||||
components[i] = new Component(value, ids);
|
||||
}
|
||||
results.add(new DirectionResultPair(directionKey, new HPending(components)));
|
||||
results.add(new DirectionResultPair(directionKey, new Pending(components)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new HEquations(results, stable);
|
||||
return new Equations(results, stable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+37
-43
@@ -46,7 +46,7 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys
|
||||
*
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Bytes, HEquations>> {
|
||||
public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMethod, Equations>> {
|
||||
|
||||
public static final Final FINAL_TOP = new Final(Value.Top);
|
||||
public static final Final FINAL_FAIL = new Final(Value.Fail);
|
||||
@@ -56,12 +56,12 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<Bytes, HEquations> calcData(@NotNull Project project, @NotNull VirtualFile file) {
|
||||
HashMap<Bytes, HEquations> map = new HashMap<>();
|
||||
public Map<HMethod, Equations> calcData(@NotNull Project project, @NotNull VirtualFile file) {
|
||||
HashMap<HMethod, Equations> map = new HashMap<>();
|
||||
try {
|
||||
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
|
||||
Map<Key, List<Equation>> allEquations = processClass(new ClassReader(file.contentsToByteArray(false)), file.getPresentableUrl());
|
||||
allEquations.forEach((methodKey, equations) -> map.put(compressKey(md, methodKey), convertEquations(md, methodKey, equations)));
|
||||
Map<EKey, List<Equation>> allEquations = processClass(new ClassReader(file.contentsToByteArray(false)), file.getPresentableUrl());
|
||||
allEquations.forEach((methodKey, equations) -> map.put(methodKey.method.hashed(md), convertEquations(methodKey, equations)));
|
||||
}
|
||||
catch (ProcessCanceledException e) {
|
||||
throw e;
|
||||
@@ -75,18 +75,13 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static Bytes compressKey(MessageDigest md, Key methodKey) {
|
||||
return new Bytes(BytecodeAnalysisConverter.asmKey(methodKey, md).key);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static HEquations convertEquations(MessageDigest md, Key methodKey, List<Equation> rawMethodEquations) {
|
||||
private static Equations convertEquations(EKey methodKey, List<Equation> rawMethodEquations) {
|
||||
List<DirectionResultPair> compressedMethodEquations =
|
||||
ContainerUtil.map(rawMethodEquations, equation -> BytecodeAnalysisConverter.convert(equation, md));
|
||||
return new HEquations(compressedMethodEquations, methodKey.stable);
|
||||
ContainerUtil.map(rawMethodEquations, equation -> new DirectionResultPair(equation.key.dirKey, equation.result));
|
||||
return new Equations(compressedMethodEquations, methodKey.stable);
|
||||
}
|
||||
|
||||
public static Map<Key, List<Equation>> processClass(final ClassReader classReader, final String presentableUrl) {
|
||||
public static Map<EKey, List<Equation>> processClass(final ClassReader classReader, final String presentableUrl) {
|
||||
|
||||
// It is OK to share pending states, actions and results for analyses.
|
||||
// Analyses are designed in such a way that they first write to states/actions/results and then read only those portion
|
||||
@@ -95,11 +90,11 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
|
||||
final State[] sharedPendingStates = new State[Analysis.STEPS_LIMIT];
|
||||
final PendingAction[] sharedPendingActions = new PendingAction[Analysis.STEPS_LIMIT];
|
||||
final PResults.PResult[] sharedResults = new PResults.PResult[Analysis.STEPS_LIMIT];
|
||||
final Map<Key, List<Equation>> equations = new HashMap<>();
|
||||
final Map<EKey, List<Equation>> equations = new HashMap<>();
|
||||
|
||||
classReader.accept(new KeyedMethodVisitor() {
|
||||
|
||||
protected MethodVisitor visitMethod(final MethodNode node, final Key key) {
|
||||
protected MethodVisitor visitMethod(final MethodNode node, Method method, final EKey key) {
|
||||
return new MethodVisitor(Opcodes.API_VERSION, node) {
|
||||
private boolean jsr;
|
||||
|
||||
@@ -114,7 +109,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
super.visitEnd();
|
||||
equations.put(key, processMethod(node, jsr, key.method, key.stable));
|
||||
equations.put(key, processMethod(node, jsr, method, key.stable));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -133,9 +128,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
|
||||
final boolean isBooleanResult = ASMUtils.isBooleanType(resultType);
|
||||
final boolean isInterestingResult = isReferenceResult || isBooleanResult;
|
||||
|
||||
// 4*n: for each reference parameter: @NotNull IN, @Nullable, null -> ... contract, !null -> contract
|
||||
// 3: @NotNull OUT, @Nullable OUT, purity analysis
|
||||
List<Equation> equations = new ArrayList<>(argumentTypes.length * 4 + 3);
|
||||
List<Equation> equations = new ArrayList<>();
|
||||
equations.add(PurityAnalysis.analyze(method, methodNode, stable));
|
||||
|
||||
try {
|
||||
@@ -300,14 +293,14 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
|
||||
|
||||
if (isReferenceResult) {
|
||||
result.add(outEquation);
|
||||
result.add(new Equation(new Key(method, NullableOut, stable), NullableMethodAnalysis.analyze(methodNode, origins, jsr)));
|
||||
result.add(new Equation(new EKey(method, NullableOut, stable), NullableMethodAnalysis.analyze(methodNode, origins, jsr)));
|
||||
}
|
||||
final boolean shouldInferNonTrivialFailingContracts;
|
||||
final Equation throwEquation;
|
||||
if(methodNode.name.equals("<init>")) {
|
||||
// Do not infer failing contracts for constructors
|
||||
shouldInferNonTrivialFailingContracts = false;
|
||||
throwEquation = new Equation(new Key(method, Throw, stable), FINAL_TOP);
|
||||
throwEquation = new Equation(new EKey(method, Throw, stable), FINAL_TOP);
|
||||
} else {
|
||||
final InThrowAnalysis inThrowAnalysis = new InThrowAnalysis(richControlFlow, Throw, origins, stable, sharedPendingStates);
|
||||
throwEquation = inThrowAnalysis.analyze();
|
||||
@@ -333,8 +326,8 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
|
||||
}
|
||||
if (shouldInferNonTrivialFailingContracts) {
|
||||
InThrow direction = new InThrow(index, val);
|
||||
if (throwEquation.rhs.equals(FINAL_FAIL)) {
|
||||
builder.add(new Equation(new Key(method, direction, stable), FINAL_FAIL));
|
||||
if (throwEquation.result.equals(FINAL_FAIL)) {
|
||||
builder.add(new Equation(new EKey(method, direction, stable), FINAL_FAIL));
|
||||
}
|
||||
else {
|
||||
builder.add(new InThrowAnalysis(richControlFlow, direction, origins, stable, sharedPendingStates).analyze());
|
||||
@@ -354,39 +347,40 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
|
||||
boolean possibleNPE = false;
|
||||
if (leakingParameters[i]) {
|
||||
NonNullInAnalysis notNullInAnalysis =
|
||||
new NonNullInAnalysis(richControlFlow, new In(i, In.NOT_NULL_MASK), stable, sharedPendingActions, sharedResults);
|
||||
new NonNullInAnalysis(richControlFlow, new In(i, false), stable, sharedPendingActions, sharedResults);
|
||||
Equation notNullParamEquation = notNullInAnalysis.analyze();
|
||||
possibleNPE = notNullInAnalysis.possibleNPE;
|
||||
notNullParam = notNullParamEquation.rhs.equals(FINAL_NOT_NULL);
|
||||
notNullParam = notNullParamEquation.result.equals(FINAL_NOT_NULL);
|
||||
result.add(notNullParamEquation);
|
||||
}
|
||||
else {
|
||||
// parameter is not leaking, so it is definitely NOT @NotNull
|
||||
result.add(new Equation(new Key(method, new In(i, In.NOT_NULL_MASK), stable), FINAL_TOP));
|
||||
result.add(new Equation(new EKey(method, new In(i, false), stable), FINAL_TOP));
|
||||
}
|
||||
|
||||
if (leakingNullableParameters[i]) {
|
||||
if (notNullParam || possibleNPE) {
|
||||
result.add(new Equation(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_TOP));
|
||||
result.add(new Equation(new EKey(method, new In(i, true), stable), FINAL_TOP));
|
||||
}
|
||||
else {
|
||||
result.add(new NullableInAnalysis(richControlFlow, new In(i, In.NULLABLE_MASK), stable, sharedPendingStates).analyze());
|
||||
result.add(new NullableInAnalysis(richControlFlow, new In(i, true), stable, sharedPendingStates).analyze());
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.add(new Equation(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_NULL));
|
||||
result.add(new Equation(new EKey(method, new In(i, true), stable), FINAL_NULL));
|
||||
}
|
||||
|
||||
if (isInterestingResult) {
|
||||
if (!leakingParameters[i]) {
|
||||
// parameter is not leaking, so a contract is the same as for the whole method
|
||||
result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), outEquation.rhs));
|
||||
result.add(new Equation(new Key(method, new InOut(i, Value.NotNull), stable), outEquation.rhs));
|
||||
result.add(new Equation(new EKey(method, new InOut(i, Value.Null), stable), outEquation.result));
|
||||
result.add(new Equation(new EKey(method, new InOut(i, Value.NotNull), stable), outEquation.result));
|
||||
continue;
|
||||
}
|
||||
if (notNullParam) {
|
||||
// @NotNull, like "null->fail"
|
||||
result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), FINAL_BOT));
|
||||
result.add(new Equation(new EKey(method, new InOut(i, Value.Null), stable), FINAL_BOT));
|
||||
result.add(new Equation(new EKey(method, new InOut(i, Value.NotNull), stable), outEquation.result));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -421,23 +415,23 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
|
||||
}
|
||||
|
||||
private List<Equation> topEquations(Method method,
|
||||
Type[] argumentTypes,
|
||||
boolean isReferenceResult,
|
||||
boolean isInterestingResult,
|
||||
boolean stable) {
|
||||
Type[] argumentTypes,
|
||||
boolean isReferenceResult,
|
||||
boolean isInterestingResult,
|
||||
boolean stable) {
|
||||
// 4 = @NotNull parameter, @Nullable parameter, null -> ..., !null -> ...
|
||||
List<Equation> result = new ArrayList<>(argumentTypes.length * 4 + 2);
|
||||
if (isReferenceResult) {
|
||||
result.add(new Equation(new Key(method, Out, stable), FINAL_TOP));
|
||||
result.add(new Equation(new Key(method, NullableOut, stable), FINAL_BOT));
|
||||
result.add(new Equation(new EKey(method, Out, stable), FINAL_TOP));
|
||||
result.add(new Equation(new EKey(method, NullableOut, stable), FINAL_BOT));
|
||||
}
|
||||
for (int i = 0; i < argumentTypes.length; i++) {
|
||||
if (ASMUtils.isReferenceType(argumentTypes[i])) {
|
||||
result.add(new Equation(new Key(method, new In(i, In.NOT_NULL_MASK), stable), FINAL_TOP));
|
||||
result.add(new Equation(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_TOP));
|
||||
result.add(new Equation(new EKey(method, new In(i, false), stable), FINAL_TOP));
|
||||
result.add(new Equation(new EKey(method, new In(i, true), stable), FINAL_TOP));
|
||||
if (isInterestingResult) {
|
||||
result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), FINAL_TOP));
|
||||
result.add(new Equation(new Key(method, new InOut(i, Value.NotNull), stable), FINAL_TOP));
|
||||
result.add(new Equation(new EKey(method, new InOut(i, Value.Null), stable), FINAL_TOP));
|
||||
result.add(new Equation(new EKey(method, new InOut(i, Value.NotNull), stable), FINAL_TOP));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-34
@@ -104,14 +104,14 @@ interface CombinedData {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Set<Key> getKeysForParameter(int idx, ParamValueBasedDirection direction) {
|
||||
Set<Key> keys = new HashSet<>();
|
||||
Set<EKey> getKeysForParameter(int idx, ParamValueBasedDirection direction) {
|
||||
Set<EKey> keys = new HashSet<>();
|
||||
for (int argI = 0; argI < this.args.size(); argI++) {
|
||||
BasicValue arg = this.args.get(argI);
|
||||
if (arg instanceof NthParamValue) {
|
||||
NthParamValue npv = (NthParamValue)arg;
|
||||
if (npv.n == idx) {
|
||||
keys.add(new Key(this.method, direction.withIndex(argI), this.stableCall));
|
||||
keys.add(new EKey(this.method, direction.withIndex(argI), this.stableCall));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,7 @@ final class CombinedAnalysis {
|
||||
}
|
||||
|
||||
final Equation notNullParamEquation(int i, boolean stable) {
|
||||
final Key key = new Key(method, new In(i, In.NOT_NULL_MASK), stable);
|
||||
final EKey key = new EKey(method, new In(i, false), stable);
|
||||
final Result result;
|
||||
if (interpreter.dereferencedParams[i]) {
|
||||
result = new Final(Value.NotNull);
|
||||
@@ -214,18 +214,18 @@ final class CombinedAnalysis {
|
||||
result = new Final(Value.Top);
|
||||
}
|
||||
else {
|
||||
Set<Key> keys = new HashSet<>();
|
||||
Set<EKey> keys = new HashSet<>();
|
||||
for (ParamKey pk: calls) {
|
||||
keys.add(new Key(pk.method, new In(pk.i, In.NOT_NULL_MASK), pk.stable));
|
||||
keys.add(new EKey(pk.method, new In(pk.i, false), pk.stable));
|
||||
}
|
||||
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
|
||||
result = new Pending(new SingletonSet<>(new Component(Value.Top, keys)));
|
||||
}
|
||||
}
|
||||
return new Equation(key, result);
|
||||
}
|
||||
|
||||
final Equation nullableParamEquation(int i, boolean stable) {
|
||||
final Key key = new Key(method, new In(i, In.NULLABLE_MASK), stable);
|
||||
final EKey key = new EKey(method, new In(i, true), stable);
|
||||
final Result result;
|
||||
if (interpreter.dereferencedParams[i] || interpreter.notNullableParams[i] || returnValue instanceof NthParamValue && ((NthParamValue)returnValue).n == i) {
|
||||
result = new Final(Value.Top);
|
||||
@@ -236,9 +236,9 @@ final class CombinedAnalysis {
|
||||
result = new Final(Value.Null);
|
||||
}
|
||||
else {
|
||||
Set<Product> sum = new HashSet<>();
|
||||
Set<Component> sum = new HashSet<>();
|
||||
for (ParamKey pk: calls) {
|
||||
sum.add(new Product(Value.Top, Collections.singleton(new Key(pk.method, new In(pk.i, In.NULLABLE_MASK), pk.stable))));
|
||||
sum.add(new Component(Value.Top, Collections.singleton(new EKey(pk.method, new In(pk.i, true), pk.stable))));
|
||||
}
|
||||
result = new Pending(sum);
|
||||
}
|
||||
@@ -249,7 +249,7 @@ final class CombinedAnalysis {
|
||||
@Nullable
|
||||
final Equation contractEquation(int i, Value inValue, boolean stable) {
|
||||
final InOut direction = new InOut(i, inValue);
|
||||
final Key key = new Key(method, direction, stable);
|
||||
final EKey key = new EKey(method, direction, stable);
|
||||
final Result result;
|
||||
if (exception || (inValue == Value.Null && interpreter.dereferencedParams[i])) {
|
||||
result = new Final(Value.Bot);
|
||||
@@ -271,14 +271,14 @@ final class CombinedAnalysis {
|
||||
}
|
||||
else if (returnValue instanceof TrackableCallValue) {
|
||||
TrackableCallValue call = (TrackableCallValue)returnValue;
|
||||
Set<Key> keys = call.getKeysForParameter(i, direction);
|
||||
Set<EKey> keys = call.getKeysForParameter(i, direction);
|
||||
if (ASMUtils.isReferenceType(call.getType())) {
|
||||
keys.add(new Key(call.method, Out, call.stableCall));
|
||||
keys.add(new EKey(call.method, Out, call.stableCall));
|
||||
}
|
||||
if (keys.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
|
||||
result = new Pending(new SingletonSet<>(new Component(Value.Top, keys)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -289,15 +289,15 @@ final class CombinedAnalysis {
|
||||
|
||||
@Nullable
|
||||
final Equation failEquation(boolean stable) {
|
||||
final Key key = new Key(method, Throw, stable);
|
||||
final EKey key = new EKey(method, Throw, stable);
|
||||
final Result result;
|
||||
if (exception) {
|
||||
result = new Final(Value.Fail);
|
||||
}
|
||||
else if (!interpreter.calls.isEmpty()) {
|
||||
Set<Key> keys =
|
||||
interpreter.calls.stream().map(call -> new Key(call.method, Throw, call.stableCall)).collect(Collectors.toSet());
|
||||
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
|
||||
Set<EKey> keys =
|
||||
interpreter.calls.stream().map(call -> new EKey(call.method, Throw, call.stableCall)).collect(Collectors.toSet());
|
||||
result = new Pending(new SingletonSet<>(new Component(Value.Top, keys)));
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@@ -308,18 +308,18 @@ final class CombinedAnalysis {
|
||||
@Nullable
|
||||
final Equation failEquation(int i, Value inValue, boolean stable) {
|
||||
final InThrow direction = new InThrow(i, inValue);
|
||||
final Key key = new Key(method, direction, stable);
|
||||
final EKey key = new EKey(method, direction, stable);
|
||||
final Result result;
|
||||
if (exception) {
|
||||
result = new Final(Value.Fail);
|
||||
}
|
||||
else if (!interpreter.calls.isEmpty()) {
|
||||
Set<Key> keys = new HashSet<>();
|
||||
Set<EKey> keys = new HashSet<>();
|
||||
for (TrackableCallValue call : interpreter.calls) {
|
||||
keys.addAll(call.getKeysForParameter(i, direction));
|
||||
keys.add(new Key(call.method, Throw, call.stableCall));
|
||||
keys.add(new EKey(call.method, Throw, call.stableCall));
|
||||
}
|
||||
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
|
||||
result = new Pending(new SingletonSet<>(new Component(Value.Top, keys)));
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@@ -329,7 +329,7 @@ final class CombinedAnalysis {
|
||||
|
||||
@Nullable
|
||||
final Equation outContractEquation(boolean stable) {
|
||||
final Key key = new Key(method, Out, stable);
|
||||
final EKey key = new EKey(method, Out, stable);
|
||||
final Result result;
|
||||
if (exception) {
|
||||
result = new Final(Value.Bot);
|
||||
@@ -348,9 +348,9 @@ final class CombinedAnalysis {
|
||||
}
|
||||
else if (returnValue instanceof TrackableCallValue) {
|
||||
TrackableCallValue call = (TrackableCallValue)returnValue;
|
||||
Key callKey = new Key(call.method, Out, call.stableCall);
|
||||
Set<Key> keys = new SingletonSet<>(callKey);
|
||||
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
|
||||
EKey callKey = new EKey(call.method, Out, call.stableCall);
|
||||
Set<EKey> keys = new SingletonSet<>(callKey);
|
||||
result = new Pending(new SingletonSet<>(new Component(Value.Top, keys)));
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@@ -359,7 +359,7 @@ final class CombinedAnalysis {
|
||||
}
|
||||
|
||||
final Equation nullableResultEquation(boolean stable) {
|
||||
final Key key = new Key(method, NullableOut, stable);
|
||||
final EKey key = new EKey(method, NullableOut, stable);
|
||||
final Result result;
|
||||
if (exception ||
|
||||
returnValue instanceof Trackable && interpreter.dereferencedValues[((Trackable)returnValue).getOriginInsnIndex()]) {
|
||||
@@ -367,9 +367,9 @@ final class CombinedAnalysis {
|
||||
}
|
||||
else if (returnValue instanceof TrackableCallValue) {
|
||||
TrackableCallValue call = (TrackableCallValue)returnValue;
|
||||
Key callKey = new Key(call.method, NullableOut, call.stableCall || call.thisCall);
|
||||
Set<Key> keys = new SingletonSet<>(callKey);
|
||||
result = new Pending(new SingletonSet<>(new Product(Value.Null, keys)));
|
||||
EKey callKey = new EKey(call.method, NullableOut, call.stableCall || call.thisCall);
|
||||
Set<EKey> keys = new SingletonSet<>(callKey);
|
||||
result = new Pending(new SingletonSet<>(new Component(Value.Null, keys)));
|
||||
}
|
||||
else if (returnValue instanceof TrackableNullValue) {
|
||||
result = new Final(Value.Null);
|
||||
@@ -735,22 +735,22 @@ final class NegationAnalysis {
|
||||
}
|
||||
|
||||
final Equation contractEquation(int i, Value inValue, boolean stable) {
|
||||
final Key key = new Key(method, new InOut(i, inValue), stable);
|
||||
final EKey key = new EKey(method, new InOut(i, inValue), stable);
|
||||
final Result result;
|
||||
HashSet<Key> keys = new HashSet<>();
|
||||
HashSet<EKey> keys = new HashSet<>();
|
||||
for (int argI = 0; argI < conditionValue.args.size(); argI++) {
|
||||
BasicValue arg = conditionValue.args.get(argI);
|
||||
if (arg instanceof NthParamValue) {
|
||||
NthParamValue npv = (NthParamValue)arg;
|
||||
if (npv.n == i) {
|
||||
keys.add(new Key(conditionValue.method, new InOut(argI, inValue), conditionValue.stableCall, true));
|
||||
keys.add(new EKey(conditionValue.method, new InOut(argI, inValue), conditionValue.stableCall, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (keys.isEmpty()) {
|
||||
result = new Final(Value.Top);
|
||||
} else {
|
||||
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
|
||||
result = new Pending(new SingletonSet<>(new Component(Value.Top, keys)));
|
||||
}
|
||||
return new Equation(key, result);
|
||||
}
|
||||
|
||||
+19
-9
@@ -29,7 +29,7 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -65,6 +65,16 @@ abstract class ContractAnalysis extends Analysis<Result> {
|
||||
return new Equation(aKey, res);
|
||||
}
|
||||
|
||||
static Result checkLimit(Result result) throws AnalyzerException {
|
||||
if(result instanceof Pending) {
|
||||
int size = Arrays.stream(((Pending)result).delta).mapToInt(prod -> prod.ids.length).sum();
|
||||
if (size > Analysis.EQUATION_SIZE_LIMIT) {
|
||||
throw new AnalyzerException(null, "Equation size is too big");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected Equation analyze() throws AnalyzerException {
|
||||
pendingPush(createStartState());
|
||||
@@ -279,14 +289,14 @@ class InOutAnalysis extends ContractAnalysis {
|
||||
subResult = new Final(inValue);
|
||||
}
|
||||
else if (stackTop instanceof CallResultValue) {
|
||||
Set<Key> keys = ((CallResultValue) stackTop).inters;
|
||||
subResult = new Pending(Collections.singleton(new Product(Value.Top, keys)));
|
||||
Set<EKey> keys = ((CallResultValue) stackTop).inters;
|
||||
subResult = new Pending(new Component[] {new Component(Value.Top, keys)});
|
||||
}
|
||||
else {
|
||||
earlyResult = new Final(Value.Top);
|
||||
return true;
|
||||
}
|
||||
internalResult = resultUtil.join(internalResult, subResult);
|
||||
internalResult = checkLimit(resultUtil.join(internalResult, subResult));
|
||||
if (internalResult instanceof Final && ((Final)internalResult).value == Value.Top) {
|
||||
earlyResult = internalResult;
|
||||
}
|
||||
@@ -507,22 +517,22 @@ class InOutInterpreter extends BasicInterpreter {
|
||||
boolean isRefRetType = retType.getSort() == Type.OBJECT || retType.getSort() == Type.ARRAY;
|
||||
if (!Type.VOID_TYPE.equals(retType)) {
|
||||
if (direction != null) {
|
||||
HashSet<Key> keys = new HashSet<>();
|
||||
HashSet<EKey> keys = new HashSet<>();
|
||||
for (int i = shift; i < values.size(); i++) {
|
||||
if (values.get(i) instanceof ParamValue) {
|
||||
keys.add(new Key(method, direction.withIndex(i - shift), stable));
|
||||
keys.add(new EKey(method, direction.withIndex(i - shift), stable));
|
||||
}
|
||||
}
|
||||
if (isRefRetType) {
|
||||
keys.add(new Key(method, Out, stable));
|
||||
keys.add(new EKey(method, Out, stable));
|
||||
}
|
||||
if (!keys.isEmpty()) {
|
||||
return new CallResultValue(retType, keys);
|
||||
}
|
||||
}
|
||||
else if (isRefRetType) {
|
||||
HashSet<Key> keys = new HashSet<>();
|
||||
keys.add(new Key(method, Out, stable));
|
||||
HashSet<EKey> keys = new HashSet<>();
|
||||
keys.add(new EKey(method, Out, stable));
|
||||
return new CallResultValue(retType, keys);
|
||||
}
|
||||
}
|
||||
|
||||
+68
-114
@@ -15,23 +15,26 @@
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.util.ArrayFactory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a lattice product of a constant {@link #value} and all {@link #ids}.
|
||||
*/
|
||||
final class HComponent {
|
||||
static final HComponent[] EMPTY_ARRAY = new HComponent[0];
|
||||
static final ArrayFactory<HComponent> ARRAY_FACTORY = count -> count == 0 ? EMPTY_ARRAY : new HComponent[count];
|
||||
final class Component {
|
||||
static final Component[] EMPTY_ARRAY = new Component[0];
|
||||
@NotNull Value value;
|
||||
@NotNull final HKey[] ids;
|
||||
@NotNull final EKey[] ids;
|
||||
|
||||
HComponent(@NotNull Value value, @NotNull HKey[] ids) {
|
||||
Component(@NotNull Value value, @NotNull Set<EKey> ids) {
|
||||
this(value, ids.toArray(new EKey[0]));
|
||||
}
|
||||
|
||||
Component(@NotNull Value value, @NotNull EKey[] ids) {
|
||||
this.value = value;
|
||||
this.ids = ids;
|
||||
}
|
||||
@@ -41,45 +44,17 @@ final class HComponent {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
HComponent that = (HComponent)o;
|
||||
Component that = (Component)o;
|
||||
|
||||
if (!Arrays.equals(ids, that.ids)) return false;
|
||||
if (value != that.value) return false;
|
||||
|
||||
return true;
|
||||
return value == that.value && Arrays.equals(ids, that.ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = value.hashCode();
|
||||
result = 31 * result + Arrays.hashCode(ids);
|
||||
return result;
|
||||
return 31 * value.hashCode() + Arrays.hashCode(ids);
|
||||
}
|
||||
|
||||
public boolean remove(@NotNull HKey id) {
|
||||
return HUtils.remove(ids, id);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return HUtils.isEmpty(ids);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HComponent copy() {
|
||||
return new HComponent(value, ids.clone());
|
||||
}
|
||||
}
|
||||
|
||||
class HUtils {
|
||||
|
||||
static boolean isEmpty(HKey[] ids) {
|
||||
for (HKey id : ids) {
|
||||
if (id != null) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean remove(HKey[] ids, @NotNull HKey id) {
|
||||
public boolean remove(@NotNull EKey id) {
|
||||
boolean removed = false;
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
if (id.equals(ids[i])) {
|
||||
@@ -89,13 +64,25 @@ class HUtils {
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
for (EKey id : ids) {
|
||||
if (id != null) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Component copy() {
|
||||
return new Component(value, ids.clone());
|
||||
}
|
||||
}
|
||||
|
||||
final class HEquation {
|
||||
@NotNull final HKey key;
|
||||
@NotNull final HResult result;
|
||||
final class Equation {
|
||||
@NotNull final EKey key;
|
||||
@NotNull final Result result;
|
||||
|
||||
HEquation(@NotNull HKey key, @NotNull HResult result) {
|
||||
Equation(@NotNull EKey key, @NotNull Result result) {
|
||||
this.key = key;
|
||||
this.result = result;
|
||||
}
|
||||
@@ -104,48 +91,26 @@ final class HEquation {
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
HEquation hEquation = (HEquation)o;
|
||||
if (!key.equals(hEquation.key)) return false;
|
||||
if (!result.equals(hEquation.result)) return false;
|
||||
return true;
|
||||
Equation equation = (Equation)o;
|
||||
return key.equals(equation.key) && result.equals(equation.result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result1 = key.hashCode();
|
||||
result1 = 31 * result1 + result.hashCode();
|
||||
return result1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bytes of primary HKey of a method.
|
||||
*/
|
||||
final class Bytes {
|
||||
@NotNull
|
||||
final byte[] bytes;
|
||||
Bytes(@NotNull byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
return 31 * key.hashCode() + result.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
return Arrays.equals(bytes, ((Bytes)o).bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(bytes);
|
||||
public String toString() {
|
||||
return "Equation{" + "key=" + key + ", result=" + result + '}';
|
||||
}
|
||||
}
|
||||
|
||||
class HEquations {
|
||||
class Equations {
|
||||
@NotNull final List<DirectionResultPair> results;
|
||||
final boolean stable;
|
||||
|
||||
HEquations(@NotNull List<DirectionResultPair> results, boolean stable) {
|
||||
Equations(@NotNull List<DirectionResultPair> results, boolean stable) {
|
||||
this.results = results;
|
||||
this.stable = stable;
|
||||
}
|
||||
@@ -155,28 +120,22 @@ class HEquations {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
HEquations that = (HEquations)o;
|
||||
|
||||
if (stable != that.stable) return false;
|
||||
if (!results.equals(that.results)) return false;
|
||||
|
||||
return true;
|
||||
Equations that = (Equations)o;
|
||||
return stable == that.stable && results.equals(that.results);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = results.hashCode();
|
||||
result = 31 * result + (stable ? 1 : 0);
|
||||
return result;
|
||||
return 31 * results.hashCode() + (stable ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
class DirectionResultPair {
|
||||
final int directionKey;
|
||||
@NotNull
|
||||
final HResult hResult;
|
||||
final Result hResult;
|
||||
|
||||
DirectionResultPair(int directionKey, @NotNull HResult hResult) {
|
||||
DirectionResultPair(int directionKey, @NotNull Result hResult) {
|
||||
this.directionKey = directionKey;
|
||||
this.hResult = hResult;
|
||||
}
|
||||
@@ -187,26 +146,20 @@ class DirectionResultPair {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
DirectionResultPair that = (DirectionResultPair)o;
|
||||
|
||||
if (directionKey != that.directionKey) return false;
|
||||
if (!hResult.equals(that.hResult)) return false;
|
||||
|
||||
return true;
|
||||
return directionKey == that.directionKey && hResult.equals(that.hResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = directionKey;
|
||||
result = 31 * result + hResult.hashCode();
|
||||
return result;
|
||||
return 31 * directionKey + hResult.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
interface HResult {}
|
||||
final class HFinal implements HResult {
|
||||
interface Result {}
|
||||
final class Final implements Result {
|
||||
@NotNull final Value value;
|
||||
|
||||
HFinal(@NotNull Value value) {
|
||||
Final(@NotNull Value value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@@ -215,23 +168,28 @@ final class HFinal implements HResult {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
HFinal hFinal = (HFinal)o;
|
||||
|
||||
if (value != hFinal.value) return false;
|
||||
|
||||
return true;
|
||||
return value == ((Final)o).value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Final{" + "value=" + value + '}';
|
||||
}
|
||||
}
|
||||
|
||||
final class HPending implements HResult {
|
||||
@NotNull final HComponent[] delta; // sum
|
||||
final class Pending implements Result {
|
||||
@NotNull final Component[] delta; // sum
|
||||
|
||||
HPending(@NotNull HComponent[] delta) {
|
||||
Pending(Collection<Component> delta) {
|
||||
this(delta.toArray(Component.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
Pending(@NotNull Component[] delta) {
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
@@ -239,9 +197,7 @@ final class HPending implements HResult {
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
HPending hPending = (HPending)o;
|
||||
if (!Arrays.equals(delta, hPending.delta)) return false;
|
||||
return true;
|
||||
return Arrays.equals(delta, ((Pending)o).delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -250,20 +206,19 @@ final class HPending implements HResult {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
HPending copy() {
|
||||
HComponent[] delta1 = new HComponent[delta.length];
|
||||
Pending copy() {
|
||||
Component[] copy = new Component[delta.length];
|
||||
for (int i = 0; i < delta.length; i++) {
|
||||
delta1[i] = delta[i].copy();
|
||||
|
||||
copy[i] = delta[i].copy();
|
||||
}
|
||||
return new HPending(delta1);
|
||||
return new Pending(copy);
|
||||
}
|
||||
}
|
||||
|
||||
final class HEffects implements HResult {
|
||||
@NotNull final Set<HEffectQuantum> effects;
|
||||
final class Effects implements Result {
|
||||
@NotNull final Set<EffectQuantum> effects;
|
||||
|
||||
HEffects(@NotNull Set<HEffectQuantum> effects) {
|
||||
Effects(@NotNull Set<EffectQuantum> effects) {
|
||||
this.effects = effects;
|
||||
}
|
||||
|
||||
@@ -271,8 +226,7 @@ final class HEffects implements HResult {
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
HEffects hEffects = (HEffects)o;
|
||||
return effects.equals(hEffects.effects);
|
||||
return this.effects.equals(((Effects)o).effects);
|
||||
}
|
||||
|
||||
@Override
|
||||
+7
-13
@@ -49,7 +49,7 @@ public abstract class Direction {
|
||||
int subDirectionId = paramKey % DIRECTIONS_PER_PARAM_ID;
|
||||
// 0 - 1 - @NotNull, @Nullable, parameter
|
||||
if (subDirectionId < IN_OUT_OFFSET) {
|
||||
return new In(paramId, subDirectionId);
|
||||
return new In(paramId, subDirectionId == 1);
|
||||
}
|
||||
if (subDirectionId < IN_THROW_OFFSET) {
|
||||
int valueId = subDirectionId - IN_OUT_OFFSET;
|
||||
@@ -60,7 +60,7 @@ public abstract class Direction {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes Direction object as int.
|
||||
* Encodes Direction object as non-negative int.
|
||||
*
|
||||
* @return unique int for direction
|
||||
*/
|
||||
@@ -114,27 +114,21 @@ public abstract class Direction {
|
||||
}
|
||||
|
||||
static final class In extends ParamIdBasedDirection {
|
||||
static final int NOT_NULL_MASK = 0;
|
||||
static final int NULLABLE_MASK = 1;
|
||||
/**
|
||||
* @see #NOT_NULL_MASK
|
||||
* @see #NULLABLE_MASK
|
||||
*/
|
||||
final int nullityMask;
|
||||
final boolean nullable;
|
||||
|
||||
In(int paramIndex, int nullityMask) {
|
||||
In(int paramIndex, boolean nullable) {
|
||||
super(paramIndex);
|
||||
this.nullityMask = nullityMask;
|
||||
this.nullable = nullable;
|
||||
}
|
||||
|
||||
@Override
|
||||
int asInt() {
|
||||
return super.asInt() + nullityMask;
|
||||
return super.asInt() + (nullable ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "In " + paramIndex;
|
||||
return "In " + paramIndex + "(" + (nullable ? "nullable" : "not null") + ")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* Equation key (or variable)
|
||||
*/
|
||||
public final class EKey {
|
||||
@NotNull
|
||||
final MethodDescriptor method;
|
||||
final int dirKey;
|
||||
final boolean stable;
|
||||
final boolean negated;
|
||||
|
||||
public EKey(@NotNull MethodDescriptor method, Direction direction, boolean stable) {
|
||||
this(method, direction, stable, false);
|
||||
}
|
||||
|
||||
EKey(@NotNull MethodDescriptor method, Direction direction, boolean stable, boolean negated) {
|
||||
this(method, direction.asInt(), stable, negated);
|
||||
}
|
||||
|
||||
EKey(@NotNull MethodDescriptor method, int dirKey, boolean stable, boolean negated) {
|
||||
this.method = method;
|
||||
this.dirKey = dirKey;
|
||||
this.stable = stable;
|
||||
this.negated = negated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
EKey key = (EKey) o;
|
||||
|
||||
if (stable != key.stable) return false;
|
||||
if (negated != key.negated) return false;
|
||||
if (dirKey != key.dirKey) return false;
|
||||
if (!method.equals(key.method)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = method.hashCode();
|
||||
result = 31 * result + dirKey;
|
||||
result = 31 * result + (stable ? 1 : 0);
|
||||
result = 31 * result + (negated ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
EKey invertStability() {
|
||||
return new EKey(method, dirKey, !stable, negated);
|
||||
}
|
||||
|
||||
EKey mkStable() {
|
||||
return stable ? this : new EKey(method, dirKey, true, negated);
|
||||
}
|
||||
|
||||
EKey mkUnstable() {
|
||||
return stable ? new EKey(method, dirKey, false, negated) : this;
|
||||
}
|
||||
|
||||
public EKey mkBase() {
|
||||
return withDirection(Direction.Out);
|
||||
}
|
||||
|
||||
EKey withDirection(Direction dir) {
|
||||
return dirKey == dir.asInt() ? this : new EKey(method, dir, stable, false);
|
||||
}
|
||||
|
||||
EKey negate() {
|
||||
return new EKey(method, dirKey, stable, true);
|
||||
}
|
||||
|
||||
public EKey hashed(MessageDigest md) {
|
||||
HMethod hmethod = method.hashed(md);
|
||||
return hmethod == method ? this : new EKey(hmethod, dirKey, stable, negated);
|
||||
}
|
||||
|
||||
public Direction getDirection() {
|
||||
return Direction.fromInt(dirKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Key [" + method + "|" + (stable ? "S" : "-") + (negated ? "N" : "-") + "|" + Direction.fromInt(dirKey) + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,99 +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.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import one.util.streamex.IntStreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Small size key, constructed by hashing method signature.
|
||||
* 'H' in this and related class names stands for 'Hash'.
|
||||
* @see BytecodeAnalysisConverter for details of construction.
|
||||
*/
|
||||
public final class HKey {
|
||||
@NotNull
|
||||
final byte[] key;
|
||||
final int dirKey;
|
||||
final boolean stable;
|
||||
final boolean negated;
|
||||
|
||||
HKey(@NotNull byte[] key, int dirKey, boolean stable, boolean negated) {
|
||||
this.key = key;
|
||||
this.dirKey = dirKey;
|
||||
this.stable = stable;
|
||||
this.negated = negated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
HKey hKey = (HKey)o;
|
||||
if (dirKey != hKey.dirKey) return false;
|
||||
if (stable != hKey.stable) return false;
|
||||
if (negated != hKey.negated) return false;
|
||||
if (!Arrays.equals(key, hKey.key)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Arrays.hashCode(key);
|
||||
result = 31 * result + dirKey;
|
||||
result = 31 * result + (stable ? 1 : 0);
|
||||
result = 31 * result + (negated ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
HKey invertStability() {
|
||||
return new HKey(key, dirKey, !stable, negated);
|
||||
}
|
||||
|
||||
HKey mkStable() {
|
||||
return stable ? this : new HKey(key, dirKey, true, negated);
|
||||
}
|
||||
|
||||
HKey mkUnstable() {
|
||||
return stable ? new HKey(key, dirKey, false, negated) : this;
|
||||
}
|
||||
|
||||
public HKey mkBase() {
|
||||
return dirKey == 0 ? this : new HKey(key, 0, stable, false);
|
||||
}
|
||||
|
||||
HKey withDirection(Direction dir) {
|
||||
return new HKey(key, dir.asInt(), stable, false);
|
||||
}
|
||||
|
||||
HKey negate() {
|
||||
return new HKey(key, dirKey, stable, true);
|
||||
}
|
||||
|
||||
public Direction getDirection() {
|
||||
return Direction.fromInt(dirKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HKey [" + bytesToString(key) + "|" + (stable ? "S" : "-") + (negated ? "N" : "-") + "|" + getDirection() + "]";
|
||||
}
|
||||
|
||||
static String bytesToString(byte[] key) {
|
||||
return IntStreamEx.of(key).mapToObj(b -> String.format("%02x", b & 0xFF)).joining(".");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import one.util.streamex.IntStreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter.getMessageDigest;
|
||||
|
||||
/**
|
||||
* Hashed representation of method.
|
||||
*/
|
||||
public final class HMethod implements MethodDescriptor {
|
||||
// how many bytes are taken from class fqn digest
|
||||
private static final int CLASS_HASH_SIZE = 10;
|
||||
// how many bytes are taken from signature digest
|
||||
private static final int SIGNATURE_HASH_SIZE = 4;
|
||||
static final int HASH_SIZE = CLASS_HASH_SIZE + SIGNATURE_HASH_SIZE;
|
||||
|
||||
@NotNull
|
||||
final byte[] myBytes;
|
||||
|
||||
HMethod(Method method, MessageDigest md) {
|
||||
if (md == null) {
|
||||
md = getMessageDigest();
|
||||
}
|
||||
byte[] classDigest = md.digest(method.internalClassName.getBytes(CharsetToolkit.UTF8_CHARSET));
|
||||
md.update(method.methodName.getBytes(CharsetToolkit.UTF8_CHARSET));
|
||||
md.update(method.methodDesc.getBytes(CharsetToolkit.UTF8_CHARSET));
|
||||
byte[] sigDigest = md.digest();
|
||||
myBytes = new byte[HASH_SIZE];
|
||||
System.arraycopy(classDigest, 0, myBytes, 0, CLASS_HASH_SIZE);
|
||||
System.arraycopy(sigDigest, 0, myBytes, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE);
|
||||
}
|
||||
|
||||
public HMethod(@NotNull byte[] bytes) {
|
||||
myBytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
return Arrays.equals(myBytes, ((HMethod)o).myBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(myBytes);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HMethod hashed(MessageDigest md) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return bytesToString(myBytes);
|
||||
}
|
||||
|
||||
static String bytesToString(byte[] key) {
|
||||
return IntStreamEx.of(key).mapToObj(b -> String.format("%02x", b & 0xFF)).joining(".");
|
||||
}
|
||||
}
|
||||
@@ -1,63 +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.codeInspection.bytecodeAnalysis;
|
||||
|
||||
public final class Key {
|
||||
final Method method;
|
||||
final Direction direction;
|
||||
final boolean stable;
|
||||
final boolean negated;
|
||||
|
||||
public Key(Method method, Direction direction, boolean stable) {
|
||||
this.method = method;
|
||||
this.direction = direction;
|
||||
this.stable = stable;
|
||||
this.negated = false;
|
||||
}
|
||||
|
||||
Key(Method method, Direction direction, boolean stable, boolean negated) {
|
||||
this.method = method;
|
||||
this.direction = direction;
|
||||
this.stable = stable;
|
||||
this.negated = negated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Key key = (Key) o;
|
||||
|
||||
if (!direction.equals(key.direction)) return false;
|
||||
if (!method.equals(key.method)) return false;
|
||||
if (stable != key.stable) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = method.hashCode();
|
||||
result = 31 * result + direction.hashCode();
|
||||
result = 31 * result + (stable ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return method + " " + direction + " " + stable;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -49,9 +49,9 @@ abstract class KeyedMethodVisitor extends ClassVisitor {
|
||||
Method method = new Method(className, node.name, node.desc);
|
||||
boolean stable = stableClass || (node.access & STABLE_FLAGS) != 0 || "<init>".equals(node.name);
|
||||
|
||||
return visitMethod(node, new Key(method, Out, stable));
|
||||
return visitMethod(node, method, new EKey(method, Out, stable));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
abstract MethodVisitor visitMethod(final MethodNode node, final Key key);
|
||||
abstract MethodVisitor visitMethod(final MethodNode node, Method method, final EKey key);
|
||||
}
|
||||
|
||||
+11
-1
@@ -15,9 +15,13 @@
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode;
|
||||
|
||||
public final class Method {
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public final class Method implements MethodDescriptor {
|
||||
final String internalClassName;
|
||||
final String methodName;
|
||||
final String methodDesc;
|
||||
@@ -62,6 +66,12 @@ public final class Method {
|
||||
this.methodDesc = mNode.desc;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public HMethod hashed(@Nullable MessageDigest md) {
|
||||
return new HMethod(this, md);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return internalClassName + ' ' + methodName + ' ' + methodDesc;
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* An uniquely method (class name+method name+signature) identifier: either {@link Method} or {@link HMethod}.
|
||||
*/
|
||||
public interface MethodDescriptor {
|
||||
/**
|
||||
* Creates and returns the hashed representation of this method descriptor.
|
||||
* May return itself if already hashed. Note that hashed descriptor is not equal to
|
||||
* non-hashed one.
|
||||
*
|
||||
* @param md message digest to use for hashing (could be null to use the default one)
|
||||
* @return a corresponding HMethod.
|
||||
*/
|
||||
@NotNull HMethod hashed(@Nullable MessageDigest md);
|
||||
}
|
||||
+7
-7
@@ -149,13 +149,13 @@ class NullableMethodAnalysis {
|
||||
Calls calls = ((Calls)result);
|
||||
int mergedMappedLabels = calls.mergedLabels;
|
||||
if (mergedMappedLabels != 0) {
|
||||
Set<Product> sum = new HashSet<>();
|
||||
Key[] createdKeys = interpreter.keys;
|
||||
Set<Component> sum = new HashSet<>();
|
||||
EKey[] createdKeys = interpreter.keys;
|
||||
for (int origin = 0; origin < originsMapping.length; origin++) {
|
||||
int mappedOrigin = originsMapping[origin];
|
||||
Key createdKey = createdKeys[origin];
|
||||
EKey createdKey = createdKeys[origin];
|
||||
if (createdKey != null && (mergedMappedLabels & (1 << mappedOrigin)) != 0) {
|
||||
sum.add(new Product(Value.Null, Collections.singleton(createdKey)));
|
||||
sum.add(new Component(Value.Null, Collections.singleton(createdKey)));
|
||||
}
|
||||
}
|
||||
if (!sum.isEmpty()) {
|
||||
@@ -211,7 +211,7 @@ class NullableMethodInterpreter extends BasicInterpreter implements InterpreterE
|
||||
final InsnList insns;
|
||||
final boolean[] origins;
|
||||
private final int[] originsMapping;
|
||||
final Key[] keys;
|
||||
final EKey[] keys;
|
||||
|
||||
Constraint constraint;
|
||||
int delta;
|
||||
@@ -224,7 +224,7 @@ class NullableMethodInterpreter extends BasicInterpreter implements InterpreterE
|
||||
this.insns = insns;
|
||||
this.origins = origins;
|
||||
this.originsMapping = originsMapping;
|
||||
keys = new Key[originsMapping.length];
|
||||
keys = new EKey[originsMapping.length];
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -344,7 +344,7 @@ class NullableMethodInterpreter extends BasicInterpreter implements InterpreterE
|
||||
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
|
||||
int label = 1 << originsMapping[insnIndex];
|
||||
if (keys[insnIndex] == null) {
|
||||
keys[insnIndex] = new Key(method, Direction.NullableOut, stable);
|
||||
keys[insnIndex] = new EKey(method, Direction.NullableOut, stable);
|
||||
}
|
||||
return new Calls(label);
|
||||
}
|
||||
|
||||
+22
-22
@@ -38,18 +38,18 @@ import static org.jetbrains.org.objectweb.asm.Opcodes.*;
|
||||
|
||||
abstract class PResults {
|
||||
// SoP = sum of products
|
||||
static Set<Set<Key>> join(Set<Set<Key>> sop1, Set<Set<Key>> sop2) {
|
||||
Set<Set<Key>> sop = new HashSet<>();
|
||||
static Set<Set<EKey>> join(Set<Set<EKey>> sop1, Set<Set<EKey>> sop2) {
|
||||
Set<Set<EKey>> sop = new HashSet<>();
|
||||
sop.addAll(sop1);
|
||||
sop.addAll(sop2);
|
||||
return sop;
|
||||
}
|
||||
|
||||
static Set<Set<Key>> meet(Set<Set<Key>> sop1, Set<Set<Key>> sop2) {
|
||||
Set<Set<Key>> sop = new HashSet<>();
|
||||
for (Set<Key> prod1 : sop1) {
|
||||
for (Set<Key> prod2 : sop2) {
|
||||
Set<Key> prod = new HashSet<>();
|
||||
static Set<Set<EKey>> meet(Set<Set<EKey>> sop1, Set<Set<EKey>> sop2) {
|
||||
Set<Set<EKey>> sop = new HashSet<>();
|
||||
for (Set<EKey> prod1 : sop1) {
|
||||
for (Set<EKey> prod2 : sop2) {
|
||||
Set<EKey> prod = new HashSet<>();
|
||||
prod.addAll(prod1);
|
||||
prod.addAll(prod2);
|
||||
sop.add(prod);
|
||||
@@ -83,23 +83,23 @@ abstract class PResults {
|
||||
}
|
||||
};
|
||||
static final class ConditionalNPE implements PResult {
|
||||
final Set<Set<Key>> sop;
|
||||
public ConditionalNPE(Set<Set<Key>> sop) throws AnalyzerException {
|
||||
final Set<Set<EKey>> sop;
|
||||
public ConditionalNPE(Set<Set<EKey>> sop) throws AnalyzerException {
|
||||
this.sop = sop;
|
||||
checkLimit(sop);
|
||||
}
|
||||
|
||||
public ConditionalNPE(Key key) {
|
||||
public ConditionalNPE(EKey key) {
|
||||
sop = new HashSet<>();
|
||||
Set<Key> prod = new HashSet<>();
|
||||
Set<EKey> prod = new HashSet<>();
|
||||
prod.add(key);
|
||||
sop.add(prod);
|
||||
}
|
||||
|
||||
static void checkLimit(Set<Set<Key>> sop) throws AnalyzerException {
|
||||
static void checkLimit(Set<Set<EKey>> sop) throws AnalyzerException {
|
||||
int size = sop.stream().mapToInt(Set::size).sum();
|
||||
if (size > Analysis.EQUATION_SIZE_LIMIT) {
|
||||
throw new AnalyzerException(null, "Equation size is too big");
|
||||
throw new AnalyzerException(null, "HEquation size is too big");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ class NonNullInAnalysis extends Analysis<PResult> {
|
||||
}
|
||||
else {
|
||||
ConditionalNPE condNpe = (ConditionalNPE) result;
|
||||
Set<Product> components = condNpe.sop.stream().map(prod -> new Product(Value.Top, prod)).collect(Collectors.toSet());
|
||||
Set<Component> components = condNpe.sop.stream().map(prod -> new Component(Value.Top, prod)).collect(Collectors.toSet());
|
||||
return new Equation(aKey, new Pending(components));
|
||||
}
|
||||
}
|
||||
@@ -421,7 +421,7 @@ class NullableInAnalysis extends Analysis<PResult> {
|
||||
}
|
||||
else {
|
||||
ConditionalNPE condNpe = (ConditionalNPE) result;
|
||||
Set<Product> components = condNpe.sop.stream().map(prod -> new Product(Value.Top, prod)).collect(Collectors.toSet());
|
||||
Set<Component> components = condNpe.sop.stream().map(prod -> new Component(Value.Top, prod)).collect(Collectors.toSet());
|
||||
return new Equation(aKey, new Pending(components));
|
||||
}
|
||||
}
|
||||
@@ -593,13 +593,13 @@ class NullableInAnalysis extends Analysis<PResult> {
|
||||
abstract class NullityInterpreter extends BasicInterpreter {
|
||||
boolean top;
|
||||
final boolean nullableAnalysis;
|
||||
final int nullityMask;
|
||||
final boolean nullable;
|
||||
private PResult subResult = Identity;
|
||||
protected boolean taken;
|
||||
|
||||
NullityInterpreter(boolean nullableAnalysis, int nullityMask) {
|
||||
NullityInterpreter(boolean nullableAnalysis, boolean nullable) {
|
||||
this.nullableAnalysis = nullableAnalysis;
|
||||
this.nullityMask = nullityMask;
|
||||
this.nullable = nullable;
|
||||
}
|
||||
|
||||
abstract PResult combine(PResult res1, PResult res2) throws AnalyzerException;
|
||||
@@ -736,8 +736,8 @@ abstract class NullityInterpreter extends BasicInterpreter {
|
||||
boolean stable = opcode == INVOKESTATIC || opcode == INVOKESPECIAL;
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
BasicValue value = values.get(i);
|
||||
if (value instanceof ParamValue || (NullValue == value && nullityMask == In.NULLABLE_MASK && "<init>".equals(method.methodName))) {
|
||||
subResult = combine(subResult, new ConditionalNPE(new Key(method, new In(i, nullityMask), stable)));
|
||||
if (value instanceof ParamValue || (NullValue == value && nullable && "<init>".equals(method.methodName))) {
|
||||
subResult = combine(subResult, new ConditionalNPE(new EKey(method, new In(i, nullable), stable)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -747,7 +747,7 @@ abstract class NullityInterpreter extends BasicInterpreter {
|
||||
class NotNullInterpreter extends NullityInterpreter {
|
||||
|
||||
NotNullInterpreter() {
|
||||
super(false, In.NOT_NULL_MASK);
|
||||
super(false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -759,7 +759,7 @@ class NotNullInterpreter extends NullityInterpreter {
|
||||
class NullableInterpreter extends NullityInterpreter {
|
||||
|
||||
NullableInterpreter() {
|
||||
super(true, In.NULLABLE_MASK);
|
||||
super(true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+56
-58
@@ -57,7 +57,7 @@ public class ProjectBytecodeAnalysis {
|
||||
private final Project myProject;
|
||||
private final boolean nullableMethod;
|
||||
private final boolean nullableMethodTransitivity;
|
||||
private final Map<Bytes, List<HEquations>> myEquationCache = ContainerUtil.createConcurrentSoftValueMap();
|
||||
private final Map<HMethod, List<Equations>> myEquationCache = ContainerUtil.createConcurrentSoftValueMap();
|
||||
|
||||
public static ProjectBytecodeAnalysis getInstance(@NotNull Project project) {
|
||||
return ServiceManager.getService(project, ProjectBytecodeAnalysis.class);
|
||||
@@ -117,12 +117,12 @@ public class ProjectBytecodeAnalysis {
|
||||
|
||||
try {
|
||||
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
|
||||
HKey primaryKey = getKey(listOwner, md);
|
||||
EKey primaryKey = getKey(listOwner, md);
|
||||
if (primaryKey == null) {
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
if (listOwner instanceof PsiMethod) {
|
||||
ArrayList<HKey> allKeys = collectMethodKeys((PsiMethod)listOwner, primaryKey);
|
||||
ArrayList<EKey> allKeys = collectMethodKeys((PsiMethod)listOwner, primaryKey);
|
||||
MethodAnnotations methodAnnotations = loadMethodAnnotations((PsiMethod)listOwner, primaryKey, allKeys);
|
||||
return toPsi(primaryKey, methodAnnotations);
|
||||
} else if (listOwner instanceof PsiParameter) {
|
||||
@@ -148,7 +148,7 @@ public class ProjectBytecodeAnalysis {
|
||||
* @return Psi annotations
|
||||
*/
|
||||
@NotNull
|
||||
private PsiAnnotation[] toPsi(HKey primaryKey, MethodAnnotations methodAnnotations) {
|
||||
private PsiAnnotation[] toPsi(EKey primaryKey, MethodAnnotations methodAnnotations) {
|
||||
boolean notNull = methodAnnotations.notNulls.contains(primaryKey);
|
||||
boolean nullable = methodAnnotations.nullables.contains(primaryKey);
|
||||
boolean pure = methodAnnotations.pures.contains(primaryKey);
|
||||
@@ -233,7 +233,7 @@ public class ProjectBytecodeAnalysis {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static HKey getKey(@NotNull PsiModifierListOwner owner, MessageDigest md) {
|
||||
public static EKey getKey(@NotNull PsiModifierListOwner owner, MessageDigest md) {
|
||||
LOG.assertTrue(owner instanceof PsiCompiledElement, owner);
|
||||
if (owner instanceof PsiMethod) {
|
||||
return BytecodeAnalysisConverter.psiKey((PsiMethod)owner, Out, md);
|
||||
@@ -244,7 +244,7 @@ public class ProjectBytecodeAnalysis {
|
||||
PsiElement gParent = parent.getParent();
|
||||
if (gParent instanceof PsiMethod) {
|
||||
final int index = ((PsiParameterList)parent).getParameterIndex((PsiParameter)owner);
|
||||
return BytecodeAnalysisConverter.psiKey((PsiMethod)gParent, new In(index, In.NOT_NULL_MASK), md);
|
||||
return BytecodeAnalysisConverter.psiKey((PsiMethod)gParent, new In(index, false), md);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,44 +258,44 @@ public class ProjectBytecodeAnalysis {
|
||||
* @param primaryKey primary compressed key for this method
|
||||
* @return compressed keys for this method
|
||||
*/
|
||||
public static ArrayList<HKey> collectMethodKeys(@NotNull PsiMethod method, HKey primaryKey) {
|
||||
public static ArrayList<EKey> collectMethodKeys(@NotNull PsiMethod method, EKey primaryKey) {
|
||||
return BytecodeAnalysisConverter.mkInOutKeys(method, primaryKey);
|
||||
}
|
||||
|
||||
private ParameterAnnotations loadParameterAnnotations(@NotNull HKey notNullKey)
|
||||
private ParameterAnnotations loadParameterAnnotations(@NotNull EKey notNullKey)
|
||||
throws EquationsLimitException {
|
||||
|
||||
final Solver notNullSolver = new Solver(new ELattice<>(Value.NotNull, Value.Top), Value.Top);
|
||||
collectEquations(Collections.singletonList(notNullKey), notNullSolver);
|
||||
|
||||
Map<HKey, Value> notNullSolutions = notNullSolver.solve();
|
||||
Map<EKey, Value> notNullSolutions = notNullSolver.solve();
|
||||
// subtle point
|
||||
boolean notNull =
|
||||
(Value.NotNull == notNullSolutions.get(notNullKey)) || (Value.NotNull == notNullSolutions.get(notNullKey.mkUnstable()));
|
||||
|
||||
final Solver nullableSolver = new Solver(new ELattice<>(Value.Null, Value.Top), Value.Top);
|
||||
final HKey nullableKey = new HKey(notNullKey.key, notNullKey.dirKey + 1, true, false);
|
||||
final EKey nullableKey = new EKey(notNullKey.method, notNullKey.dirKey + 1, true, false);
|
||||
collectEquations(Collections.singletonList(nullableKey), nullableSolver);
|
||||
Map<HKey, Value> nullableSolutions = nullableSolver.solve();
|
||||
Map<EKey, Value> nullableSolutions = nullableSolver.solve();
|
||||
// subtle point
|
||||
boolean nullable =
|
||||
(Value.Null == nullableSolutions.get(nullableKey)) || (Value.Null == nullableSolutions.get(nullableKey.mkUnstable()));
|
||||
return new ParameterAnnotations(notNull, nullable);
|
||||
}
|
||||
|
||||
private MethodAnnotations loadMethodAnnotations(@NotNull PsiMethod owner, @NotNull HKey key, ArrayList<HKey> allKeys)
|
||||
private MethodAnnotations loadMethodAnnotations(@NotNull PsiMethod owner, @NotNull EKey key, ArrayList<EKey> allKeys)
|
||||
throws EquationsLimitException {
|
||||
MethodAnnotations result = new MethodAnnotations();
|
||||
|
||||
final PuritySolver puritySolver = new PuritySolver();
|
||||
collectPurityEquations(key.withDirection(Pure), puritySolver);
|
||||
|
||||
Map<HKey, Set<HEffectQuantum>> puritySolutions = puritySolver.solve();
|
||||
Map<EKey, Set<EffectQuantum>> puritySolutions = puritySolver.solve();
|
||||
|
||||
int arity = owner.getParameterList().getParameters().length;
|
||||
BytecodeAnalysisConverter.addEffectAnnotations(puritySolutions, result, key, owner.isConstructor());
|
||||
|
||||
HKey failureKey = key.withDirection(Throw);
|
||||
EKey failureKey = key.withDirection(Throw);
|
||||
final Solver failureSolver = new Solver(new ELattice<>(Value.Fail, Value.Top), Value.Top);
|
||||
collectEquations(Collections.singletonList(failureKey), failureSolver);
|
||||
if (failureSolver.solve().get(failureKey) == Value.Fail) {
|
||||
@@ -304,20 +304,20 @@ public class ProjectBytecodeAnalysis {
|
||||
} else {
|
||||
final Solver outSolver = new Solver(new ELattice<>(Value.Bot, Value.Top), Value.Top);
|
||||
collectEquations(allKeys, outSolver);
|
||||
Map<HKey, Value> solutions = outSolver.solve();
|
||||
Map<EKey, Value> solutions = outSolver.solve();
|
||||
BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity);
|
||||
}
|
||||
|
||||
if (nullableMethod) {
|
||||
final Solver nullableMethodSolver = new Solver(new ELattice<>(Value.Bot, Value.Null), Value.Bot);
|
||||
HKey nullableKey = key.withDirection(NullableOut);
|
||||
EKey nullableKey = key.withDirection(NullableOut);
|
||||
if (nullableMethodTransitivity) {
|
||||
collectEquations(Collections.singletonList(nullableKey), nullableMethodSolver);
|
||||
}
|
||||
else {
|
||||
collectSingleEquation(nullableKey, nullableMethodSolver);
|
||||
}
|
||||
Map<HKey, Value> nullableSolutions = nullableMethodSolver.solve();
|
||||
Map<EKey, Value> nullableSolutions = nullableMethodSolver.solve();
|
||||
if (nullableSolutions.get(nullableKey) == Value.Null || nullableSolutions.get(nullableKey.invertStability()) == Value.Null) {
|
||||
result.nullables.add(key);
|
||||
}
|
||||
@@ -325,18 +325,19 @@ public class ProjectBytecodeAnalysis {
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<HEquations> getEquations(Bytes key) {
|
||||
List<HEquations> result = myEquationCache.get(key);
|
||||
private List<Equations> getEquations(MethodDescriptor methodDescriptor) {
|
||||
HMethod key = methodDescriptor.hashed(null);
|
||||
List<Equations> result = myEquationCache.get(key);
|
||||
if (result == null) {
|
||||
myEquationCache.put(key, result = BytecodeAnalysisIndex.getEquations(ProjectScope.getLibrariesScope(myProject), key));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void collectPurityEquations(HKey key, PuritySolver puritySolver)
|
||||
private void collectPurityEquations(EKey key, PuritySolver puritySolver)
|
||||
throws EquationsLimitException {
|
||||
HashSet<HKey> queued = new HashSet<>();
|
||||
Stack<HKey> queue = new Stack<>();
|
||||
HashSet<EKey> queued = new HashSet<>();
|
||||
Stack<EKey> queue = new Stack<>();
|
||||
|
||||
queue.push(key);
|
||||
queued.add(key);
|
||||
@@ -346,19 +347,18 @@ public class ProjectBytecodeAnalysis {
|
||||
throw new EquationsLimitException();
|
||||
}
|
||||
ProgressManager.checkCanceled();
|
||||
HKey hKey = queue.pop();
|
||||
Bytes bytes = new Bytes(hKey.key);
|
||||
EKey hKey = queue.pop();
|
||||
|
||||
for (HEquations hEquations : getEquations(bytes)) {
|
||||
boolean stable = hEquations.stable;
|
||||
for (DirectionResultPair pair : hEquations.results) {
|
||||
for (Equations equations : getEquations(hKey.method)) {
|
||||
boolean stable = equations.stable;
|
||||
for (DirectionResultPair pair : equations.results) {
|
||||
int dirKey = pair.directionKey;
|
||||
if (dirKey == hKey.dirKey) {
|
||||
Set<HEffectQuantum> effects = ((HEffects)pair.hResult).effects;
|
||||
puritySolver.addEquation(new HKey(bytes.bytes, dirKey, stable, false), effects);
|
||||
for (HEffectQuantum effect : effects) {
|
||||
if (effect instanceof HEffectQuantum.CallQuantum) {
|
||||
HKey depKey = ((HEffectQuantum.CallQuantum)effect).key;
|
||||
Set<EffectQuantum> effects = ((Effects)pair.hResult).effects;
|
||||
puritySolver.addEquation(new EKey(hKey.method, dirKey, stable, false), effects);
|
||||
for (EffectQuantum effect : effects) {
|
||||
if (effect instanceof EffectQuantum.CallQuantum) {
|
||||
EKey depKey = ((EffectQuantum.CallQuantum)effect).key;
|
||||
if (!queued.contains(depKey)) {
|
||||
queue.push(depKey);
|
||||
queued.add(depKey);
|
||||
@@ -371,11 +371,11 @@ public class ProjectBytecodeAnalysis {
|
||||
}
|
||||
}
|
||||
|
||||
private void collectEquations(List<HKey> keys, Solver solver) throws EquationsLimitException {
|
||||
HashSet<HKey> queued = new HashSet<>();
|
||||
Stack<HKey> queue = new Stack<>();
|
||||
private void collectEquations(List<EKey> keys, Solver solver) throws EquationsLimitException {
|
||||
HashSet<EKey> queued = new HashSet<>();
|
||||
Stack<EKey> queue = new Stack<>();
|
||||
|
||||
for (HKey key : keys) {
|
||||
for (EKey key : keys) {
|
||||
queue.push(key);
|
||||
queued.add(key);
|
||||
}
|
||||
@@ -385,21 +385,20 @@ public class ProjectBytecodeAnalysis {
|
||||
throw new EquationsLimitException();
|
||||
}
|
||||
ProgressManager.checkCanceled();
|
||||
HKey hKey = queue.pop();
|
||||
Bytes bytes = new Bytes(hKey.key);
|
||||
EKey hKey = queue.pop();
|
||||
|
||||
for (HEquations hEquations : getEquations(bytes)) {
|
||||
boolean stable = hEquations.stable;
|
||||
for (DirectionResultPair pair : hEquations.results) {
|
||||
for (Equations equations : getEquations(hKey.method)) {
|
||||
boolean stable = equations.stable;
|
||||
for (DirectionResultPair pair : equations.results) {
|
||||
int dirKey = pair.directionKey;
|
||||
if (dirKey == hKey.dirKey) {
|
||||
HResult result = pair.hResult;
|
||||
Result result = pair.hResult;
|
||||
|
||||
solver.addEquation(new HEquation(new HKey(bytes.bytes, dirKey, stable, false), result));
|
||||
if (result instanceof HPending) {
|
||||
HPending pending = (HPending)result;
|
||||
for (HComponent component : pending.delta) {
|
||||
for (HKey depKey : component.ids) {
|
||||
solver.addEquation(new Equation(new EKey(hKey.method, dirKey, stable, false), result));
|
||||
if (result instanceof Pending) {
|
||||
Pending pending = (Pending)result;
|
||||
for (Component component : pending.delta) {
|
||||
for (EKey depKey : component.ids) {
|
||||
if (!queued.contains(depKey)) {
|
||||
queue.push(depKey);
|
||||
queued.add(depKey);
|
||||
@@ -413,17 +412,16 @@ public class ProjectBytecodeAnalysis {
|
||||
}
|
||||
}
|
||||
|
||||
private void collectSingleEquation(HKey hKey, Solver solver) throws EquationsLimitException {
|
||||
private void collectSingleEquation(EKey hKey, Solver solver) throws EquationsLimitException {
|
||||
ProgressManager.checkCanceled();
|
||||
Bytes bytes = new Bytes(hKey.key);
|
||||
|
||||
for (HEquations hEquations : getEquations(bytes)) {
|
||||
boolean stable = hEquations.stable;
|
||||
for (DirectionResultPair pair : hEquations.results) {
|
||||
for (Equations equations : getEquations(hKey.method)) {
|
||||
boolean stable = equations.stable;
|
||||
for (DirectionResultPair pair : equations.results) {
|
||||
int dirKey = pair.directionKey;
|
||||
if (dirKey == hKey.dirKey) {
|
||||
HResult result = pair.hResult;
|
||||
solver.addEquation(new HEquation(new HKey(bytes.bytes, dirKey, stable, false), result));
|
||||
Result result = pair.hResult;
|
||||
solver.addEquation(new Equation(new EKey(hKey.method, dirKey, stable, false), result));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,13 +438,13 @@ public class ProjectBytecodeAnalysis {
|
||||
|
||||
class MethodAnnotations {
|
||||
// @NotNull keys
|
||||
final Set<HKey> notNulls = new HashSet<>(1);
|
||||
final Set<EKey> notNulls = new HashSet<>(1);
|
||||
// @Nullable keys
|
||||
final Set<HKey> nullables = new HashSet<>(1);
|
||||
final Set<EKey> nullables = new HashSet<>(1);
|
||||
// @Contract(pure=true) part of contract
|
||||
final Set<HKey> pures = new HashSet<>(1);
|
||||
final Set<EKey> pures = new HashSet<>(1);
|
||||
// @Contracts
|
||||
final Map<HKey, String> contractsValues = new HashMap<>();
|
||||
final Map<EKey, String> contractsValues = new HashMap<>();
|
||||
}
|
||||
|
||||
class ParameterAnnotations {
|
||||
|
||||
+60
-131
@@ -36,14 +36,14 @@ import java.util.*;
|
||||
*/
|
||||
public class PurityAnalysis {
|
||||
static final Set<EffectQuantum> topEffect = Collections.singleton(EffectQuantum.TopEffectQuantum);
|
||||
static final Set<HEffectQuantum> topHEffect = Collections.singleton(HEffectQuantum.TopEffectQuantum);
|
||||
static final Set<EffectQuantum> topHEffect = Collections.singleton(EffectQuantum.TopEffectQuantum);
|
||||
|
||||
static final int UN_ANALYZABLE_FLAG = Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE | Opcodes.ACC_INTERFACE;
|
||||
|
||||
@NotNull
|
||||
public static Equation analyze(Method method, MethodNode methodNode, boolean stable) {
|
||||
Key key = new Key(method, Direction.Pure, stable);
|
||||
Set<EffectQuantum> hardCodedSolution = HardCodedPurity.getHardCodedSolution(key);
|
||||
EKey key = new EKey(method, Direction.Pure, stable);
|
||||
Set<EffectQuantum> hardCodedSolution = HardCodedPurity.getHardCodedSolution(method);
|
||||
if (hardCodedSolution != null) {
|
||||
return new Equation(key, new Effects(hardCodedSolution));
|
||||
}
|
||||
@@ -170,81 +170,10 @@ abstract class DataValue implements org.jetbrains.org.objectweb.asm.tree.analysi
|
||||
};
|
||||
}
|
||||
|
||||
interface EffectQuantum {
|
||||
EffectQuantum TopEffectQuantum = new EffectQuantum() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Top";
|
||||
}
|
||||
};
|
||||
EffectQuantum ThisChangeQuantum = new EffectQuantum() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Changes this";
|
||||
}
|
||||
};
|
||||
|
||||
final class ParamChangeQuantum implements EffectQuantum {
|
||||
final int n;
|
||||
public ParamChangeQuantum(int n) {
|
||||
this.n = n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ParamChangeQuantum)) return false;
|
||||
|
||||
return n == ((ParamChangeQuantum)o).n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Changes param#" + n;
|
||||
}
|
||||
}
|
||||
|
||||
final class CallQuantum implements EffectQuantum {
|
||||
final @NotNull Key key;
|
||||
final @NotNull DataValue[] data;
|
||||
final boolean isStatic;
|
||||
|
||||
public CallQuantum(@NotNull Key key, @NotNull DataValue[] data, boolean isStatic) {
|
||||
this.key = key;
|
||||
this.data = data;
|
||||
this.isStatic = isStatic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof CallQuantum)) return false;
|
||||
|
||||
CallQuantum quantum = (CallQuantum)o;
|
||||
return isStatic == quantum.isStatic && key.equals(quantum.key) && Arrays.equals(data, quantum.data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * (31 * key.hashCode() + Arrays.hashCode(data)) + (isStatic ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Calls " + key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class HEffectQuantum {
|
||||
abstract class EffectQuantum {
|
||||
private final int myHash;
|
||||
|
||||
HEffectQuantum(int hash) {
|
||||
EffectQuantum(int hash) {
|
||||
myHash = hash;
|
||||
}
|
||||
|
||||
@@ -253,19 +182,19 @@ abstract class HEffectQuantum {
|
||||
return myHash;
|
||||
}
|
||||
|
||||
static final HEffectQuantum TopEffectQuantum = new HEffectQuantum(-1) {
|
||||
static final EffectQuantum TopEffectQuantum = new EffectQuantum(-1) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Top";
|
||||
}
|
||||
};
|
||||
static final HEffectQuantum ThisChangeQuantum = new HEffectQuantum(-2) {
|
||||
static final EffectQuantum ThisChangeQuantum = new EffectQuantum(-2) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Changes this";
|
||||
}
|
||||
};
|
||||
static class ParamChangeQuantum extends HEffectQuantum {
|
||||
static class ParamChangeQuantum extends EffectQuantum {
|
||||
final int n;
|
||||
public ParamChangeQuantum(int n) {
|
||||
super(n);
|
||||
@@ -289,11 +218,11 @@ abstract class HEffectQuantum {
|
||||
return "Changes param#" + n;
|
||||
}
|
||||
}
|
||||
static class CallQuantum extends HEffectQuantum {
|
||||
final HKey key;
|
||||
static class CallQuantum extends EffectQuantum {
|
||||
final EKey key;
|
||||
final DataValue[] data;
|
||||
final boolean isStatic;
|
||||
public CallQuantum(HKey key, DataValue[] data, boolean isStatic) {
|
||||
public CallQuantum(EKey key, DataValue[] data, boolean isStatic) {
|
||||
super((key.hashCode() * 31 + Arrays.hashCode(data)) * 31 + (isStatic ? 1 : 0));
|
||||
this.key = key;
|
||||
this.data = data;
|
||||
@@ -453,14 +382,15 @@ class DataInterpreter extends Interpreter<DataValue> {
|
||||
boolean stable = opCode == Opcodes.INVOKESPECIAL || opCode == Opcodes.INVOKESTATIC;
|
||||
MethodInsnNode mNode = ((MethodInsnNode)insn);
|
||||
DataValue[] data = values.toArray(new DataValue[0]);
|
||||
Key key = new Key(new Method(mNode.owner, mNode.name, mNode.desc), Direction.Pure, stable);
|
||||
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
|
||||
EKey key = new EKey(method, Direction.Pure, stable);
|
||||
EffectQuantum quantum = new EffectQuantum.CallQuantum(key, data, opCode == Opcodes.INVOKESTATIC);
|
||||
DataValue result = (ASMUtils.getReturnSizeFast(mNode.desc) == 1) ? DataValue.UnknownDataValue1 : DataValue.UnknownDataValue2;
|
||||
if (HardCodedPurity.isPureMethod(key)) {
|
||||
if (HardCodedPurity.isPureMethod(method)) {
|
||||
quantum = null;
|
||||
result = DataValue.LocalDataValue;
|
||||
}
|
||||
else if (HardCodedPurity.isThisChangingMethod(key)) {
|
||||
else if (HardCodedPurity.isThisChangingMethod(method)) {
|
||||
DataValue receiver = ArrayUtil.getFirstElement(data);
|
||||
if (receiver == DataValue.ThisDataValue) {
|
||||
quantum = EffectQuantum.ThisChangeQuantum;
|
||||
@@ -468,7 +398,7 @@ class DataInterpreter extends Interpreter<DataValue> {
|
||||
else if (receiver == DataValue.LocalDataValue || receiver == DataValue.OwnedDataValue) {
|
||||
quantum = null;
|
||||
}
|
||||
if (HardCodedPurity.isBuilderChainCall(key)) {
|
||||
if (HardCodedPurity.isBuilderChainCall(method)) {
|
||||
// mostly to support string concatenation
|
||||
result = receiver;
|
||||
}
|
||||
@@ -575,16 +505,15 @@ final class HardCodedPurity {
|
||||
solutions.put(new Method("java/lang/Object", "hashCode", "()I"), Collections.emptySet());
|
||||
}
|
||||
|
||||
static Set<EffectQuantum> getHardCodedSolution(Key key) {
|
||||
return isThisChangingMethod(key) ? thisChange : isPureMethod(key) ? Collections.emptySet() : solutions.get(key.method);
|
||||
static Set<EffectQuantum> getHardCodedSolution(Method method) {
|
||||
return isThisChangingMethod(method) ? thisChange : isPureMethod(method) ? Collections.emptySet() : solutions.get(method);
|
||||
}
|
||||
|
||||
static boolean isThisChangingMethod(Key key) {
|
||||
return isBuilderChainCall(key) || thisChangingMethods.contains(key.method);
|
||||
static boolean isThisChangingMethod(Method method) {
|
||||
return isBuilderChainCall(method) || thisChangingMethods.contains(method);
|
||||
}
|
||||
|
||||
static boolean isBuilderChainCall(Key key) {
|
||||
Method method = key.method;
|
||||
static boolean isBuilderChainCall(Method method) {
|
||||
// Those methods are virtual, thus contracts cannot be inferred automatically,
|
||||
// but all possible implementations are controlled
|
||||
// (only final classes j.l.StringBuilder and j.l.StringBuffer extend package-private j.l.AbstractStringBuilder)
|
||||
@@ -592,9 +521,9 @@ final class HardCodedPurity {
|
||||
method.methodName.startsWith("append");
|
||||
}
|
||||
|
||||
static boolean isPureMethod(Key key) {
|
||||
return key.method.methodName.equals("toString") && key.method.methodDesc.equals("()Ljava/lang/String;") ||
|
||||
pureMethods.contains(key.method);
|
||||
static boolean isPureMethod(Method method) {
|
||||
return method.methodName.equals("toString") && method.methodDesc.equals("()Ljava/lang/String;") ||
|
||||
pureMethods.contains(method);
|
||||
}
|
||||
|
||||
static boolean isOwnedField(FieldInsnNode fieldInsn) {
|
||||
@@ -603,16 +532,16 @@ final class HardCodedPurity {
|
||||
}
|
||||
|
||||
final class PuritySolver {
|
||||
private HashMap<HKey, Set<HEffectQuantum>> solved = new HashMap<>();
|
||||
private HashMap<HKey, Set<HKey>> dependencies = new HashMap<>();
|
||||
private final Stack<HKey> moving = new Stack<>();
|
||||
private HashMap<HKey, Set<HEffectQuantum>> pending = new HashMap<>();
|
||||
private HashMap<EKey, Set<EffectQuantum>> solved = new HashMap<>();
|
||||
private HashMap<EKey, Set<EKey>> dependencies = new HashMap<>();
|
||||
private final Stack<EKey> moving = new Stack<>();
|
||||
private HashMap<EKey, Set<EffectQuantum>> pending = new HashMap<>();
|
||||
|
||||
void addEquation(HKey key, Set<HEffectQuantum> effects) {
|
||||
Set<HKey> callKeys = new HashSet<>();
|
||||
for (HEffectQuantum effect : effects) {
|
||||
if (effect instanceof HEffectQuantum.CallQuantum) {
|
||||
callKeys.add(((HEffectQuantum.CallQuantum)effect).key);
|
||||
void addEquation(EKey key, Set<EffectQuantum> effects) {
|
||||
Set<EKey> callKeys = new HashSet<>();
|
||||
for (EffectQuantum effect : effects) {
|
||||
if (effect instanceof EffectQuantum.CallQuantum) {
|
||||
callKeys.add(((EffectQuantum.CallQuantum)effect).key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,8 +550,8 @@ final class PuritySolver {
|
||||
moving.add(key);
|
||||
} else {
|
||||
pending.put(key, effects);
|
||||
for (HKey callKey : callKeys) {
|
||||
Set<HKey> deps = dependencies.get(callKey);
|
||||
for (EKey callKey : callKeys) {
|
||||
Set<EKey> deps = dependencies.get(callKey);
|
||||
if (deps == null) {
|
||||
deps = new HashSet<>();
|
||||
dependencies.put(callKey, deps);
|
||||
@@ -632,41 +561,41 @@ final class PuritySolver {
|
||||
}
|
||||
}
|
||||
|
||||
public Map<HKey, Set<HEffectQuantum>> solve() {
|
||||
public Map<EKey, Set<EffectQuantum>> solve() {
|
||||
while (!moving.isEmpty()) {
|
||||
HKey key = moving.pop();
|
||||
Set<HEffectQuantum> effects = solved.get(key);
|
||||
EKey key = moving.pop();
|
||||
Set<EffectQuantum> effects = solved.get(key);
|
||||
|
||||
HKey[] propagateKeys;
|
||||
EKey[] propagateKeys;
|
||||
Set[] propagateEffects;
|
||||
|
||||
if (key.stable) {
|
||||
propagateKeys = new HKey[]{key, key.mkUnstable()};
|
||||
propagateKeys = new EKey[]{key, key.mkUnstable()};
|
||||
propagateEffects = new Set[]{effects, effects};
|
||||
}
|
||||
else {
|
||||
propagateKeys = new HKey[]{key.mkStable(), key};
|
||||
propagateKeys = new EKey[]{key.mkStable(), key};
|
||||
propagateEffects = new Set[]{effects, PurityAnalysis.topHEffect};
|
||||
}
|
||||
for (int i = 0; i < propagateKeys.length; i++) {
|
||||
HKey pKey = propagateKeys[i];
|
||||
EKey pKey = propagateKeys[i];
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<HEffectQuantum> pEffects = propagateEffects[i];
|
||||
Set<HKey> dKeys = dependencies.remove(pKey);
|
||||
Set<EffectQuantum> pEffects = propagateEffects[i];
|
||||
Set<EKey> dKeys = dependencies.remove(pKey);
|
||||
if (dKeys != null) {
|
||||
for (HKey dKey : dKeys) {
|
||||
Set<HEffectQuantum> dEffects = pending.remove(dKey);
|
||||
for (EKey dKey : dKeys) {
|
||||
Set<EffectQuantum> dEffects = pending.remove(dKey);
|
||||
if (dEffects == null) {
|
||||
// already solved, for example, solution is top
|
||||
continue;
|
||||
}
|
||||
Set<HKey> callKeys = new HashSet<>();
|
||||
Set<HEffectQuantum> newEffects = new HashSet<>();
|
||||
Set<HEffectQuantum> delta = null;
|
||||
Set<EKey> callKeys = new HashSet<>();
|
||||
Set<EffectQuantum> newEffects = new HashSet<>();
|
||||
Set<EffectQuantum> delta = null;
|
||||
|
||||
for (HEffectQuantum dEffect : dEffects) {
|
||||
if (dEffect instanceof HEffectQuantum.CallQuantum) {
|
||||
HEffectQuantum.CallQuantum call = ((HEffectQuantum.CallQuantum)dEffect);
|
||||
for (EffectQuantum dEffect : dEffects) {
|
||||
if (dEffect instanceof EffectQuantum.CallQuantum) {
|
||||
EffectQuantum.CallQuantum call = ((EffectQuantum.CallQuantum)dEffect);
|
||||
if (call.key.equals(pKey)) {
|
||||
delta = substitute(pEffects, call.data, call.isStatic);
|
||||
newEffects.addAll(delta);
|
||||
@@ -702,29 +631,29 @@ final class PuritySolver {
|
||||
return solved;
|
||||
}
|
||||
|
||||
private static Set<HEffectQuantum> substitute(Set<HEffectQuantum> effects, DataValue[] data, boolean isStatic) {
|
||||
private static Set<EffectQuantum> substitute(Set<EffectQuantum> effects, DataValue[] data, boolean isStatic) {
|
||||
if (effects.isEmpty() || PurityAnalysis.topHEffect.equals(effects)) {
|
||||
return effects;
|
||||
}
|
||||
Set<HEffectQuantum> newEffects = new HashSet<>(effects.size());
|
||||
Set<EffectQuantum> newEffects = new HashSet<>(effects.size());
|
||||
int shift = isStatic ? 0 : 1;
|
||||
for (HEffectQuantum effect : effects) {
|
||||
for (EffectQuantum effect : effects) {
|
||||
DataValue arg = null;
|
||||
if (effect == HEffectQuantum.ThisChangeQuantum) {
|
||||
if (effect == EffectQuantum.ThisChangeQuantum) {
|
||||
arg = data[0];
|
||||
} else if (effect instanceof HEffectQuantum.ParamChangeQuantum) {
|
||||
HEffectQuantum.ParamChangeQuantum paramChange = ((HEffectQuantum.ParamChangeQuantum)effect);
|
||||
} else if (effect instanceof EffectQuantum.ParamChangeQuantum) {
|
||||
EffectQuantum.ParamChangeQuantum paramChange = ((EffectQuantum.ParamChangeQuantum)effect);
|
||||
arg = data[paramChange.n + shift];
|
||||
}
|
||||
if (arg == null || arg == DataValue.LocalDataValue) {
|
||||
continue;
|
||||
}
|
||||
if (arg == DataValue.ThisDataValue || arg == DataValue.OwnedDataValue) {
|
||||
newEffects.add(HEffectQuantum.ThisChangeQuantum);
|
||||
newEffects.add(EffectQuantum.ThisChangeQuantum);
|
||||
continue;
|
||||
}
|
||||
if (arg instanceof DataValue.ParameterDataValue) {
|
||||
newEffects.add(new HEffectQuantum.ParamChangeQuantum(((DataValue.ParameterDataValue)arg).n));
|
||||
newEffects.add(new EffectQuantum.ParamChangeQuantum(((DataValue.ParameterDataValue)arg).n));
|
||||
continue;
|
||||
}
|
||||
return PurityAnalysis.topHEffect;
|
||||
|
||||
+68
-196
@@ -17,7 +17,6 @@ package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -50,6 +49,7 @@ final class ELattice<T extends Enum<T>> {
|
||||
|
||||
|
||||
class ResultUtil {
|
||||
private static final EKey[] EMPTY_PRODUCT = new EKey[0];
|
||||
private final ELattice<Value> lattice;
|
||||
final Value top;
|
||||
ResultUtil(ELattice<Value> lattice) {
|
||||
@@ -57,7 +57,7 @@ class ResultUtil {
|
||||
top = lattice.top;
|
||||
}
|
||||
|
||||
Result join(Result r1, Result r2) throws AnalyzerException {
|
||||
Result join(Result r1, Result r2) {
|
||||
if (r1 instanceof Final && ((Final) r1).value == top) {
|
||||
return r1;
|
||||
}
|
||||
@@ -68,160 +68,37 @@ class ResultUtil {
|
||||
return new Final(lattice.join(((Final) r1).value, ((Final) r2).value));
|
||||
}
|
||||
if (r1 instanceof Final && r2 instanceof Pending) {
|
||||
Final f1 = (Final)r1;
|
||||
Pending pending = (Pending) r2;
|
||||
Set<Product> sum1 = new HashSet<>(pending.sum);
|
||||
sum1.add(new Product(f1.value, Collections.emptySet()));
|
||||
return new Pending(sum1);
|
||||
return addSingle((Pending)r2, (Final)r1);
|
||||
}
|
||||
if (r1 instanceof Pending && r2 instanceof Final) {
|
||||
Final f2 = (Final)r2;
|
||||
Pending pending = (Pending) r1;
|
||||
Set<Product> sum1 = new HashSet<>(pending.sum);
|
||||
sum1.add(new Product(f2.value, Collections.emptySet()));
|
||||
return new Pending(sum1);
|
||||
return addSingle((Pending)r1, (Final)r2);
|
||||
}
|
||||
assert r1 instanceof Pending && r2 instanceof Pending;
|
||||
Pending pending1 = (Pending) r1;
|
||||
Pending pending2 = (Pending) r2;
|
||||
Set<Product> sum = new HashSet<>();
|
||||
sum.addAll(pending1.sum);
|
||||
sum.addAll(pending2.sum);
|
||||
checkLimit(sum);
|
||||
Set<Component> sum = new HashSet<>();
|
||||
sum.addAll(Arrays.asList(pending1.delta));
|
||||
sum.addAll(Arrays.asList(pending2.delta));
|
||||
return new Pending(sum);
|
||||
}
|
||||
|
||||
private static void checkLimit(Set<Product> sum) throws AnalyzerException {
|
||||
int size = sum.stream().mapToInt(prod -> prod.ids.size()).sum();
|
||||
if (size > Analysis.EQUATION_SIZE_LIMIT) {
|
||||
throw new AnalyzerException(null, "Equation size is too big");
|
||||
@NotNull
|
||||
private static Pending addSingle(Pending pending, Final result) {
|
||||
Component component = new Component(result.value, EMPTY_PRODUCT);
|
||||
if(ArrayUtil.contains(component, pending.delta)) {
|
||||
return pending;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HResultUtil {
|
||||
private static final HKey[] EMPTY_PRODUCT = new HKey[0];
|
||||
private final ELattice<Value> lattice;
|
||||
final Value top;
|
||||
|
||||
HResultUtil(ELattice<Value> lattice) {
|
||||
this.lattice = lattice;
|
||||
top = lattice.top;
|
||||
}
|
||||
|
||||
HResult join(HResult r1, HResult r2) {
|
||||
if (r1 instanceof HFinal && ((HFinal) r1).value == top) {
|
||||
return r1;
|
||||
}
|
||||
if (r2 instanceof HFinal && ((HFinal) r2).value == top) {
|
||||
return r2;
|
||||
}
|
||||
if (r1 instanceof HFinal && r2 instanceof HFinal) {
|
||||
return new HFinal(lattice.join(((HFinal) r1).value, ((HFinal) r2).value));
|
||||
}
|
||||
if (r1 instanceof HFinal && r2 instanceof HPending) {
|
||||
HFinal f1 = (HFinal)r1;
|
||||
HPending pending = (HPending) r2;
|
||||
HComponent[] delta = new HComponent[pending.delta.length + 1];
|
||||
delta[0] = new HComponent(f1.value, EMPTY_PRODUCT);
|
||||
System.arraycopy(pending.delta, 0, delta, 1, pending.delta.length);
|
||||
return new HPending(delta);
|
||||
}
|
||||
if (r1 instanceof HPending && r2 instanceof HFinal) {
|
||||
HFinal f2 = (HFinal)r2;
|
||||
HPending pending = (HPending) r1;
|
||||
HComponent[] delta = new HComponent[pending.delta.length + 1];
|
||||
delta[0] = new HComponent(f2.value, EMPTY_PRODUCT);
|
||||
System.arraycopy(pending.delta, 0, delta, 1, pending.delta.length);
|
||||
return new HPending(delta);
|
||||
}
|
||||
HPending pending1 = (HPending) r1;
|
||||
HPending pending2 = (HPending) r2;
|
||||
return new HPending(ArrayUtil.mergeArrays(pending1.delta, pending2.delta, HComponent.ARRAY_FACTORY));
|
||||
}
|
||||
}
|
||||
|
||||
final class Product {
|
||||
@NotNull final Value value;
|
||||
@NotNull final Set<Key> ids;
|
||||
|
||||
Product(@NotNull Value value, @NotNull Set<Key> ids) {
|
||||
this.value = value;
|
||||
this.ids = ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Product product = (Product)o;
|
||||
|
||||
if (!ids.equals(product.ids)) return false;
|
||||
if (!value.equals(product.value)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = value.hashCode();
|
||||
result = 31 * result + ids.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
interface Result {}
|
||||
final class Final implements Result {
|
||||
final Value value;
|
||||
Final(Value value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Final{" + "value=" + value + '}';
|
||||
}
|
||||
}
|
||||
|
||||
final class Pending implements Result {
|
||||
final Set<Product> sum;
|
||||
|
||||
Pending(Set<Product> sum) {
|
||||
this.sum = sum;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final class Effects implements Result {
|
||||
final Set<EffectQuantum> effects;
|
||||
|
||||
Effects(Set<EffectQuantum> effects) {
|
||||
this.effects = effects;
|
||||
}
|
||||
}
|
||||
|
||||
final class Equation {
|
||||
final Key id;
|
||||
final Result rhs;
|
||||
|
||||
Equation(Key id, Result rhs) {
|
||||
this.id = id;
|
||||
this.rhs = rhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Equation{" + "id=" + id + ", rhs=" + rhs + '}';
|
||||
return new Pending(ArrayUtil.append(pending.delta, component));
|
||||
}
|
||||
}
|
||||
|
||||
final class CoreHKey {
|
||||
@NotNull
|
||||
final byte[] key;
|
||||
final MethodDescriptor myMethod;
|
||||
final int dirKey;
|
||||
|
||||
CoreHKey(@NotNull byte[] key, int dirKey) {
|
||||
this.key = key;
|
||||
CoreHKey(@NotNull MethodDescriptor method, int dirKey) {
|
||||
this.myMethod = method;
|
||||
this.dirKey = dirKey;
|
||||
}
|
||||
|
||||
@@ -231,83 +108,78 @@ final class CoreHKey {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
CoreHKey coreHKey = (CoreHKey)o;
|
||||
|
||||
if (dirKey != coreHKey.dirKey) return false;
|
||||
if (!Arrays.equals(key, coreHKey.key)) return false;
|
||||
return true;
|
||||
return dirKey == coreHKey.dirKey && myMethod.equals(coreHKey.myMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Arrays.hashCode(key);
|
||||
result = 31 * result + dirKey;
|
||||
return result;
|
||||
return 31 * myMethod.hashCode() + dirKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CoreHKey [" + HKey.bytesToString(key) + "|" + Direction.fromInt(dirKey) + "]";
|
||||
return "CoreHKey [" + myMethod + "|" + Direction.fromInt(dirKey) + "]";
|
||||
}
|
||||
}
|
||||
|
||||
final class Solver {
|
||||
|
||||
private final ELattice<Value> lattice;
|
||||
private final HashMap<HKey, HashSet<HKey>> dependencies = new HashMap<>();
|
||||
private final HashMap<HKey, HPending> pending = new HashMap<>();
|
||||
private final HashMap<HKey, Value> solved = new HashMap<>();
|
||||
private final Stack<HKey> moving = new Stack<>();
|
||||
private final HashMap<EKey, HashSet<EKey>> dependencies = new HashMap<>();
|
||||
private final HashMap<EKey, Pending> pending = new HashMap<>();
|
||||
private final HashMap<EKey, Value> solved = new HashMap<>();
|
||||
private final Stack<EKey> moving = new Stack<>();
|
||||
|
||||
private final HResultUtil resultUtil;
|
||||
private final HashMap<CoreHKey, HEquation> equations = new HashMap<>();
|
||||
private final ResultUtil resultUtil;
|
||||
private final HashMap<CoreHKey, Equation> equations = new HashMap<>();
|
||||
private final Value unstableValue;
|
||||
|
||||
Solver(ELattice<Value> lattice, Value unstableValue) {
|
||||
this.lattice = lattice;
|
||||
this.unstableValue = unstableValue;
|
||||
resultUtil = new HResultUtil(lattice);
|
||||
resultUtil = new ResultUtil(lattice);
|
||||
}
|
||||
|
||||
void addEquation(HEquation equation) {
|
||||
HKey key = equation.key;
|
||||
CoreHKey coreKey = new CoreHKey(key.key, key.dirKey);
|
||||
void addEquation(Equation equation) {
|
||||
EKey key = equation.key;
|
||||
CoreHKey coreKey = new CoreHKey(key.method, key.dirKey);
|
||||
|
||||
HEquation previousEquation = equations.get(coreKey);
|
||||
Equation previousEquation = equations.get(coreKey);
|
||||
if (previousEquation == null) {
|
||||
equations.put(coreKey, equation);
|
||||
} else {
|
||||
HKey joinKey = new HKey(coreKey.key, coreKey.dirKey, equation.key.stable && previousEquation.key.stable, true);
|
||||
HResult joinResult = resultUtil.join(equation.result, previousEquation.result);
|
||||
HEquation joinEquation = new HEquation(joinKey, joinResult);
|
||||
EKey joinKey = new EKey(coreKey.myMethod, coreKey.dirKey, equation.key.stable && previousEquation.key.stable, true);
|
||||
Result joinResult = resultUtil.join(equation.result, previousEquation.result);
|
||||
Equation joinEquation = new Equation(joinKey, joinResult);
|
||||
equations.put(coreKey, joinEquation);
|
||||
}
|
||||
}
|
||||
|
||||
void queueEquation(HEquation equation) {
|
||||
HResult rhs = equation.result;
|
||||
if (rhs instanceof HFinal) {
|
||||
solved.put(equation.key, ((HFinal) rhs).value);
|
||||
void queueEquation(Equation equation) {
|
||||
Result rhs = equation.result;
|
||||
if (rhs instanceof Final) {
|
||||
solved.put(equation.key, ((Final) rhs).value);
|
||||
moving.push(equation.key);
|
||||
} else if (rhs instanceof HPending) {
|
||||
HPending pendResult = ((HPending)rhs).copy();
|
||||
HResult norm = normalize(pendResult.delta);
|
||||
if (norm instanceof HFinal) {
|
||||
solved.put(equation.key, ((HFinal) norm).value);
|
||||
} else if (rhs instanceof Pending) {
|
||||
Pending pendResult = ((Pending)rhs).copy();
|
||||
Result norm = normalize(pendResult.delta);
|
||||
if (norm instanceof Final) {
|
||||
solved.put(equation.key, ((Final) norm).value);
|
||||
moving.push(equation.key);
|
||||
}
|
||||
else {
|
||||
HPending pendResult1 = ((HPending)rhs).copy();
|
||||
for (HComponent component : pendResult1.delta) {
|
||||
for (HKey trigger : component.ids) {
|
||||
HashSet<HKey> set = dependencies.get(trigger);
|
||||
Pending pendResult1 = ((Pending)rhs).copy();
|
||||
for (Component component : pendResult1.delta) {
|
||||
for (EKey trigger : component.ids) {
|
||||
HashSet<EKey> set = dependencies.get(trigger);
|
||||
if (set == null) {
|
||||
set = new HashSet<>();
|
||||
dependencies.put(trigger, set);
|
||||
}
|
||||
set.add(equation.key);
|
||||
}
|
||||
pending.put(equation.key, pendResult1);
|
||||
}
|
||||
pending.put(equation.key, pendResult1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -323,38 +195,38 @@ final class Solver {
|
||||
}
|
||||
}
|
||||
|
||||
Map<HKey, Value> solve() {
|
||||
for (HEquation hEquation : equations.values()) {
|
||||
queueEquation(hEquation);
|
||||
Map<EKey, Value> solve() {
|
||||
for (Equation equation : equations.values()) {
|
||||
queueEquation(equation);
|
||||
}
|
||||
while (!moving.empty()) {
|
||||
HKey id = moving.pop();
|
||||
EKey id = moving.pop();
|
||||
Value value = solved.get(id);
|
||||
|
||||
HKey[] initialPIds = id.stable ? new HKey[]{id, id.invertStability()} : new HKey[]{id.invertStability(), id};
|
||||
EKey[] initialPIds = id.stable ? new EKey[]{id, id.invertStability()} : new EKey[]{id.invertStability(), id};
|
||||
Value[] initialPVals = id.stable ? new Value[]{value, value} : new Value[]{value, unstableValue};
|
||||
|
||||
HKey[] pIds = new HKey[]{initialPIds[0], initialPIds[1], initialPIds[0].negate(), initialPIds[1].negate()};
|
||||
EKey[] pIds = new EKey[]{initialPIds[0], initialPIds[1], initialPIds[0].negate(), initialPIds[1].negate()};
|
||||
Value[] pVals = new Value[]{initialPVals[0], initialPVals[1], negate(initialPVals[0]), negate(initialPVals[1])};
|
||||
|
||||
for (int i = 0; i < pIds.length; i++) {
|
||||
HKey pId = pIds[i];
|
||||
EKey pId = pIds[i];
|
||||
Value pVal = pVals[i];
|
||||
HashSet<HKey> dIds = dependencies.get(pId);
|
||||
HashSet<EKey> dIds = dependencies.get(pId);
|
||||
if (dIds == null) {
|
||||
continue;
|
||||
}
|
||||
for (HKey dId : dIds) {
|
||||
HPending pend = pending.remove(dId);
|
||||
for (EKey dId : dIds) {
|
||||
Pending pend = pending.remove(dId);
|
||||
if (pend != null) {
|
||||
HResult pend1 = substitute(pend, pId, pVal);
|
||||
if (pend1 instanceof HFinal) {
|
||||
HFinal fi = (HFinal)pend1;
|
||||
Result pend1 = substitute(pend, pId, pVal);
|
||||
if (pend1 instanceof Final) {
|
||||
Final fi = (Final)pend1;
|
||||
solved.put(dId, fi.value);
|
||||
moving.push(dId);
|
||||
}
|
||||
else {
|
||||
pending.put(dId, (HPending)pend1);
|
||||
pending.put(dId, (Pending)pend1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -365,9 +237,9 @@ final class Solver {
|
||||
}
|
||||
|
||||
// substitute id -> value into pending
|
||||
HResult substitute(@NotNull HPending pending, @NotNull HKey id, @NotNull Value value) {
|
||||
HComponent[] sum = pending.delta;
|
||||
for (HComponent intIdComponent : sum) {
|
||||
Result substitute(@NotNull Pending pending, @NotNull EKey id, @NotNull Value value) {
|
||||
Component[] sum = pending.delta;
|
||||
for (Component intIdComponent : sum) {
|
||||
if (intIdComponent.remove(id)) {
|
||||
intIdComponent.value = lattice.meet(intIdComponent.value, value);
|
||||
}
|
||||
@@ -375,17 +247,17 @@ final class Solver {
|
||||
return normalize(sum);
|
||||
}
|
||||
|
||||
@NotNull HResult normalize(@NotNull HComponent[] sum) {
|
||||
@NotNull Result normalize(@NotNull Component[] sum) {
|
||||
Value acc = lattice.bot;
|
||||
boolean computableNow = true;
|
||||
for (HComponent prod : sum) {
|
||||
for (Component prod : sum) {
|
||||
if (prod.isEmpty() || prod.value == lattice.bot) {
|
||||
acc = lattice.join(acc, prod.value);
|
||||
} else {
|
||||
computableNow = false;
|
||||
}
|
||||
}
|
||||
return (acc == lattice.top || computableNow) ? new HFinal(acc) : new HPending(sum);
|
||||
return (acc == lattice.top || computableNow) ? new Final(acc) : new Pending(sum);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -679,7 +679,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM
|
||||
|
||||
@NonNls
|
||||
@NotNull
|
||||
private static String createAnnotationTag(@NotNull String annotationFQName, @Nullable PsiNameValuePair[] values) {
|
||||
public static String createAnnotationTag(@NotNull String annotationFQName, @Nullable PsiNameValuePair[] values) {
|
||||
@NonNls String text;
|
||||
if (values != null && values.length != 0) {
|
||||
text = " <annotation name=\'" + annotationFQName + "\'>\n";
|
||||
@@ -696,6 +696,11 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM
|
||||
|
||||
@Nullable
|
||||
private XmlFile createAnnotationsXml(@NotNull VirtualFile root, @NonNls @NotNull String packageName) {
|
||||
return createAnnotationsXml(root, packageName, myPsiManager);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static XmlFile createAnnotationsXml(@NotNull VirtualFile root, @NonNls @NotNull String packageName, PsiManager manager) {
|
||||
final String[] dirs = packageName.split("[\\.]");
|
||||
for (String dir : dirs) {
|
||||
if (dir.isEmpty()) break;
|
||||
@@ -710,7 +715,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM
|
||||
}
|
||||
root = subdir;
|
||||
}
|
||||
final PsiDirectory directory = myPsiManager.findDirectory(root);
|
||||
final PsiDirectory directory = manager.findDirectory(root);
|
||||
if (directory == null) return null;
|
||||
|
||||
final PsiFile psiFile = directory.findFile(ANNOTATIONS_XML);
|
||||
@@ -719,7 +724,7 @@ public class ExternalAnnotationsManagerImpl extends ReadableExternalAnnotationsM
|
||||
}
|
||||
|
||||
try {
|
||||
final PsiFileFactory factory = PsiFileFactory.getInstance(myPsiManager.getProject());
|
||||
final PsiFileFactory factory = PsiFileFactory.getInstance(manager.getProject());
|
||||
return (XmlFile)directory.add(factory.createFileFromText(ANNOTATIONS_XML, XmlFileType.INSTANCE, "<root></root>"));
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ public class IntroduceEmptyVariableHandler {
|
||||
|
||||
public void invoke(@NotNull Editor editor, @NotNull PsiFile file, @NotNull PsiType type) {
|
||||
Project project = file.getProject();
|
||||
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) return;
|
||||
int offset = editor.getCaretModel().getOffset();
|
||||
PsiElement at = file.findElementAt(offset);
|
||||
PsiElement anchorStatement = RefactoringUtil.getParentStatement(at, false);
|
||||
|
||||
-1
@@ -281,7 +281,6 @@
|
||||
</item>
|
||||
<item name='java.io.File java.lang.String slashify(java.lang.String, boolean)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val name="value" val=""!null,_->!null""/>
|
||||
<val name="pure" val="true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
|
||||
-5
@@ -343,11 +343,6 @@
|
||||
<item name='java.lang.invoke.BoundMethodHandle java.lang.RuntimeException badBoundArgumentException(java.lang.Object, java.lang.invoke.MethodHandle, int) 1'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='java.lang.invoke.BoundMethodHandle java.lang.String noParens(java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.lang.invoke.BoundMethodHandle java.lang.String noParens(java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
-25
@@ -486,11 +486,6 @@
|
||||
<val val=""!null->!null;null->null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.net.URI java.lang.String encode(java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.net.URI java.lang.String encode(java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
@@ -537,11 +532,6 @@
|
||||
<item name='java.net.URI java.lang.String normalize(java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='java.net.URI java.lang.String quote(java.lang.String, long, long)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null,_,_->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.net.URI java.lang.String quote(java.lang.String, long, long) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
@@ -586,30 +576,15 @@
|
||||
<item name='java.net.URI java.net.URI create(java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='java.net.URI java.net.URI normalize(java.net.URI)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.net.URI java.net.URI normalize(java.net.URI) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='java.net.URI java.net.URI parseServerAuthority()'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='java.net.URI java.net.URI relativize(java.net.URI)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.net.URI java.net.URI relativize(java.net.URI) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='java.net.URI java.net.URI relativize(java.net.URI, java.net.URI)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""_,!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.net.URI java.net.URI relativize(java.net.URI, java.net.URI) 1'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
-10
@@ -1673,11 +1673,6 @@
|
||||
<val name="pure" val="true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Collections.EmptyList T[] toArray(T[])'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Collections.EmptyList T[] toArray(T[]) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
@@ -1861,11 +1856,6 @@
|
||||
<val name="pure" val="true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Collections.EmptySet T[] toArray(T[])'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Collections.EmptySet T[] toArray(T[]) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
-20
@@ -3119,19 +3119,9 @@
|
||||
<item name='org.apache.commons.lang.StringUtils java.lang.String chomp(java.lang.String, java.lang.String) 1'>
|
||||
<annotation name='org.jetbrains.annotations.Nullable'/>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.StringUtils java.lang.String chompLast(java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.StringUtils java.lang.String chompLast(java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.StringUtils java.lang.String chompLast(java.lang.String, java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null,_->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.StringUtils java.lang.String chompLast(java.lang.String, java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
@@ -3217,11 +3207,6 @@
|
||||
<item name='org.apache.commons.lang.StringUtils java.lang.String escape(java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.Nullable'/>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.StringUtils java.lang.String getChomp(java.lang.String, java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""_,!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.StringUtils java.lang.String getChomp(java.lang.String, java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
@@ -3451,11 +3436,6 @@
|
||||
</annotation>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.StringUtils java.lang.String prechomp(java.lang.String, java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null,_->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.StringUtils java.lang.String prechomp(java.lang.String, java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
+1
-11
@@ -285,17 +285,12 @@
|
||||
</annotation>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction add(org.apache.commons.lang.math.Fraction)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction add(org.apache.commons.lang.math.Fraction) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction addSub(org.apache.commons.lang.math.Fraction, boolean)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null,_->!null;_,false->!null""/>
|
||||
<val val=""_,false->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction addSub(org.apache.commons.lang.math.Fraction, boolean) 0'>
|
||||
@@ -359,11 +354,6 @@
|
||||
<val name="pure" val="true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction subtract(org.apache.commons.lang.math.Fraction)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction subtract(org.apache.commons.lang.math.Fraction) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
-5
@@ -82,11 +82,6 @@
|
||||
<val name="pure" val="true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.velocity.app.event.implement.IncludeRelativePath java.lang.String includeEvent(java.lang.String, java.lang.String, java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null,_,_->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.velocity.app.event.implement.IncludeRelativePath java.lang.String includeEvent(java.lang.String, java.lang.String, java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
-5
@@ -34,11 +34,6 @@
|
||||
<item name='org.apache.velocity.app.tools.VelocityFormatter java.lang.String formatVector(java.util.List, java.lang.String, java.lang.String) 2'>
|
||||
<annotation name='org.jetbrains.annotations.Nullable'/>
|
||||
</item>
|
||||
<item name='org.apache.velocity.app.tools.VelocityFormatter java.lang.String limitLen(int, java.lang.String, java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""_,!null,_->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.velocity.app.tools.VelocityFormatter java.lang.String limitLen(int, java.lang.String, java.lang.String) 1'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
-5
@@ -15,11 +15,6 @@
|
||||
<item name='org.apache.velocity.convert.WebMacro boolean writeTemplate(java.lang.String, java.lang.String, java.lang.String) 2'>
|
||||
<annotation name='org.jetbrains.annotations.Nullable'/>
|
||||
</item>
|
||||
<item name='org.apache.velocity.convert.WebMacro java.lang.String convertName(java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.velocity.convert.WebMacro java.lang.String convertName(java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
-5
@@ -32,11 +32,6 @@
|
||||
<item name='org.apache.velocity.runtime.parser.Parser Parser(org.apache.velocity.runtime.RuntimeServices) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='org.apache.velocity.runtime.parser.Parser java.lang.String escapedDirective(java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.velocity.runtime.parser.Parser java.lang.String escapedDirective(java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
-5
@@ -169,11 +169,6 @@
|
||||
</annotation>
|
||||
<annotation name='org.jetbrains.annotations.Nullable'/>
|
||||
</item>
|
||||
<item name='org.apache.velocity.util.StringUtils java.lang.String sub(java.lang.String, java.lang.String, java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null,_,_->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.velocity.util.StringUtils java.lang.String sub(java.lang.String, java.lang.String, java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
+131
-79
@@ -17,10 +17,15 @@ package com.intellij.java.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManager;
|
||||
import com.intellij.codeInsight.ExternalAnnotationsManagerImpl;
|
||||
import com.intellij.codeInsight.daemon.GutterMark;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter;
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis;
|
||||
import com.intellij.openapi.application.Result;
|
||||
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.projectRoots.Sdk;
|
||||
import com.intellij.openapi.projectRoots.SdkModificator;
|
||||
import com.intellij.openapi.roots.AnnotationOrderRootType;
|
||||
@@ -38,19 +43,21 @@ import com.intellij.openapi.vfs.VirtualFileVisitor;
|
||||
import com.intellij.psi.*;
|
||||
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.PsiTestUtil;
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.AsynchConsumer;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import one.util.streamex.EntryStream;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.java.decompiler.IdeaDecompiler;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
@@ -61,6 +68,7 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC
|
||||
private MessageDigest myMessageDigest;
|
||||
private final List<String> myDiffs = new ArrayList<>();
|
||||
private boolean myNullableMethodRegistryValue;
|
||||
private VirtualFile myAnnotationsDir;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
@@ -88,8 +96,8 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC
|
||||
|
||||
private void setUpExternalUpAnnotations() {
|
||||
String annotationsPath = PathManagerEx.getTestDataPath() + "/codeInspection/bytecodeAnalysis/annotations";
|
||||
VirtualFile annotationsDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(annotationsPath);
|
||||
assertNotNull(annotationsDir);
|
||||
myAnnotationsDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(annotationsPath);
|
||||
assertNotNull(myAnnotationsDir);
|
||||
|
||||
ModuleRootModificationUtil.updateModel(myModule, new AsynchConsumer<ModifiableRootModel>() {
|
||||
@Override
|
||||
@@ -101,7 +109,7 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC
|
||||
Library[] libs = libraryTable.getLibraries();
|
||||
for (Library library : libs) {
|
||||
Library.ModifiableModel libraryModel = library.getModifiableModel();
|
||||
libraryModel.addRoot(annotationsDir, AnnotationOrderRootType.getInstance());
|
||||
libraryModel.addRoot(myAnnotationsDir, AnnotationOrderRootType.getInstance());
|
||||
libraryModel.commit();
|
||||
}
|
||||
Sdk sdk = modifiableRootModel.getSdk();
|
||||
@@ -114,15 +122,16 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
SdkModificator sdkModificator = clone.getSdkModificator();
|
||||
sdkModificator.addRoot(annotationsDir, AnnotationOrderRootType.getInstance());
|
||||
sdkModificator.addRoot(myAnnotationsDir, AnnotationOrderRootType.getInstance());
|
||||
sdkModificator.commitChanges();
|
||||
modifiableRootModel.setSdk(clone);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
VfsUtilCore.visitChildrenRecursively(annotationsDir, new VirtualFileVisitor() { });
|
||||
annotationsDir.refresh(false, true);
|
||||
VfsUtilCore.visitChildrenRecursively(myAnnotationsDir, new VirtualFileVisitor() {
|
||||
});
|
||||
myAnnotationsDir.refresh(false, true);
|
||||
}
|
||||
|
||||
private void openDecompiledClass(String name) {
|
||||
@@ -198,80 +207,11 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC
|
||||
assert rootPackage != null;
|
||||
|
||||
GlobalSearchScope scope = GlobalSearchScope.allScope(getProject());
|
||||
JavaRecursiveElementVisitor visitor = new JavaRecursiveElementVisitor() {
|
||||
@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)) {
|
||||
processClass(aClass);
|
||||
for (PsiClass innerClass : aClass.getInnerClasses()) {
|
||||
processClass(innerClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processClass(PsiClass aClass) {
|
||||
for (PsiMethod method : aClass.getMethods()) {
|
||||
exportMethodAnnotations(method);
|
||||
}
|
||||
for (PsiClass innerClass : aClass.getInnerClasses()) {
|
||||
processClass(innerClass);
|
||||
}
|
||||
}
|
||||
};
|
||||
JavaRecursiveElementVisitor visitor = new AnnotationExporter(scope);
|
||||
|
||||
rootPackage.accept(visitor);
|
||||
}
|
||||
|
||||
private void exportMethodAnnotations(PsiMethod method) {
|
||||
// @Contract
|
||||
PsiAnnotation inferredContractAnnotation = findInferredAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT);
|
||||
if (inferredContractAnnotation != null) {
|
||||
PsiNameValuePair[] attributes = inferredContractAnnotation.getParameterList().getAttributes();
|
||||
ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT, method.getContainingFile(), attributes);
|
||||
}
|
||||
|
||||
{
|
||||
// @NotNull method
|
||||
PsiAnnotation inferredNotNullMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NOT_NULL);
|
||||
if (inferredNotNullMethodAnnotation != null) {
|
||||
ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(method, AnnotationUtil.NOT_NULL, method.getContainingFile(), null);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// @Nullable method
|
||||
PsiAnnotation inferredNullableMethodAnnotation = findInferredAnnotation(method, AnnotationUtil.NULLABLE);
|
||||
if (inferredNullableMethodAnnotation != null) {
|
||||
ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(method, AnnotationUtil.NULLABLE, method.getContainingFile(), null);
|
||||
}
|
||||
}
|
||||
|
||||
for (PsiParameter parameter : method.getParameterList().getParameters()) {
|
||||
{
|
||||
// @NotNull parameter
|
||||
PsiAnnotation inferredNotNull = findInferredAnnotation(parameter, AnnotationUtil.NOT_NULL);
|
||||
if (inferredNotNull != null) {
|
||||
ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(parameter, AnnotationUtil.NOT_NULL, method.getContainingFile(), null);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// @Nullable parameter
|
||||
PsiAnnotation inferredNullable = findInferredAnnotation(parameter, AnnotationUtil.NULLABLE);
|
||||
if (inferredNullable != null) {
|
||||
ExternalAnnotationsManager.getInstance(myModule.getProject()).annotateExternally(parameter, AnnotationUtil.NULLABLE, method.getContainingFile(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkMethodAnnotations(PsiMethod method) {
|
||||
if (ProjectBytecodeAnalysis.getKey(method, myMessageDigest) == null) {
|
||||
return;
|
||||
@@ -344,4 +284,116 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC
|
||||
private PsiAnnotation findExternalAnnotation(PsiModifierListOwner owner, String fqn) {
|
||||
return ExternalAnnotationsManager.getInstance(myModule.getProject()).findExternalAnnotation(owner, fqn);
|
||||
}
|
||||
|
||||
private class AnnotationExporter extends JavaRecursiveElementVisitor {
|
||||
private final GlobalSearchScope myScope;
|
||||
|
||||
public AnnotationExporter(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;
|
||||
new WriteCommandAction(getProject()) {
|
||||
@Override
|
||||
protected void run(@NotNull Result result) throws Throwable {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -222,7 +222,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
*/
|
||||
|
||||
|
||||
HKey psiKey = BytecodeAnalysisConverter.psiKey(psiMethod, Direction.Out, myMessageDigest);
|
||||
EKey psiKey = BytecodeAnalysisConverter.psiKey(psiMethod, Direction.Out, myMessageDigest);
|
||||
if (noKey) {
|
||||
assertTrue(null == psiKey);
|
||||
return;
|
||||
@@ -230,7 +230,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
else {
|
||||
assertFalse(null == psiKey);
|
||||
}
|
||||
HKey asmKey = BytecodeAnalysisConverter.asmKey(new Key(method, Direction.Out, true), myMessageDigest);
|
||||
EKey asmKey = new EKey(method, Direction.Out, true).hashed(myMessageDigest);
|
||||
Assert.assertEquals(asmKey, psiKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
* 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.
|
||||
@@ -38,4 +38,9 @@ public interface GlobalOptions {
|
||||
String JPS_SYSTEM_BUILDER_ID = "JPS";
|
||||
// notification about the files changed during compilation, but not compiled in current compilation session
|
||||
String JPS_UNPROCESSED_FS_CHANGES_MESSAGE_ID = "!unprocessed_fs_changes_detected!";
|
||||
|
||||
/**
|
||||
* The path to external project config directory (used for external system projects).
|
||||
*/
|
||||
String EXTERNAL_PROJECT_CONFIG = "external.project.config";
|
||||
}
|
||||
|
||||
+6
-5
@@ -55,7 +55,7 @@ public class JpsGlobalLoader extends JpsLoaderBase {
|
||||
|
||||
public static void loadGlobalSettings(JpsGlobal global, String optionsPath) throws IOException {
|
||||
Path optionsDir = Paths.get(FileUtil.toCanonicalPath(optionsPath));
|
||||
new JpsGlobalLoader(global, Collections.emptyMap()).loadGlobalComponents(optionsDir, new PathVariablesSerializer());
|
||||
new JpsGlobalLoader(global, Collections.emptyMap()).loadGlobalComponents(optionsDir, optionsDir.resolve("other.xml"), new PathVariablesSerializer());
|
||||
Map<String, String> pathVariables = JpsModelSerializationDataService.computeAllPathVariables(global);
|
||||
new JpsGlobalLoader(global, pathVariables).load(optionsDir);
|
||||
}
|
||||
@@ -69,19 +69,20 @@ public class JpsGlobalLoader extends JpsLoaderBase {
|
||||
}
|
||||
|
||||
private void load(@NotNull Path optionsDir) {
|
||||
Path defaultConfigFile = optionsDir.resolve("other.xml");
|
||||
LOG.debug("Loading config from " + optionsDir.toAbsolutePath());
|
||||
for (JpsGlobalExtensionSerializer serializer : SERIALIZERS) {
|
||||
loadGlobalComponents(optionsDir, serializer);
|
||||
loadGlobalComponents(optionsDir, defaultConfigFile, serializer);
|
||||
}
|
||||
for (JpsModelSerializerExtension extension : JpsModelSerializerExtension.getExtensions()) {
|
||||
for (JpsGlobalExtensionSerializer serializer : extension.getGlobalExtensionSerializers()) {
|
||||
loadGlobalComponents(optionsDir, serializer);
|
||||
loadGlobalComponents(optionsDir, defaultConfigFile, serializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadGlobalComponents(@NotNull Path optionsDir, JpsGlobalExtensionSerializer serializer) {
|
||||
loadComponents(optionsDir, "other.xml", serializer, myGlobal);
|
||||
private void loadGlobalComponents(@NotNull Path optionsDir, @NotNull Path defaultConfigFile, JpsGlobalExtensionSerializer serializer) {
|
||||
loadComponents(optionsDir, defaultConfigFile.getParent(), serializer, myGlobal);
|
||||
}
|
||||
|
||||
public static class PathVariablesSerializer extends JpsGlobalExtensionSerializer {
|
||||
|
||||
+34
-25
@@ -21,11 +21,13 @@ import com.intellij.openapi.util.SystemInfo;
|
||||
import org.jdom.Element;
|
||||
import org.jdom.JDOMException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jps.TimingLog;
|
||||
import org.jetbrains.jps.model.JpsElement;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
@@ -40,25 +42,22 @@ public abstract class JpsLoaderBase {
|
||||
myMacroExpander = macroExpander;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if file doesn't exist
|
||||
*/
|
||||
@Nullable
|
||||
protected Element loadRootElement(@NotNull Path file) {
|
||||
return loadRootElement(file, myMacroExpander);
|
||||
}
|
||||
|
||||
protected <E extends JpsElement> void loadComponents(@NotNull Path dir,
|
||||
final String defaultFileName,
|
||||
@NotNull Path defaultConfigFile,
|
||||
JpsElementExtensionSerializerBase<E> serializer,
|
||||
final E element) {
|
||||
String fileName = serializer.getConfigFileName();
|
||||
Path configFile = dir.resolve(fileName != null ? fileName : defaultFileName);
|
||||
Path configFile = fileName == null ? defaultConfigFile : dir.resolve(fileName);
|
||||
Runnable timingLog = TimingLog.startActivity("loading: " + configFile.getFileName() + ":" + serializer.getComponentName());
|
||||
Element componentTag;
|
||||
if (Files.exists(configFile)) {
|
||||
componentTag = JDomSerializationUtil.findComponent(loadRootElement(configFile), serializer.getComponentName());
|
||||
}
|
||||
else {
|
||||
componentTag = null;
|
||||
}
|
||||
|
||||
Element componentTag = JDomSerializationUtil.findComponent(loadRootElement(configFile), serializer.getComponentName());
|
||||
if (componentTag != null) {
|
||||
serializer.loadExtension(element, componentTag);
|
||||
}
|
||||
@@ -68,35 +67,45 @@ public abstract class JpsLoaderBase {
|
||||
timingLog.run();
|
||||
}
|
||||
|
||||
protected static Element loadRootElement(@NotNull Path file, final JpsMacroExpander macroExpander) {
|
||||
try {
|
||||
final Element element = tryLoadRootElement(file);
|
||||
/**
|
||||
* Returns null if file doesn't exist
|
||||
*/
|
||||
@Nullable
|
||||
protected static Element loadRootElement(@NotNull Path file, @NotNull JpsMacroExpander macroExpander) {
|
||||
final Element element = tryLoadRootElement(file);
|
||||
if (element != null) {
|
||||
macroExpander.substitute(element, SystemInfo.isFileSystemCaseSensitive);
|
||||
return element;
|
||||
}
|
||||
catch (JDOMException e) {
|
||||
throw new CannotLoadJpsModelException(file.toFile(), "Cannot parse xml file " + file.toAbsolutePath() + ": " + e.getMessage(), e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new CannotLoadJpsModelException(file.toFile(), "Cannot read file " + file.toAbsolutePath() + ": " + e.getMessage(), e);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
private static Element tryLoadRootElement(@NotNull Path file) throws IOException, JDOMException {
|
||||
for (int i = 0; i < MAX_ATTEMPTS - 1; i++) {
|
||||
@Nullable
|
||||
private static Element tryLoadRootElement(@NotNull Path file) {
|
||||
int i = 0;
|
||||
while (true) {
|
||||
try {
|
||||
return JDOMUtil.load(Files.newBufferedReader(file));
|
||||
}
|
||||
catch (Exception e) {
|
||||
catch (NoSuchFileException e) {
|
||||
return null;
|
||||
}
|
||||
catch (IOException | JDOMException e) {
|
||||
if (++i == MAX_ATTEMPTS) {
|
||||
//noinspection InstanceofCatchParameter
|
||||
throw new CannotLoadJpsModelException(file.toFile(), "Cannot " + (e instanceof IOException ? "read" : "parse") + " file " + file.toAbsolutePath() + ": " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
LOG.info("Loading attempt #" + i + " failed", e);
|
||||
}
|
||||
|
||||
//most likely configuration file is being written by IDE so we'll wait a little
|
||||
try {
|
||||
//noinspection BusyWait
|
||||
Thread.sleep(300);
|
||||
}
|
||||
catch (InterruptedException ignored) { }
|
||||
catch (InterruptedException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return JDOMUtil.load(Files.newBufferedReader(file));
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
* 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.
|
||||
@@ -19,6 +19,7 @@ import com.intellij.openapi.components.ExpandMacroToPathMap;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
@@ -59,7 +60,7 @@ public class JpsMacroExpander {
|
||||
}
|
||||
}
|
||||
|
||||
public void substitute(Element element, boolean caseSensitive) {
|
||||
public void substitute(@NotNull Element element, boolean caseSensitive) {
|
||||
myExpandMacroMap.substitute(element, caseSensitive);
|
||||
}
|
||||
|
||||
|
||||
+25
-20
@@ -117,10 +117,11 @@ public class JpsProjectLoader extends JpsLoaderBase {
|
||||
|
||||
private void loadFromDirectory(@NotNull Path dir) {
|
||||
myProject.setName(getDirectoryBaseProjectName(dir));
|
||||
JpsSdkType<?> projectSdkType = loadProjectRoot(loadRootElement(dir.resolve("misc.xml")));
|
||||
Path defaultConfigFile = dir.resolve("misc.xml");
|
||||
JpsSdkType<?> projectSdkType = loadProjectRoot(loadRootElement(defaultConfigFile));
|
||||
for (JpsModelSerializerExtension extension : JpsModelSerializerExtension.getExtensions()) {
|
||||
for (JpsProjectExtensionSerializer serializer : extension.getProjectExtensionSerializers()) {
|
||||
loadComponents(dir, "misc.xml", serializer, myProject);
|
||||
loadComponents(dir, defaultConfigFile, serializer, myProject);
|
||||
}
|
||||
}
|
||||
loadModules(loadRootElement(dir.resolve("modules.xml")), projectSdkType);
|
||||
@@ -129,6 +130,13 @@ public class JpsProjectLoader extends JpsLoaderBase {
|
||||
for (Path libraryFile : listXmlFiles(dir.resolve("libraries"))) {
|
||||
loadProjectLibraries(loadRootElement(libraryFile));
|
||||
}
|
||||
|
||||
Path externalConfigDir = resolveExternalProjectConfig("project");
|
||||
if (externalConfigDir != null) {
|
||||
LOG.info("External project config dir is used: " + externalConfigDir);
|
||||
loadProjectLibraries(loadRootElement(externalConfigDir.resolve("libraries.xml")));
|
||||
}
|
||||
|
||||
timingLog.run();
|
||||
|
||||
Runnable artifactsTimingLog = TimingLog.startActivity("loading artifacts");
|
||||
@@ -142,11 +150,7 @@ public class JpsProjectLoader extends JpsLoaderBase {
|
||||
for (Path configurationFile : listXmlFiles(dir.resolve("runConfigurations"))) {
|
||||
JpsRunConfigurationSerializer.loadRunConfigurations(myProject, loadRootElement(configurationFile));
|
||||
}
|
||||
Path workspaceFile = dir.resolve("workspace.xml");
|
||||
if (Files.exists(workspaceFile)) {
|
||||
Element runManager = JDomSerializationUtil.findComponent(loadRootElement(workspaceFile), "RunManager");
|
||||
JpsRunConfigurationSerializer.loadRunConfigurations(myProject, runManager);
|
||||
}
|
||||
JpsRunConfigurationSerializer.loadRunConfigurations(myProject, JDomSerializationUtil.findComponent(loadRootElement(dir.resolve("workspace.xml")), "RunManager"));
|
||||
runConfTimingLog.run();
|
||||
}
|
||||
}
|
||||
@@ -178,7 +182,7 @@ public class JpsProjectLoader extends JpsLoaderBase {
|
||||
String projectName = FileUtil.getNameWithoutExtension(iprFile.getFileName().toString());
|
||||
myProject.setName(projectName);
|
||||
Path iwsFile = iprFile.getParent().resolve(projectName + ".iws");
|
||||
Element iwsRoot = Files.exists(iwsFile) ? loadRootElement(iwsFile) : null;
|
||||
Element iwsRoot = loadRootElement(iwsFile);
|
||||
|
||||
JpsSdkType<?> projectSdkType = loadProjectRoot(iprRoot);
|
||||
for (JpsModelSerializerExtension extension : JpsModelSerializerExtension.getExtensions()) {
|
||||
@@ -207,7 +211,7 @@ public class JpsProjectLoader extends JpsLoaderBase {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JpsSdkType<?> loadProjectRoot(Element root) {
|
||||
private JpsSdkType<?> loadProjectRoot(@Nullable Element root) {
|
||||
JpsSdkType<?> sdkType = null;
|
||||
Element rootManagerElement = JDomSerializationUtil.findComponent(root, "ProjectRootManager");
|
||||
if (rootManagerElement != null) {
|
||||
@@ -225,7 +229,7 @@ public class JpsProjectLoader extends JpsLoaderBase {
|
||||
JpsLibraryTableSerializer.loadLibraries(libraryTableElement, myProject.getLibraryCollection());
|
||||
}
|
||||
|
||||
private void loadModules(Element root, final @Nullable JpsSdkType<?> projectSdkType) {
|
||||
private void loadModules(@Nullable Element root, final @Nullable JpsSdkType<?> projectSdkType) {
|
||||
Runnable timingLog = TimingLog.startActivity("loading modules");
|
||||
Element componentRoot = JDomSerializationUtil.findComponent(root, "ProjectModuleManager");
|
||||
if (componentRoot == null) return;
|
||||
@@ -248,29 +252,30 @@ public class JpsProjectLoader extends JpsLoaderBase {
|
||||
timingLog.run();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Path resolveExternalProjectConfig(@NotNull String subDirName) {
|
||||
String externalProjectConfigDir = System.getProperty("external.project.config");
|
||||
return StringUtil.isEmptyOrSpaces(externalProjectConfigDir) ? null : Paths.get(externalProjectConfigDir, subDirName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<JpsModule> loadModules(@NotNull List<Path> moduleFiles, @Nullable final JpsSdkType<?> projectSdkType,
|
||||
@NotNull final Map<String, String> pathVariables) {
|
||||
List<JpsModule> modules = new ArrayList<>();
|
||||
List<Future<Pair<Path, Element>>> futureModuleFilesContents = new ArrayList<>();
|
||||
String externalProjectConfigDir = System.getProperty("external.project.config");
|
||||
Path externalModuleDir = StringUtil.isEmptyOrSpaces(externalProjectConfigDir) ? null : Paths.get(externalProjectConfigDir, "modules");
|
||||
Path externalModuleDir = resolveExternalProjectConfig("modules");
|
||||
if (externalModuleDir != null) {
|
||||
LOG.info("External project config dir is used: " + externalProjectConfigDir);
|
||||
LOG.info("External project config dir is used for modules: " + externalModuleDir);
|
||||
}
|
||||
|
||||
for (Path file : moduleFiles) {
|
||||
futureModuleFilesContents.add(ourThreadPool.submit(() -> {
|
||||
final JpsMacroExpander expander = createModuleMacroExpander(pathVariables, file);
|
||||
|
||||
Element data = null;
|
||||
if (Files.exists(file)) {
|
||||
data = loadRootElement(file, expander);
|
||||
}
|
||||
|
||||
Element data = loadRootElement(file, expander);
|
||||
Path externalPath = externalModuleDir == null ? null : externalModuleDir.resolve(FileUtilRt.getNameWithoutExtension(file.getFileName().toString()) + ".xml");
|
||||
if (externalPath != null && Files.exists(externalPath)) {
|
||||
Element externalData = loadRootElement(externalPath, expander);
|
||||
Element externalData = externalPath == null ? null : loadRootElement(externalPath, expander);
|
||||
if (externalData != null) {
|
||||
if (data == null) {
|
||||
data = externalData;
|
||||
}
|
||||
|
||||
+8
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
* 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.
|
||||
@@ -26,6 +26,7 @@ import org.jetbrains.jps.model.JpsElementFactory;
|
||||
import org.jetbrains.jps.model.JpsProject;
|
||||
import org.jetbrains.jps.model.serialization.JpsModelSerializerExtension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -35,6 +36,11 @@ public class JpsRunConfigurationSerializer {
|
||||
private static final Logger LOG = Logger.getInstance(JpsRunConfigurationSerializer.class);
|
||||
|
||||
public static void loadRunConfigurations(@NotNull JpsProject project, @Nullable Element runManagerTag) {
|
||||
List<Element> elements = JDOMUtil.getChildren(runManagerTag, "configuration");
|
||||
if (elements.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, JpsRunConfigurationPropertiesSerializer<?>> serializers = new HashMap<>();
|
||||
for (JpsModelSerializerExtension extension : JpsModelSerializerExtension.getExtensions()) {
|
||||
for (JpsRunConfigurationPropertiesSerializer<?> serializer : extension.getRunConfigurationPropertiesSerializers()) {
|
||||
@@ -42,7 +48,7 @@ public class JpsRunConfigurationSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
for (Element configurationTag : JDOMUtil.getChildren(runManagerTag, "configuration")) {
|
||||
for (Element configurationTag : elements) {
|
||||
if (Boolean.parseBoolean(configurationTag.getAttributeValue("default"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ final class JpsGantTool {
|
||||
|
||||
private static JpsModule addModule(File imlFile, JpsModel model, JpsGantProjectBuilder builder) {
|
||||
def pathVariables = JpsModelSerializationDataService.computeAllPathVariables(model.global)
|
||||
def modules = JpsProjectLoader.loadModules(Collections.singletonList(imlFile), JpsJavaSdkType.INSTANCE, pathVariables)
|
||||
def modules = JpsProjectLoader.loadModules(Collections.singletonList(imlFile.toPath()), JpsJavaSdkType.INSTANCE, pathVariables)
|
||||
def module = modules.get(0)
|
||||
model.project.addModule(module)
|
||||
builder.info("Module ${module.getName()} added to the project")
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.jetbrains.jsonSchema.ide.JsonSchemaService;
|
||||
import com.jetbrains.jsonSchema.impl.JsonSchemaObject;
|
||||
import com.jetbrains.jsonSchema.impl.JsonSchemaReferenceContributor;
|
||||
import com.jetbrains.jsonSchema.schemaFile.TestJsonSchemaMappingsProjectConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.util.*;
|
||||
@@ -52,7 +53,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
return BASE_PATH;
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testJsonSchemaCrossReferenceCompletion() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -95,7 +95,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
assertStringItems(strings);
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testRefreshSchemaCompletionSimpleVariant() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
private String myModuleDir;
|
||||
@@ -126,7 +125,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testJsonSchemaCrossReferenceCompletionWithSchemaEditing() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
private String myModuleDir;
|
||||
@@ -197,7 +195,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
assertStringItems("\"one1\"", "\"two1\"");
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testJsonSchemaRefsCrossResolve() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -233,7 +230,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testJsonSchemaGlobalRefsCrossResolve() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -270,7 +266,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testJson2SchemaPropertyResolve() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -313,7 +308,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testFindRefInOtherFile() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -343,7 +337,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testFindRefToOtherFile() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -374,7 +367,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testNavigateToPropertyDefinitionInPackageJsonSchema() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -405,7 +397,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testNavigateToPropertyDefinitionNestedDefinitions() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -434,7 +425,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testNavigateToAllOfOneOfDefinitions() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -465,7 +455,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testNestedAllOneAnyWithInheritanceNavigation() throws Exception {
|
||||
final String prefix = "nestedAllOneAnyWithInheritance/";
|
||||
skeleton(new Callback() {
|
||||
@@ -496,7 +485,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testNestedAllOneAnyWithInheritanceCompletion() throws Exception {
|
||||
final String prefix = "nestedAllOneAnyWithInheritance/";
|
||||
skeleton(new Callback() {
|
||||
@@ -521,7 +509,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testNestedAllOneAnyWithInheritanceHighlighting() throws Exception {
|
||||
final String prefix = "nestedAllOneAnyWithInheritance/";
|
||||
skeleton(new Callback() {
|
||||
@@ -546,7 +533,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testNavigateToDefinitionByRef() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -606,7 +592,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testNavigateFromSchemaDefinitionToMainSchema() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -637,7 +622,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testNavigateToRefInsideMainSchema() throws Exception {
|
||||
final JsonSchemaService service = JsonSchemaService.Impl.get(myProject);
|
||||
final List<JsonSchemaFileProvider> providers = new JsonSchemaProjectSelfProviderFactory().getProviders(myProject);
|
||||
@@ -672,7 +656,6 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
Assert.assertEquals("positiveInteger", ((JsonProperty) resolve.getParent()).getName());
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testNavigateToDefinitionByRefInFileWithIncorrectReference() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -691,16 +674,20 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
@Override
|
||||
public void doCheck() {
|
||||
final String midia = "midia";
|
||||
checkNavigationIntoDefinition(midia);
|
||||
checkNavigationTo(midia, JsonSchemaObject.DEFINITIONS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void checkNavigationIntoDefinition(String name) {
|
||||
private void checkNavigationTo(@NotNull String name, @NotNull String base) {
|
||||
int offset = myEditor.getCaretModel().getPrimaryCaret().getOffset();
|
||||
final PsiElement element = myFile.findElementAt(offset);
|
||||
Assert.assertNotNull(element);
|
||||
|
||||
checkNavigationTo(name, offset, base);
|
||||
}
|
||||
|
||||
private void checkNavigationTo(@NotNull String name, int offset, @NotNull String base) {
|
||||
final PsiReference referenceAt = myFile.findReferenceAt(offset);
|
||||
Assert.assertNotNull(referenceAt);
|
||||
final PsiElement resolve = referenceAt.resolve();
|
||||
@@ -710,10 +697,9 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
Assert.assertTrue(parent instanceof JsonProperty);
|
||||
Assert.assertEquals(name, ((JsonProperty)parent).getName());
|
||||
Assert.assertTrue(parent.getParent().getParent() instanceof JsonProperty);
|
||||
Assert.assertEquals(JsonSchemaObject.DEFINITIONS, ((JsonProperty)parent.getParent().getParent()).getName());
|
||||
Assert.assertEquals(base, ((JsonProperty)parent.getParent().getParent()).getName());
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testInsideCycledSchemaNavigation() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -730,12 +716,11 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
|
||||
@Override
|
||||
public void doCheck() {
|
||||
checkNavigationIntoDefinition("all");
|
||||
checkNavigationTo("all", JsonSchemaObject.DEFINITIONS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@CanChangeDocumentDuringHighlighting
|
||||
public void testNavigationIntoCycledSchema() throws Exception {
|
||||
skeleton(new Callback() {
|
||||
@Override
|
||||
@@ -753,20 +738,7 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
|
||||
@Override
|
||||
public void doCheck() {
|
||||
int offset = myEditor.getCaretModel().getPrimaryCaret().getOffset();
|
||||
final PsiElement element = myFile.findElementAt(offset);
|
||||
Assert.assertNotNull(element);
|
||||
|
||||
final PsiReference referenceAt = myFile.findReferenceAt(offset);
|
||||
Assert.assertNotNull(referenceAt);
|
||||
final PsiElement resolve = referenceAt.resolve();
|
||||
Assert.assertNotNull(resolve);
|
||||
Assert.assertEquals("\"bbb\"", resolve.getText());
|
||||
final PsiElement parent = resolve.getParent();
|
||||
Assert.assertTrue(parent instanceof JsonProperty);
|
||||
Assert.assertEquals("bbb", ((JsonProperty)parent).getName());
|
||||
Assert.assertTrue(parent.getParent().getParent() instanceof JsonProperty);
|
||||
Assert.assertEquals("properties", ((JsonProperty)parent.getParent().getParent()).getName());
|
||||
checkNavigationTo("bbb", JsonSchemaObject.PROPERTIES);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -792,19 +764,7 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
final List<JsonStringLiteral> list = strings.stream()
|
||||
.filter(expression -> expression.getText().contains("#/definitions")).collect(Collectors.toList());
|
||||
Assert.assertEquals(3, list.size());
|
||||
list.forEach(literal -> {
|
||||
final PsiReference ref = myFile.findReferenceAt(literal.getTextRange().getStartOffset() + 1);
|
||||
Assert.assertNotNull(ref);
|
||||
final PsiElement resolve = ref.resolve();
|
||||
Assert.assertNotNull(literal.getText(), resolve);
|
||||
Assert.assertTrue(resolve.isValid());
|
||||
Assert.assertEquals("\"cycle.schema\"", resolve.getText());
|
||||
final PsiElement parent = resolve.getParent();
|
||||
Assert.assertTrue(parent instanceof JsonProperty);
|
||||
Assert.assertEquals("cycle.schema", ((JsonProperty)parent).getName());
|
||||
Assert.assertTrue(parent.getParent().getParent() instanceof JsonProperty);
|
||||
Assert.assertEquals("definitions", ((JsonProperty)parent.getParent().getParent()).getName());
|
||||
});
|
||||
list.forEach(literal -> checkNavigationTo("cycle.schema", literal.getTextRange().getStartOffset() + 1, JsonSchemaObject.DEFINITIONS));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -827,20 +787,7 @@ public class JsonSchemaCrossReferencesTest extends JsonSchemaHeavyAbstractTest {
|
||||
|
||||
@Override
|
||||
public void doCheck() {
|
||||
int offset = myEditor.getCaretModel().getPrimaryCaret().getOffset();
|
||||
final PsiElement element = myFile.findElementAt(offset);
|
||||
Assert.assertNotNull(element);
|
||||
|
||||
final PsiReference referenceAt = myFile.findReferenceAt(offset);
|
||||
Assert.assertNotNull(referenceAt);
|
||||
final PsiElement resolve = referenceAt.resolve();
|
||||
Assert.assertNotNull(resolve);
|
||||
Assert.assertEquals("\"id\"", resolve.getText());
|
||||
final PsiElement parent = resolve.getParent();
|
||||
Assert.assertTrue(parent instanceof JsonProperty);
|
||||
Assert.assertEquals("id", ((JsonProperty)parent).getName());
|
||||
Assert.assertTrue(parent.getParent().getParent() instanceof JsonProperty);
|
||||
Assert.assertEquals(JsonSchemaObject.PROPERTIES, ((JsonProperty)parent.getParent().getParent()).getName());
|
||||
checkNavigationTo("id", JsonSchemaObject.PROPERTIES);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+2
-1
@@ -27,8 +27,9 @@ import java.util.List;
|
||||
/**
|
||||
* @author Vladislav.Soroka
|
||||
* @since 12/19/13
|
||||
*
|
||||
* @deprecated use {@link ExternalSystemTaskManager} interface
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class AbstractExternalSystemTaskManager<S extends ExternalSystemExecutionSettings> implements ExternalSystemTaskManager<S> {
|
||||
|
||||
public abstract void executeTasks(@NotNull ExternalSystemTaskId id,
|
||||
|
||||
+4
-2
@@ -19,10 +19,10 @@ import com.intellij.openapi.externalSystem.model.ExternalSystemException;
|
||||
import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings;
|
||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
|
||||
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -52,7 +52,9 @@ public interface ExternalSystemTaskManager<S extends ExternalSystemExecutionSett
|
||||
@Nullable S settings,
|
||||
@Nullable String jvmAgentSetup,
|
||||
@NotNull ExternalSystemTaskNotificationListener listener) throws ExternalSystemException {
|
||||
executeTasks(id, taskNames, projectPath, settings, Collections.emptyList(), Collections.emptyList(), jvmAgentSetup, listener);
|
||||
List<String> vmOptions = settings == null ? ContainerUtil.emptyList() : ContainerUtil.newArrayList(settings.getVmOptions());
|
||||
List<String> arguments = settings == null ? ContainerUtil.emptyList() : ContainerUtil.newArrayList(settings.getArguments());
|
||||
executeTasks(id, taskNames, projectPath, settings, vmOptions, arguments, jvmAgentSetup, listener);
|
||||
}
|
||||
|
||||
boolean cancelTask(@NotNull ExternalSystemTaskId id,
|
||||
|
||||
@@ -492,7 +492,7 @@ public class FindDialog extends DialogWrapper implements FindUI {
|
||||
|
||||
final FindUsagesProcessPresentation processPresentation =
|
||||
FindInProjectUtil.setupProcessPresentation(myProject, showPanelIfOnlyOneUsage, presentation);
|
||||
Ref<VirtualFile> lastUsageFileRef = new Ref<>();
|
||||
ThreadLocal<VirtualFile> lastUsageFileRef = new ThreadLocal<>();
|
||||
|
||||
FindInProjectUtil.findUsages(findModel, myProject, info -> {
|
||||
if(isCancelled()) {
|
||||
|
||||
@@ -823,8 +823,8 @@ public class FindPopupPanel extends JBPanel implements FindUI, DataProvider {
|
||||
|
||||
final FindUsagesProcessPresentation processPresentation =
|
||||
FindInProjectUtil.setupProcessPresentation(myProject, showPanelIfOnlyOneUsage, presentation);
|
||||
Ref<VirtualFile> lastUsageFileRef = new Ref<>();
|
||||
Ref<Usage> recentUsageRef = new Ref<>();
|
||||
ThreadLocal<VirtualFile> lastUsageFileRef = new ThreadLocal<>();
|
||||
ThreadLocal<Usage> recentUsageRef = new ThreadLocal<>();
|
||||
|
||||
FindInProjectUtil.findUsages(myHelper.getModel().clone(), myProject, info -> {
|
||||
if(isCancelled()) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.diagnostic.LogUtil;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.RecentStringInterner;
|
||||
import com.intellij.util.io.AbstractStringEnumerator;
|
||||
@@ -114,7 +115,15 @@ public class StubSerializationHelper {
|
||||
|
||||
private int getClassId(final ObjectStubSerializer serializer) {
|
||||
final int idValue = mySerializerToId.get(serializer);
|
||||
assert idValue > 0: "No ID found for serializer " + LogUtil.objectAndClass(serializer);
|
||||
if (idValue <= 0) {
|
||||
assert false : "No ID found for serializer " +
|
||||
LogUtil.objectAndClass(serializer) +
|
||||
", external id:" +
|
||||
serializer.getExternalId() +
|
||||
(serializer instanceof IElementType ?
|
||||
", language:" + ((IElementType)serializer).getLanguage() + ", " + serializer : "")
|
||||
;
|
||||
}
|
||||
return idValue;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ class MemoryDiskConflictResolver {
|
||||
|
||||
for (VirtualFile file : conflicts) {
|
||||
Document document = FileDocumentManager.getInstance().getCachedDocument(file);
|
||||
if (document != null && askReloadFromDisk(file, document)) {
|
||||
if (document != null && file.getModificationStamp() != document.getModificationStamp() && askReloadFromDisk(file, document)) {
|
||||
FileDocumentManager.getInstance().reloadFromDisk(document);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +141,9 @@ public class FrameWrapper implements Disposable, DataProvider {
|
||||
} else {
|
||||
((JDialog)frame).setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
}
|
||||
|
||||
((RootPaneContainer)frame).getRootPane().putClientProperty("jetbrains.awt.windowDarkAppearance" , UIUtil.isUnderDarcula());
|
||||
|
||||
final WindowAdapter focusListener = new WindowAdapter() {
|
||||
@Override
|
||||
public void windowOpened(WindowEvent e) {
|
||||
|
||||
@@ -286,5 +286,8 @@
|
||||
<extensionPoint name="streamProviderFactory" interface="com.intellij.configurationStore.StreamProviderFactory" area="IDEA_PROJECT"/>
|
||||
|
||||
<extensionPoint name="reporting.sendPermission" interface="com.intellij.reporting.DataSendPermission"/>
|
||||
|
||||
<extensionPoint name="tasks.contextProvider" interface="com.intellij.tasks.context.WorkingContextProvider" area="IDEA_PROJECT"/>
|
||||
|
||||
</extensionPoints>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -490,5 +490,15 @@
|
||||
|
||||
<credentialStore implementation="com.intellij.credentialStore.MacOsCredentialStoreFactory" os="mac"/>
|
||||
<credentialStore implementation="com.intellij.credentialStore.LinuxSecretCredentialStoreFactory" os="linux"/>
|
||||
|
||||
<projectService serviceImplementation="com.intellij.tasks.context.WorkingContextManager"/>
|
||||
|
||||
<!--context providers-->
|
||||
<tasks.contextProvider implementation="com.intellij.tasks.context.OpenEditorsContextProvider"/>
|
||||
<tasks.contextProvider implementation="com.intellij.tasks.context.ProjectViewContextProvider"/>
|
||||
<tasks.contextProvider implementation="com.intellij.tasks.context.RunConfigurationsContextProvider"/>
|
||||
<tasks.contextProvider implementation="com.intellij.tasks.context.XDebuggerBreakpointsContextProvider"/>
|
||||
<tasks.contextProvider implementation="com.intellij.tasks.context.XDebuggerWatchesProvider"/>
|
||||
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -107,16 +107,20 @@ public abstract class PerFileMappingsBase<T> implements PersistentStateComponent
|
||||
if (t != null) return t;
|
||||
t = getMappingForHierarchy(originalFile, mappings);
|
||||
if (t != null) return t;
|
||||
Project project = getProject();
|
||||
if (project == null || file == null ||
|
||||
file.getFileSystem() instanceof NonPhysicalFileSystem ||
|
||||
ProjectFileIndex.getInstance(project).isInContent(file)) {
|
||||
return mappings.get(null);
|
||||
}
|
||||
return null;
|
||||
return getNotInHierarchy(file, mappings);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected T getNotInHierarchy(@Nullable VirtualFile file, @NotNull Map<VirtualFile, T> mappings) {
|
||||
if (getProject() == null || file == null ||
|
||||
file.getFileSystem() instanceof NonPhysicalFileSystem ||
|
||||
ProjectFileIndex.getInstance(getProject()).isInContent(file)) {
|
||||
return mappings.get(null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static <T> T getMappingForHierarchy(@Nullable VirtualFile file, @NotNull Map<VirtualFile, T> mappings) {
|
||||
for (VirtualFile cur = file; cur != null; cur = cur.getParent()) {
|
||||
T t = mappings.get(cur);
|
||||
|
||||
@@ -58,6 +58,9 @@ com.intellij.junit4.JUnit4IntegrationTest
|
||||
[GUI_TESTS]
|
||||
com.intellij.testGuiFramework.tests.*
|
||||
|
||||
[JAVA_TESTS]
|
||||
com.intellij.java.*
|
||||
|
||||
[GROOVY_TESTS]
|
||||
org.jetbrains.plugins.groovy.*
|
||||
|
||||
|
||||
@@ -54,5 +54,6 @@
|
||||
<orderEntry type="library" exported="" scope="TEST" name="cucumber-testing" level="project" />
|
||||
<orderEntry type="module" module-name="testGuiFramework" scope="TEST" />
|
||||
<orderEntry type="library" name="ini4j" level="project" />
|
||||
<orderEntry type="module" module-name="tasks-platform-impl" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 git4idea.branch
|
||||
|
||||
import com.intellij.tasks.context.WorkingContextManager
|
||||
import git4idea.test.GitPlatformTest
|
||||
import junit.framework.TestCase
|
||||
|
||||
class GitBranchContextTest: GitPlatformTest() {
|
||||
|
||||
fun testContextManager() {
|
||||
val contextManager = WorkingContextManager.getInstance(project)
|
||||
TestCase.assertNotNull(contextManager)
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,6 @@
|
||||
|
||||
<extensionPoints>
|
||||
<extensionPoint name="repositoryType" interface="com.intellij.tasks.TaskRepositoryType"/>
|
||||
<extensionPoint area="IDEA_PROJECT" name="contextProvider" interface="com.intellij.tasks.context.WorkingContextProvider"/>
|
||||
<extensionPoint name="dialogPanelProvider" interface="com.intellij.tasks.ui.TaskDialogPanelProvider"/>
|
||||
<extensionPoint name="commitPlaceholderProvider" interface="com.intellij.tasks.CommitPlaceholderProvider"/>
|
||||
</extensionPoints>
|
||||
@@ -111,19 +110,11 @@
|
||||
<editChangelistSupport implementation="com.intellij.tasks.impl.TaskChangelistSupport"/>
|
||||
|
||||
<projectService serviceImplementation="com.intellij.tasks.impl.TaskProjectConfiguration"/>
|
||||
<projectService serviceImplementation="com.intellij.tasks.context.WorkingContextManager"/>
|
||||
<applicationService serviceImplementation="com.intellij.tasks.config.RecentTaskRepositories"/>
|
||||
<applicationService serviceImplementation="com.intellij.tasks.config.TaskSettings"/>
|
||||
|
||||
<lang.documentationProvider language="TEXT" implementationClass="com.intellij.tasks.doc.TaskDocumentationProvider"/>
|
||||
|
||||
<!--context providers-->
|
||||
<tasks.contextProvider implementation="com.intellij.tasks.context.OpenEditorsContextProvider"/>
|
||||
<tasks.contextProvider implementation="com.intellij.tasks.context.ProjectViewContextProvider"/>
|
||||
<tasks.contextProvider implementation="com.intellij.tasks.context.RunConfigurationsContextProvider"/>
|
||||
<tasks.contextProvider implementation="com.intellij.tasks.context.XDebuggerBreakpointsContextProvider"/>
|
||||
<tasks.contextProvider implementation="com.intellij.tasks.context.XDebuggerWatchesProvider"/>
|
||||
|
||||
<tasks.repositoryType implementation="com.intellij.tasks.jira.JiraRepositoryType"/>
|
||||
<tasks.repositoryType implementation="com.intellij.tasks.youtrack.YouTrackRepositoryType"/>
|
||||
<tasks.repositoryType implementation="com.intellij.tasks.lighthouse.LighthouseRepositoryType"/>
|
||||
|
||||
Reference in New Issue
Block a user