Introduced AggressiveHardCodedPurity under registry key (IDEA-CR-21434)

New key = java.annotations.inference.aggressive.hardcoded.purity (true
by default). Implies purity for Object.toString(), Iterable.iterator(),
Iterator.hasNext() and this-changing Iterator.next(). Parameter-changing
 mode supported for hardcoded purity.
This commit is contained in:
Tagir Valeev
2017-05-30 17:32:56 +07:00
parent adb5d93cee
commit 82da560c31
10 changed files with 237 additions and 67 deletions

View File

@@ -52,8 +52,14 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys
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 int VERSION = 4; // change when inference algorithm changes
private static final int VERSION_MODIFIER = HardCodedPurity.AGGRESSIVE_HARDCODED_PURITY ? 1 : 0;
private static final int VERSION_MODIFIER_MAX = 2;
private static final int FINAL_VERSION = VERSION * VERSION_MODIFIER_MAX + VERSION_MODIFIER;
private static final VirtualFileGist<Map<HMethod, Equations>> ourGist = GistManager.getInstance().newVirtualFileGist(
"BytecodeAnalysisIndex", 7, new EquationsExternalizer(), new ClassDataIndexer());
"BytecodeAnalysisIndex", FINAL_VERSION, new EquationsExternalizer(), new ClassDataIndexer());
@NotNull
@Override

View File

