mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
make NamedObjectProviderBinding thread-safe to fix EA-1138581 (plugin) - NSEE: SmartList.getFromArray
GitOrigin-RevId: 482107e14834d2df8e50ec7f893cffd6c964a996
This commit is contained in:
committed by
intellij-monorepo-bot
parent
329c890ea0
commit
794799dce9
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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--) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user