make NamedObjectProviderBinding thread-safe to fix EA-1138581 (plugin) - NSEE: SmartList.getFromArray

GitOrigin-RevId: 482107e14834d2df8e50ec7f893cffd6c964a996
This commit is contained in:
Alexey Kudravtsev
2024-03-21 17:55:49 +01:00
committed by intellij-monorepo-bot
parent 329c890ea0
commit 794799dce9
7 changed files with 88 additions and 41 deletions

View File

@@ -7,42 +7,46 @@ import com.intellij.patterns.ElementPattern;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReferenceProvider;
import com.intellij.psi.PsiReferenceService;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ProcessingContext;
import com.intellij.util.SharedProcessingContext;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author maxim
*/
public abstract class NamedObjectProviderBinding implements ProviderBinding {
private final Map<String, List<@NotNull ProviderInfo<ElementPattern<?>>>> myNamesToProvidersMap = new HashMap<>(5);
private final Map<String, List<@NotNull ProviderInfo<ElementPattern<?>>>> myNamesToProvidersMapInsensitive = new HashMap<>(5);
/**
* arrays inside these maps must be copy-on-write to avoid data races, since they can be read concurrently,
* via {@link #addAcceptableReferenceProviders}
*/
private final Map<String, @NotNull ProviderInfo<ElementPattern<?>>[]> myNamesToProvidersMap = new ConcurrentHashMap<>(5);
private final Map<String, @NotNull ProviderInfo<ElementPattern<?>>[]> myNamesToProvidersMapInsensitive = new ConcurrentHashMap<>(5);
synchronized
public void registerProvider(@NonNls String @NotNull [] names,
@NotNull ElementPattern<?> filter,
boolean caseSensitive,
@NotNull PsiReferenceProvider provider,
double priority) {
Map<String, List<ProviderInfo<ElementPattern<?>>>> map = caseSensitive ? myNamesToProvidersMap : myNamesToProvidersMapInsensitive;
Map<String, @NotNull ProviderInfo<ElementPattern<?>>[]> map = caseSensitive ? myNamesToProvidersMap : myNamesToProvidersMapInsensitive;
for (String attributeName : names) {
String key = caseSensitive ? attributeName : StringUtil.toLowerCase(attributeName);
List<ProviderInfo<ElementPattern<?>>> psiReferenceProviders = map.get(key);
ProviderInfo<ElementPattern<?>>[] psiReferenceProviders = map.get(key);
if (psiReferenceProviders == null) {
map.put(key, psiReferenceProviders = new SmartList<>());
}
ProviderInfo<ElementPattern<?>> newInfo = new ProviderInfo<>(provider, filter, priority);
ProviderInfo<ElementPattern<?>>[] newProviders = psiReferenceProviders == null ? new ProviderInfo[]{newInfo} : ArrayUtil.append(psiReferenceProviders, newInfo);
psiReferenceProviders.add(new ProviderInfo<>(provider, filter, priority));
map.put(key, newProviders);
}
}
@@ -52,21 +56,30 @@ public abstract class NamedObjectProviderBinding implements ProviderBinding {
@NotNull PsiReferenceService.Hints hints) {
String name = getName(position);
if (name != null) {
addMatchingProviders(position, ContainerUtil.notNullize(myNamesToProvidersMap.get(name)), list, hints);
addMatchingProviders(position, ContainerUtil.notNullize(myNamesToProvidersMapInsensitive.get(StringUtil.toLowerCase(name))), list, hints);
addMatchingProviders(position, myNamesToProvidersMap.get(name), list, hints);
addMatchingProviders(position, myNamesToProvidersMapInsensitive.get(StringUtil.toLowerCase(name)), list, hints);
}
}
synchronized
@Override
public void unregisterProvider(@NotNull PsiReferenceProvider provider) {
for (List<ProviderInfo<ElementPattern<?>>> list : myNamesToProvidersMap.values()) {
list.removeIf(trinity -> trinity.provider.equals(provider));
for (Map.Entry<String, @NotNull ProviderInfo<ElementPattern<?>>[]> entry : myNamesToProvidersMap.entrySet()) {
entry.setValue((ProviderInfo<ElementPattern<?>>[])removeFromArray(provider, entry.getValue()));
}
for (List<ProviderInfo<ElementPattern<?>>> list : myNamesToProvidersMapInsensitive.values()) {
list.removeIf(trinity -> trinity.provider.equals(provider));
for (Map.Entry<String, @NotNull ProviderInfo<ElementPattern<?>>[]> entry : myNamesToProvidersMapInsensitive.entrySet()) {
entry.setValue((ProviderInfo<ElementPattern<?>>[])removeFromArray(provider, entry.getValue()));
}
}
static @NotNull ProviderInfo<?> @NotNull [] removeFromArray(@NotNull PsiReferenceProvider provider, @NotNull ProviderInfo<?> @NotNull [] array) {
int i = ContainerUtil.indexOf(array, trinity -> trinity.provider.equals(provider));
if (i != -1) {
return ArrayUtil.remove(array, i, ProviderInfo.ARRAY_FACTORY);
}
return array;
}
boolean isEmpty() {
return myNamesToProvidersMap.isEmpty() && myNamesToProvidersMapInsensitive.isEmpty();
}
@@ -74,9 +87,10 @@ public abstract class NamedObjectProviderBinding implements ProviderBinding {
protected abstract @Nullable String getName(@NotNull PsiElement position);
static void addMatchingProviders(@NotNull PsiElement position,
@NotNull List<? extends @NotNull ProviderInfo<ElementPattern<?>>> providerList,
@NotNull ProviderInfo<ElementPattern<?>> @Nullable [] providerList,
@NotNull Collection<? super ProviderInfo<ProcessingContext>> output,
@NotNull PsiReferenceService.Hints hints) {
if (providerList == null) return;
SharedProcessingContext sharedProcessingContext = new SharedProcessingContext();
for (ProviderInfo<ElementPattern<?>> info : providerList) {

View File

@@ -5,18 +5,21 @@ package com.intellij.psi.impl.source.resolve.reference;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReferenceProvider;
import com.intellij.psi.PsiReferenceService;
import com.intellij.util.ArrayFactory;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public interface ProviderBinding {
class ProviderInfo<Context> {
public final @NotNull PsiReferenceProvider provider;
public final @NotNull Context processingContext;
public final double priority;
class ProviderInfo<T> {
static final ProviderInfo<?>[] EMPTY_ARRAY = new ProviderInfo[0];
static final ArrayFactory<ProviderInfo<?>> ARRAY_FACTORY = n -> n == 0 ? ProviderInfo.EMPTY_ARRAY : new ProviderInfo[n];
final @NotNull PsiReferenceProvider provider;
final @NotNull T processingContext;
final double priority;
public ProviderInfo(@NotNull PsiReferenceProvider provider, @NotNull Context processingContext, double priority) {
ProviderInfo(@NotNull PsiReferenceProvider provider, @NotNull T processingContext, double priority) {
this.provider = provider;
this.processingContext = processingContext;
this.priority = priority;

View File

@@ -14,9 +14,11 @@ import com.intellij.util.ArrayUtilRt;
import com.intellij.util.ProcessingContext;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ConcurrentFactoryMap;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
@@ -177,8 +179,9 @@ public class PsiReferenceRegistrarImpl extends PsiReferenceRegistrar {
}
@ApiStatus.Internal
public @NotNull List<ProviderBinding.ProviderInfo<ProcessingContext>> getPairsByElement(@NotNull PsiElement element,
@NotNull PsiReferenceService.Hints hints) {
@Unmodifiable
@NotNull List<ProviderBinding.ProviderInfo<ProcessingContext>> getPairsByElement(@NotNull PsiElement element,
@NotNull PsiReferenceService.Hints hints) {
ProviderBinding[] bindings = myBindingCache.get(element.getClass());
if (bindings.length == 0) return Collections.emptyList();
@@ -188,4 +191,10 @@ public class PsiReferenceRegistrarImpl extends PsiReferenceRegistrar {
}
return ret;
}
@ApiStatus.Internal
@Unmodifiable
public @NotNull List<PsiReferenceProvider> getPsiReferenceProvidersByElement(@NotNull PsiElement element,
@NotNull PsiReferenceService.Hints hints) {
return ContainerUtil.map(getPairsByElement(element, hints), info -> info.provider);
}
}

View File

@@ -161,17 +161,17 @@ public final class ReferenceProvidersRegistryImpl extends ReferenceProvidersRegi
private static @NotNull Double2ObjectMap<List<PsiReference[]>> mapNotEmptyReferencesFromProviders(@NotNull PsiElement context,
@NotNull List<? extends ProviderBinding.ProviderInfo<ProcessingContext>> providers) {
Double2ObjectOpenHashMap<List<PsiReference[]>> map = new Double2ObjectOpenHashMap<>();
for (ProviderBinding.ProviderInfo<ProcessingContext> trinity : providers) {
PsiReference[] refs = getReferences(context, trinity);
for (ProviderBinding.ProviderInfo<ProcessingContext> info : providers) {
PsiReference[] refs = getReferences(context, info);
if (refs.length > 0) {
List<PsiReference[]> list = map.get(trinity.priority);
List<PsiReference[]> list = map.get(info.priority);
if (list == null) {
list = new SmartList<>();
map.put(trinity.priority, list);
map.put(info.priority, list);
}
list.add(refs);
if (IdempotenceChecker.isLoggingEnabled()) {
IdempotenceChecker.logTrace(trinity.provider + " returned " + Arrays.toString(refs));
IdempotenceChecker.logTrace(info.provider + " returned " + Arrays.toString(refs));
}
}
}

View File

@@ -6,32 +6,37 @@ import com.intellij.patterns.ElementPattern;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReferenceProvider;
import com.intellij.psi.PsiReferenceService;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
class SimpleProviderBinding implements ProviderBinding {
private final List<ProviderInfo<ElementPattern<?>>> myProviderPairs = ContainerUtil.createLockFreeCopyOnWriteList();
/**
* the array must be copy-on-write to avoid data races, since it can be read concurrently, via {@link #addAcceptableReferenceProviders}
*/
volatile private @NotNull ProviderInfo<?> @NotNull [] myProviderInfos = ProviderInfo.EMPTY_ARRAY;
synchronized
void registerProvider(@NotNull PsiReferenceProvider provider, @NotNull ElementPattern<?> pattern, double priority) {
myProviderPairs.add(new ProviderInfo<>(provider, pattern, priority));
myProviderInfos = ArrayUtil.append(myProviderInfos, new ProviderInfo<>(provider, pattern, priority), ProviderInfo.ARRAY_FACTORY);
}
@Override
public void addAcceptableReferenceProviders(@NotNull PsiElement position,
@NotNull List<? super ProviderInfo<ProcessingContext>> list,
@NotNull PsiReferenceService.Hints hints) {
NamedObjectProviderBinding.addMatchingProviders(position, myProviderPairs, list, hints);
NamedObjectProviderBinding.addMatchingProviders(position, (ProviderInfo<ElementPattern<?>>[])myProviderInfos, list, hints);
}
@Override
synchronized
public void unregisterProvider(@NotNull PsiReferenceProvider provider) {
myProviderPairs.removeIf(trinity -> trinity.provider.equals(provider));
myProviderInfos = NamedObjectProviderBinding.removeFromArray(provider, myProviderInfos);
}
boolean isEmpty() {
return myProviderPairs.isEmpty();
return myProviderInfos.length == 0;
}
}

View File

@@ -2535,6 +2535,25 @@ public final class ContainerUtil {
return -1;
}
/**
* Finds the first element in the array that satisfies given condition.
*
* @param list array to scan
* @param condition condition that should be satisfied
* @param <T> type of the list elements
* @return index of the first element in the array that satisfies the condition; -1 if no element satisfies the condition.
*/
@Contract(pure=true)
public static <T> int indexOf(T @NotNull [] list, @NotNull Condition<? super T> condition) {
for (int i = 0; i < list.length; i++) {
T t = list[i];
if (condition.value(t)) {
return i;
}
}
return -1;
}
@Contract(pure=true)
public static <T> int lastIndexOf(@NotNull List<? extends T> list, @NotNull Condition<? super T> condition) {
for (int i = list.size() - 1; i >= 0; i--) {

View File

@@ -86,14 +86,11 @@ fun getReferencesForDirectUsagesOfVariable(element: PsiElement, targetHint: PsiE
val uParentVariable = getUsageVariableTargetForInitializer(uElement) ?: return PsiReference.EMPTY_ARRAY
val registrar = ReferenceProvidersRegistry.getInstance().getRegistrar(Language.findLanguageByID("UAST")!!)
val providerInfos = (registrar as PsiReferenceRegistrarImpl).getPairsByElement(originalElement,
PsiReferenceService.Hints(targetHint, null))
val providers = (registrar as PsiReferenceRegistrarImpl).getPsiReferenceProvidersByElement(originalElement,
PsiReferenceService.Hints(targetHint, null))
// by-usage providers must implement acceptsTarget correctly, we rely on fact that they accept targetHint
val suitableProviders = providerInfos.asSequence()
.map { it.provider }
.filterIsInstance<UastReferenceByUsageAdapter>()
.toList()
val suitableProviders = providers.filterIsInstance<UastReferenceByUsageAdapter>()
val usages = getDirectVariableUsagesWithNonLocal(uParentVariable)
for (usage in usages) {