@@ -0,0 +1,120 @@
/*
* 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.util.Couple;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
class HardCodedPurity {
static final boolean AGGRESSIVE_HARDCODED_PURITY = Registry.is("java.annotations.inference.aggressive.hardcoded.purity", true);
private static Set<Couple<String>> ownedFields = ContainerUtil.set(
new Couple<>("java/lang/AbstractStringBuilder", "value")
);
private static Set<Method> thisChangingMethods = ContainerUtil.set(
new Method("java/lang/Throwable", "fillInStackTrace", "()Ljava/lang/Throwable;")
);
// Assumed that all these methods are not only pure, but return object which could be safely modified
private static Set<Method> pureMethods = ContainerUtil.set(
// Maybe overloaded and be not pure, but this would be definitely bad code style
// Used in Throwable(Throwable) ctor, so this helps to infer purity of many exception constructors
new Method("java/lang/Throwable", "toString", "()Ljava/lang/String;"),
// Declared in final class StringBuilder
new Method("java/lang/StringBuilder", "toString", "()Ljava/lang/String;"),
new Method("java/lang/StringBuffer", "toString", "()Ljava/lang/String;"),
// Native
new Method("java/lang/Object", "getClass", "()Ljava/lang/Class;"),
new Method("java/lang/Class", "getComponentType", "()Ljava/lang/Class;"),
new Method("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;I)Ljava/lang/Object;"),
new Method("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;[I)Ljava/lang/Object;"),
new Method("java/lang/Float", "floatToRawIntBits", "(F)I"),
new Method("java/lang/Float", "intBitsToFloat", "(I)F"),
new Method("java/lang/Double", "doubleToRawLongBits", "(D)J"),
new Method("java/lang/Double", "longBitsToDouble", "(J)D")
);
private static Map<Method, Set<EffectQuantum>> solutions = new HashMap<>();
private static Set<EffectQuantum> thisChange = Collections.singleton(EffectQuantum.ThisChangeQuantum);
static {
// Native
solutions.put(new Method("java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V"),
Collections.singleton(new EffectQuantum.ParamChangeQuantum(2)));
solutions.put(new Method("java/lang/Object", "hashCode", "()I"), Collections.emptySet());
}
static HardCodedPurity getInstance() {
return AGGRESSIVE_HARDCODED_PURITY ? new AggressiveHardCodedPurity() : new HardCodedPurity();
}
Set<EffectQuantum> getHardCodedSolution(Method method) {
return isThisChangingMethod(method) ? thisChange : isPureMethod(method) ? Collections.emptySet() : solutions.get(method);
}
boolean isThisChangingMethod(Method method) {
return isBuilderChainCall(method) || thisChangingMethods.contains(method);
}
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)
return (method.internalClassName.equals("java/lang/StringBuilder") || method.internalClassName.equals("java/lang/StringBuffer")) &&
method.methodName.startsWith("append");
}
boolean isPureMethod(Method method) {
return pureMethods.contains(method);
}
boolean isOwnedField(FieldInsnNode fieldInsn) {
return ownedFields.contains(new Couple<>(fieldInsn.owner, fieldInsn.name));
}
static class AggressiveHardCodedPurity extends HardCodedPurity {
static final Set<String> ITERABLES = ContainerUtil.set("java/lang/Iterable", "java/util/Collection",
"java/util/List", "java/util/Set", "java/util/ArrayList",
"java/util/HashSet", "java/util/AbstractList",
"java/util/AbstractSet", "java/util/TreeSet");
@Override
boolean isThisChangingMethod(Method method) {
if (method.methodName.equals("next") && method.methodDesc.startsWith("()") && method.internalClassName.equals("java/util/Iterator")) {
return true;
}
return super.isThisChangingMethod(method);
}
@Override
boolean isPureMethod(Method method) {
if (method.methodName.equals("toString") && method.methodDesc.equals("()Ljava/lang/String;")) return true;
if (method.methodName.equals("iterator") && method.methodDesc.equals("()Ljava/util/Iterator;") &&
ITERABLES.contains(method.internalClassName)) {
return true;
}
if (method.methodName.equals("hasNext") && method.methodDesc.equals("()Z") && method.internalClassName.equals("java/util/Iterator")) {
return true;
}
return super.isPureMethod(method);
}
}
}

View File

@@ -16,9 +16,7 @@
package com.intellij.codeInspection.bytecodeAnalysis;
import com.intellij.codeInspection.bytecodeAnalysis.asm.ASMUtils;
import com.intellij.openapi.util.Couple;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.Type;
@@ -43,7 +41,7 @@ public class PurityAnalysis {
@NotNull
public static Equation analyze(Method method, MethodNode methodNode, boolean stable) {
EKey key = new EKey(method, Direction.Pure, stable);
Set<EffectQuantum> hardCodedSolution = HardCodedPurity.getHardCodedSolution(method);
Set<EffectQuantum> hardCodedSolution = HardCodedPurity.getInstance().getHardCodedSolution(method);
if (hardCodedSolution != null) {
return new Equation(key, new Effects(hardCodedSolution));
}
@@ -386,11 +384,11 @@ class DataInterpreter extends Interpreter<DataValue> {
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(method)) {
if (HardCodedPurity.getInstance().isPureMethod(method)) {
quantum = null;
result = DataValue.LocalDataValue;
}
else if (HardCodedPurity.isThisChangingMethod(method)) {
else if (HardCodedPurity.getInstance().isThisChangingMethod(method)) {
DataValue receiver = ArrayUtil.getFirstElement(data);
if (receiver == DataValue.ThisDataValue) {
quantum = EffectQuantum.ThisChangeQuantum;
@@ -398,7 +396,10 @@ class DataInterpreter extends Interpreter<DataValue> {
else if (receiver == DataValue.LocalDataValue || receiver == DataValue.OwnedDataValue) {
quantum = null;
}
if (HardCodedPurity.isBuilderChainCall(method)) {
else if (receiver instanceof DataValue.ParameterDataValue) {
quantum = new EffectQuantum.ParamChangeQuantum(((DataValue.ParameterDataValue)receiver).n);
}
if (HardCodedPurity.getInstance().isBuilderChainCall(method)) {
// mostly to support string concatenation
result = receiver;
}
@@ -424,7 +425,7 @@ class DataInterpreter extends Interpreter<DataValue> {
return DataValue.UnknownDataValue2;
case Opcodes.GETFIELD:
FieldInsnNode fieldInsn = ((FieldInsnNode)insn);
if (value == DataValue.ThisDataValue && HardCodedPurity.isOwnedField(fieldInsn)) {
if (value == DataValue.ThisDataValue && HardCodedPurity.getInstance().isOwnedField(fieldInsn)) {
return DataValue.OwnedDataValue;
} else {
return ASMUtils.getSizeFast(fieldInsn.desc) == 1 ? DataValue.UnknownDataValue1 : DataValue.UnknownDataValue2;
@@ -474,63 +475,6 @@ class DataInterpreter extends Interpreter<DataValue> {
}
}
final class HardCodedPurity {
private static Set<Couple<String>> ownedFields = ContainerUtil.set(
new Couple<>("java/lang/AbstractStringBuilder", "value")
);
private static Set<Method> thisChangingMethods = ContainerUtil.set(
new Method("java/lang/Throwable", "fillInStackTrace", "()Ljava/lang/Throwable;")
);
// Assumed that all these methods are not only pure, but return object which could be safely modified
private static Set<Method> pureMethods = ContainerUtil.set(
// Maybe overloaded and be not pure, but this would be definitely bad code style
// Used in Throwable(Throwable) ctor, so this helps to infer purity of many exception constructors
new Method("java/lang/Throwable", "toString", "()Ljava/lang/String;"),
// Native
new Method("java/lang/Object", "getClass", "()Ljava/lang/Class;"),
new Method("java/lang/Class", "getComponentType", "()Ljava/lang/Class;"),
new Method("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;I)Ljava/lang/Object;"),
new Method("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;[I)Ljava/lang/Object;"),
new Method("java/lang/Float", "floatToRawIntBits", "(F)I"),
new Method("java/lang/Float", "intBitsToFloat", "(I)F"),
new Method("java/lang/Double", "doubleToRawLongBits", "(D)J"),
new Method("java/lang/Double", "longBitsToDouble", "(J)D")
);
private static Map<Method, Set<EffectQuantum>> solutions = new HashMap<>();
private static Set<EffectQuantum> thisChange = Collections.singleton(EffectQuantum.ThisChangeQuantum);
static {
// Native
solutions.put(new Method("java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V"),
Collections.singleton(new EffectQuantum.ParamChangeQuantum(2)));
solutions.put(new Method("java/lang/Object", "hashCode", "()I"), Collections.emptySet());
}
static Set<EffectQuantum> getHardCodedSolution(Method method) {
return isThisChangingMethod(method) ? thisChange : isPureMethod(method) ? Collections.emptySet() : solutions.get(method);
}
static boolean isThisChangingMethod(Method method) {
return isBuilderChainCall(method) || thisChangingMethods.contains(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)
return (method.internalClassName.equals("java/lang/StringBuilder") || method.internalClassName.equals("java/lang/StringBuffer")) &&
method.methodName.startsWith("append");
}
static boolean isPureMethod(Method method) {
return method.methodName.equals("toString") && method.methodDesc.equals("()Ljava/lang/String;") ||
pureMethods.contains(method);
}
static boolean isOwnedField(FieldInsnNode fieldInsn) {
return ownedFields.contains(new Couple<>(fieldInsn.owner, fieldInsn.name));
}
}
final class PuritySolver {
private HashMap<EKey, Set<EffectQuantum>> solved = new HashMap<>();
private HashMap<EKey, Set<EKey>> dependencies = new HashMap<>();

View File

@@ -1278,6 +1278,11 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.lang.Iterable java.util.Iterator&lt;T&gt; iterator()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.lang.Long Long(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -917,6 +917,11 @@
<item name='java.util.Calendar void writeObject(java.io.ObjectOutputStream) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.Collection java.util.Iterator&lt;E&gt; iterator()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Collections Collections()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
@@ -1063,6 +1068,9 @@
</annotation>
</item>
<item name='java.util.Collections java.util.Enumeration&lt;T&gt; enumeration(java.util.Collection&lt;T&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.Collections java.util.Iterator&lt;E&gt; singletonIterator(E)'>
@@ -1378,6 +1386,9 @@
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.Collections.CheckedCollection java.util.Iterator&lt;E&gt; iterator()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.Collections.CheckedCollection void typeCheck(java.lang.Object) 0'>
@@ -1503,6 +1514,9 @@
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.Collections.CheckedMap.CheckedEntrySet java.util.Iterator&lt;java.util.Map.Entry&lt;K,V&gt;&gt; iterator()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.Collections.CheckedMap.CheckedEntrySet.CheckedEntry CheckedEntry(java.util.Map.Entry&lt;K,V&gt;, java.lang.Class&lt;T&gt;)'>
@@ -1926,6 +1940,11 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Collections.SetFromMap java.util.Iterator&lt;E&gt; iterator()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Collections.SetFromMap void readObject(java.io.ObjectInputStream) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -2017,6 +2036,11 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Collections.SynchronizedCollection java.util.Iterator&lt;E&gt; iterator()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Collections.SynchronizedCollection void writeObject(java.io.ObjectOutputStream) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -2201,6 +2225,9 @@
</annotation>
</item>
<item name='java.util.Collections.UnmodifiableCollection java.util.Iterator&lt;E&gt; iterator()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.Collections.UnmodifiableCollection void clear()'>
@@ -2334,6 +2361,9 @@
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='java.util.Collections.UnmodifiableMap.UnmodifiableEntrySet java.util.Iterator&lt;java.util.Map.Entry&lt;K,V&gt;&gt; iterator()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.Collections.UnmodifiableMap.UnmodifiableEntrySet.UnmodifiableEntry UnmodifiableEntry(java.util.Map.Entry&lt;? extends K,? extends V&gt;)'>
@@ -3076,6 +3106,11 @@
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.Iterator boolean hasNext()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.LinkedHashMap LinkedHashMap(java.util.Map&lt;? extends K,? extends V&gt;) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -3146,6 +3181,11 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.List java.util.Iterator&lt;E&gt; iterator()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Locale Locale(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -3357,6 +3397,11 @@
<item name='java.util.Random void writeObject(java.io.ObjectOutputStream) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.Set java.util.Iterator&lt;E&gt; iterator()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.TreeMap K firstKey()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>

View File

@@ -51,6 +51,11 @@
<item name='org.apache.commons.collections.ExtendedProperties java.lang.String unescape(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.apache.commons.collections.ExtendedProperties java.util.Iterator getKeys()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.commons.collections.ExtendedProperties org.apache.commons.collections.ExtendedProperties convertProperties(java.util.Properties) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -1543,6 +1543,11 @@
<item name='org.apache.commons.lang.CharSet CharSet(java.lang.String[]) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.apache.commons.lang.CharSet boolean contains(char)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.commons.lang.CharSet boolean equals(java.lang.Object) 0'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
@@ -3315,7 +3320,8 @@
</item>
<item name='org.apache.commons.lang.StringUtils java.lang.String join(java.util.Collection, char)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;null,_-&gt;null&quot;"/>
<val name="value" val="&quot;null,_-&gt;null&quot;"/>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
@@ -3324,7 +3330,8 @@
</item>
<item name='org.apache.commons.lang.StringUtils java.lang.String join(java.util.Collection, java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;null,_-&gt;null&quot;"/>
<val name="value" val="&quot;null,_-&gt;null&quot;"/>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
@@ -4137,9 +4144,19 @@
<item name='org.apache.commons.lang.Validate void noNullElements(java.lang.Object[], java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.apache.commons.lang.Validate void noNullElements(java.util.Collection)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.commons.lang.Validate void noNullElements(java.util.Collection) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.apache.commons.lang.Validate void noNullElements(java.util.Collection, java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.commons.lang.Validate void noNullElements(java.util.Collection, java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -90,6 +90,11 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.velocity.anakia.NodeList java.util.Iterator iterator()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.velocity.anakia.NodeList java.util.List getList()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>

View File

@@ -23,6 +23,26 @@
<item name='org.apache.velocity.app.event.EventCartridge boolean removeEventHandler(org.apache.velocity.app.event.EventHandler) 0'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='org.apache.velocity.app.event.EventCartridge java.util.Iterator getIncludeEventHandlers()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.velocity.app.event.EventCartridge java.util.Iterator getInvalidReferenceEventHandlers()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.velocity.app.event.EventCartridge java.util.Iterator getMethodExceptionEventHandlers()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.velocity.app.event.EventCartridge java.util.Iterator getNullSetEventHandlers()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.velocity.app.event.EventCartridge java.util.Iterator getReferenceInsertionEventHandlers()'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>

View File

@@ -407,6 +407,9 @@ java.annotations.inference.nullable.method.restartRequired=true
java.annotations.inference.nullable.method.transitivity=true
java.annotations.inference.nullable.method.transitivity.description=If a method result is a call to a @Nullable method, reports the caller as @Nullable as well
java.annotations.inference.nullable.method.transitivity.restartRequired=true
java.annotations.inference.aggressive.hardcoded.purity=true
java.annotations.inference.aggressive.hardcoded.purity.description=Assume any implementation of methods like Object.toString() or Iterable.iterator() to be pure during bytecode inference. This assumption might lead to false-positives in some inspections, though it's believed to uncover more bugs.
java.annotations.inference.aggressive.hardcoded.purity.restartRequired=true
java.hierarchy.service=false
java.hierarchy.service.description=Enable special indexing for quick JVM class hierarchy calculation