mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-inspections] IDEA-318681, IDEA-318678, IDEA-318677, IDEA-318676 Improve previews, get rid of external annotations
GitOrigin-RevId: 57d45849299ce12c5ec19d84dcabdaaee00c3831
This commit is contained in:
committed by
intellij-monorepo-bot
parent
bdfa6f8ccb
commit
5032fa91dd
-8
@@ -1,8 +0,0 @@
|
||||
// "Add 'checker-qual' to classpath" "true"
|
||||
package x;
|
||||
|
||||
public class AddCheckerFrameworkAnnotations {
|
||||
public @Ta<caret>inted String source() {
|
||||
return "unsafe";
|
||||
}
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
// "Add 'checker-qual' to classpath" "true"
|
||||
package x;
|
||||
|
||||
public class AddCheckerFrameworkAnnotations {
|
||||
@Untaint<caret>ed
|
||||
public void safe() {
|
||||
|
||||
}
|
||||
}
|
||||
-8
@@ -146,14 +146,6 @@ public class OrderEntryTest extends DaemonAnalyzerTestCase {
|
||||
doTest("A/src/x/DoTest4junit.java", false);
|
||||
}
|
||||
|
||||
public void testAddCheckerFrameworkTainted() {
|
||||
doTest("A/src/x/AddCheckerFrameworkTainted.java", false);
|
||||
}
|
||||
|
||||
public void testAddCheckerFrameworkUntainted() {
|
||||
doTest("A/src/x/AddCheckerFrameworkUntainted.java", false);
|
||||
}
|
||||
|
||||
public void testExistingJunit() {
|
||||
doTest("B/src/y/AddExistingJunit.java", true);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,6 @@
|
||||
<notificationGroup id="UAST" displayType="BALLOON" hideFromSettings="true"/>
|
||||
<projectService serviceInterface="com.intellij.codeInsight.AnnotationCacheOwnerNormalizer"
|
||||
serviceImplementation="com.intellij.psi.UastAnnotationCacheOwnerNormalizer"/>
|
||||
<codeInsight.externalLibraryResolver implementation="com.intellij.codeInspection.sourceToSink.CheckerQualExternalLibraryResolver"/>
|
||||
<inspectionCustomComponent implementation="com.intellij.codeInsight.options.JavaInspectionButtons"/>
|
||||
</extensions>
|
||||
<extensions defaultExtensionNs="com.intellij.codeInsight">
|
||||
|
||||
@@ -79,22 +79,23 @@ jvm.inspections.source.to.sink.flow.common.unknown=Unknown string is used in a s
|
||||
jvm.inspections.source.unsafe.to.sink.flow.mark.as.safe.family=Mark as requiring validation
|
||||
jvm.inspections.source.unsafe.to.sink.flow.mark.as.safe.text=Mark ''{0}'' as requiring validation
|
||||
jvm.inspections.source.unsafe.to.sink.flow.mark.as.safe.command.name=Mark as Requiring Validation
|
||||
jvm.inspections.source.unsafe.to.sink.flow.preview.multiple.files=Add ''@Untainted'' annotation in {0}
|
||||
jvm.inspections.source.unsafe.to.sink.flow.preview=Add '@Untainted' annotation
|
||||
jvm.inspections.source.unsafe.to.sink.flow.impossible=Untainted annotation is not supported for element ''{0}''
|
||||
jvm.inspections.source.unsafe.to.sink.flow.config=Untainted annotation will be added to the inspection''s setting for element ''{0}''
|
||||
jvm.inspections.source.unsafe.to.sink.flow.impossible=Untainted annotation is not supported for element ''{0}''. The element will be skipped
|
||||
jvm.inspections.source.unsafe.to.sink.flow.preview.propagate=Show propagation tree
|
||||
jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.family=Propagate safe annotation
|
||||
jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.text=Propagate safe annotation from ''{0}''
|
||||
jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.preview=Opens a tool window to configure the propagation of the safe annotation
|
||||
jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.toolwindow.title=Select Members to Annotate as Safe
|
||||
jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.family=Propagation tree
|
||||
jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.text=Show propagation tree from ''{0}''
|
||||
jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.preview=Opens a tool window to check the propagation of the safe annotation
|
||||
jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.toolwindow.title=Unsafe Members
|
||||
jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.toolwindow.annotate=Annotate All except Excluded
|
||||
jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.toolwindow.unsafe.flow=Unsafe flow
|
||||
propagated.from=Reason to mark as safe:
|
||||
propagated.to=Target to mark as safe:
|
||||
propagate.from.empty.text=Reason to mark as safe is shown here
|
||||
propagate.to.empty.text=Target to mark as safe is shown here
|
||||
propagated.from=Reason to Mark as Safe:
|
||||
propagated.to=Target to Mark as Safe:
|
||||
jvm.inspections.source.unsafe.to.sink.flow.tainted.annotations=Tainted annotations:
|
||||
jvm.inspections.source.unsafe.to.sink.flow.untainted.annotations=Untainted annotations:
|
||||
jvm.inspections.source.unsafe.to.sink.flow.untainted.methods=Untainted methods:
|
||||
jvm.inspections.source.unsafe.to.sink.flow.untainted.fields=Untainted fields:
|
||||
jvm.inspections.source.unsafe.to.sink.flow.untainted.fields.name=Fields
|
||||
|
||||
jvm.inspections.testonly.display.name=Test-only usage in production code
|
||||
jvm.inspections.testonly.class.reference=Test-only class is referenced in production code
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInspection.sourceToSink;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.ExternalLibraryResolver;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.roots.ExternalLibraryDescriptor;
|
||||
import com.intellij.util.ThreeState;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class CheckerQualExternalLibraryResolver extends ExternalLibraryResolver {
|
||||
|
||||
public static final ExternalLibraryDescriptor CHECKER_QUAL =
|
||||
new ExternalLibraryDescriptor("org.checkerframework", "checker-qual", "3.19.0", "3.19.0");
|
||||
|
||||
private static final Set<String> CHECKER_ANNOS = Set.of("Untainted", "Tainted", "PolyTainted");
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ExternalClassResolveResult resolveClass(@NotNull String shortClassName,
|
||||
@NotNull ThreeState isAnnotation,
|
||||
@NotNull Module contextModule) {
|
||||
if (isAnnotation == ThreeState.YES && CHECKER_ANNOS.contains(shortClassName)) {
|
||||
return new ExternalClassResolveResult("org.checkerframework.checker.tainting.qual." + shortClassName, CHECKER_QUAL);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+158
-96
@@ -8,18 +8,14 @@ import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
|
||||
import com.intellij.codeInspection.LocalQuickFixOnPsiElement;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.lang.jvm.JvmField;
|
||||
import com.intellij.lang.jvm.JvmMethod;
|
||||
import com.intellij.lang.jvm.JvmModifiersOwner;
|
||||
import com.intellij.lang.jvm.JvmParameter;
|
||||
import com.intellij.codeInspection.ex.InspectionProfileModifiableModelKt;
|
||||
import com.intellij.lang.jvm.*;
|
||||
import com.intellij.lang.jvm.actions.*;
|
||||
import com.intellij.lang.jvm.types.JvmType;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.command.UndoConfirmationPolicy;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.command.undo.BasicUndoableAction;
|
||||
import com.intellij.openapi.command.undo.UndoManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiAnnotation;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
@@ -38,7 +34,7 @@ import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.intellij.codeInsight.ExternalAnnotationsManager.AnnotationPlace;
|
||||
import static com.intellij.codeInspection.UntaintedAnnotationProvider.DEFAULT_UNTAINTED_ANNOTATION;
|
||||
import static com.intellij.codeInspection.sourceToSink.TaintValueFactory.UntaintedContext;
|
||||
|
||||
public class MarkAsSafeFix extends LocalQuickFixOnPsiElement {
|
||||
|
||||
@@ -80,7 +76,7 @@ public class MarkAsSafeFix extends LocalQuickFixOnPsiElement {
|
||||
if (uExpression == null) return;
|
||||
List<PsiElement> elements = getElementsToMark(uExpression);
|
||||
if (elements == null) return;
|
||||
markAsSafe(project, elements, ApplicationManager.getApplication().isHeadlessEnvironment(), myTaintValueFactory);
|
||||
markAsSafe(project, elements, myTaintValueFactory);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -102,123 +98,189 @@ public class MarkAsSafeFix extends LocalQuickFixOnPsiElement {
|
||||
PsiElement sourcePsi = uExpression.getSourcePsi();
|
||||
if (sourcePsi == null) return IntentionPreviewInfo.EMPTY;
|
||||
PsiFile file = sourcePsi.getContainingFile();
|
||||
Set<PsiFile> filesToAnnotate = ContainerUtil.map2Set(toAnnotate, e -> e.getContainingFile());
|
||||
if (ContainerUtil.exists(filesToAnnotate, f -> f != file)) {
|
||||
String fileNames = StringUtil.join(filesToAnnotate, f -> f.getName(), ", ");
|
||||
String message = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.preview.multiple.files", fileNames);
|
||||
String message = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.preview");
|
||||
if (ContainerUtil.exists(toAnnotate, e -> fileToAnnotate(e) != file)) {
|
||||
return new IntentionPreviewInfo.Html(message);
|
||||
}
|
||||
if (ContainerUtil.exists(toAnnotate, e -> e.getContainingFile() != file)) {
|
||||
return IntentionPreviewInfo.EMPTY;
|
||||
}
|
||||
ArrayList<PsiElement> ignoredElements = new ArrayList<>();
|
||||
annotateInCode(project, toAnnotate, myTaintValueFactory, true, ignoredElements);
|
||||
if (ignoredElements.isEmpty()) {
|
||||
return IntentionPreviewInfo.DIFF;
|
||||
}
|
||||
String message = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.preview");
|
||||
return new IntentionPreviewInfo.Html(message);
|
||||
}
|
||||
|
||||
public static void markAsSafe(@NotNull Project project, @NotNull Collection<PsiElement> toAnnotate, boolean isHeadlessMode,
|
||||
public static void markAsSafe(@NotNull Project project, @NotNull Collection<PsiElement> toAnnotate,
|
||||
@NotNull TaintValueFactory taintValueFactory) {
|
||||
AnnotationPlace place = getPlace(project, toAnnotate);
|
||||
if (place == AnnotationPlace.NEED_ASK_USER && !isHeadlessMode) {
|
||||
PsiModifierListOwner first = ContainerUtil.findInstance(toAnnotate, PsiModifierListOwner.class);
|
||||
if (first == null) return;
|
||||
ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(project);
|
||||
place = annotationsManager.chooseAnnotationsPlace(first);
|
||||
}
|
||||
if (place != AnnotationPlace.EXTERNAL && place != AnnotationPlace.IN_CODE) return;
|
||||
boolean annotateExternally = place == AnnotationPlace.EXTERNAL;
|
||||
annotate(project, toAnnotate, annotateExternally, taintValueFactory);
|
||||
Map<PsiElement, AnnotationPlace> placedElements = getPlace(project, toAnnotate);
|
||||
annotate(project, placedElements, taintValueFactory);
|
||||
}
|
||||
|
||||
private static @Nullable AnnotationPlace getPlace(Project project, @NotNull Collection<PsiElement> toAnnotate) {
|
||||
private static @NotNull Map<PsiElement, AnnotationPlace> getPlace(Project project, @NotNull Collection<PsiElement> toAnnotate) {
|
||||
ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(project);
|
||||
return toAnnotate.stream().reduce(null,
|
||||
(acc, e) -> acc == AnnotationPlace.NOWHERE
|
||||
? acc
|
||||
: join(acc, annotationsManager.chooseAnnotationsPlaceNoUi(e)),
|
||||
MarkAsSafeFix::join);
|
||||
return StreamEx.of(toAnnotate).toMap(e -> e, e -> annotationsManager.chooseAnnotationsPlaceNoUi(e));
|
||||
}
|
||||
|
||||
private static AnnotationPlace join(@Nullable AnnotationPlace acc, @Nullable AnnotationPlace place) {
|
||||
if (place == acc) return place;
|
||||
if (acc == null || place == AnnotationPlace.NOWHERE) return place;
|
||||
if (place == AnnotationPlace.EXTERNAL && acc == AnnotationPlace.IN_CODE ||
|
||||
place == AnnotationPlace.IN_CODE && acc == AnnotationPlace.EXTERNAL) {
|
||||
return AnnotationPlace.EXTERNAL;
|
||||
}
|
||||
return AnnotationPlace.NEED_ASK_USER;
|
||||
}
|
||||
|
||||
private static void annotate(@NotNull Project project, @NotNull Collection<PsiElement> toAnnotate, boolean annotateExternally,
|
||||
private static void annotate(@NotNull Project project,
|
||||
@NotNull Map<PsiElement, AnnotationPlace> placedElement,
|
||||
@NotNull TaintValueFactory taintValueFactory) {
|
||||
String title = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.mark.as.safe.command.name");
|
||||
if (annotateExternally) {
|
||||
CommandProcessor.getInstance().executeCommand(project, () -> annotateExternally(project, toAnnotate),
|
||||
title, null, UndoConfirmationPolicy.DO_NOT_REQUEST_CONFIRMATION);
|
||||
}
|
||||
else {
|
||||
ArrayList<PsiElement> ignoredElements = new ArrayList<>();
|
||||
annotateInCode(project, toAnnotate, taintValueFactory, false, ignoredElements);
|
||||
if (!ignoredElements.isEmpty()) {
|
||||
MultiMap<PsiElement, String> problems = new MultiMap<>(ignoredElements.size());
|
||||
for (PsiElement element : ignoredElements) {
|
||||
problems.put(element,
|
||||
List.of(JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.impossible", element.getText())));
|
||||
MultiMap<PsiElement, String> problems = new MultiMap<>();
|
||||
ArrayList<PsiElement> toAnnotate = new ArrayList<>();
|
||||
for (Map.Entry<PsiElement, AnnotationPlace> entry : placedElement.entrySet()) {
|
||||
PsiElement element = entry.getKey();
|
||||
AnnotationPlace annotationPlace = entry.getValue();
|
||||
if (annotationPlace != AnnotationPlace.IN_CODE) {
|
||||
String message;
|
||||
if (element instanceof JvmField || element instanceof JvmMethod) {
|
||||
message = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.config", getRepresentText(element));
|
||||
}
|
||||
ConflictsDialog conflictsDialog = new ConflictsDialog(project, problems);
|
||||
if (!conflictsDialog.showAndGet()) {
|
||||
return;
|
||||
else {
|
||||
message = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.impossible", getRepresentText(element));
|
||||
}
|
||||
problems.put(element, List.of(message));
|
||||
}
|
||||
else {
|
||||
toAnnotate.add(element);
|
||||
}
|
||||
}
|
||||
ArrayList<PsiElement> ignoredElements = new ArrayList<>();
|
||||
annotateInCode(project, toAnnotate, taintValueFactory, false, ignoredElements);
|
||||
for (PsiElement element : ignoredElements) {
|
||||
if (element instanceof JvmField || element instanceof JvmMethod) {
|
||||
placedElement.put(element, AnnotationPlace.EXTERNAL);
|
||||
String message = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.config", getRepresentText(element));
|
||||
problems.put(element, List.of(message));
|
||||
}
|
||||
else {
|
||||
String message = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.impossible", getRepresentText(element));
|
||||
problems.put(element, List.of(message));
|
||||
}
|
||||
}
|
||||
if (!problems.isEmpty()) {
|
||||
ConflictsDialog conflictsDialog = new ConflictsDialog(project, problems);
|
||||
if (!conflictsDialog.showAndGet()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
run(project, placedElement, taintValueFactory);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getRepresentText(@NotNull PsiElement element) {
|
||||
if (element instanceof JvmNamedElement jvmNamedElement && jvmNamedElement.getName() != null) {
|
||||
return jvmNamedElement.getName();
|
||||
}
|
||||
return element.getText();
|
||||
}
|
||||
|
||||
private static void run(@NotNull Project project,
|
||||
@NotNull Map<PsiElement, AnnotationPlace> placedElement,
|
||||
@NotNull TaintValueFactory taintValueFactory) {
|
||||
String title = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.mark.as.safe.command.name");
|
||||
ArrayList<PsiElement> toAnnotate = new ArrayList<>();
|
||||
ArrayList<PsiElement> toConfig = new ArrayList<>();
|
||||
for (Map.Entry<PsiElement, AnnotationPlace> entry : placedElement.entrySet()) {
|
||||
AnnotationPlace annotationPlace = entry.getValue();
|
||||
if (annotationPlace == AnnotationPlace.IN_CODE) {
|
||||
toAnnotate.add(entry.getKey());
|
||||
}
|
||||
if (annotationPlace == AnnotationPlace.EXTERNAL || annotationPlace == AnnotationPlace.NEED_ASK_USER) {
|
||||
toConfig.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
PsiFile[] files = filesToAnnotate(toAnnotate);
|
||||
WriteCommandAction.runWriteCommandAction(project, title, null, () -> {
|
||||
annotateInCode(project, toAnnotate, taintValueFactory, true, new ArrayList<>());
|
||||
addToConfig(project, toConfig, taintValueFactory.getContext());
|
||||
}, files);
|
||||
}
|
||||
|
||||
private static void addToConfig(@NotNull Project project, @NotNull List<PsiElement> config, @NotNull UntaintedContext context) {
|
||||
UntaintedContext previousContext = context.copy();
|
||||
InspectionProfileModifiableModelKt.modifyAndCommitProjectProfile(project, model -> {
|
||||
for (PsiElement element : config) {
|
||||
if (element instanceof JvmMethod jvmMethod) {
|
||||
JvmClass containingClass = jvmMethod.getContainingClass();
|
||||
if (containingClass == null) continue;
|
||||
context.methodClass().add(containingClass.getQualifiedName());
|
||||
context.methodPatterns().add(jvmMethod.getName());
|
||||
}
|
||||
if (element instanceof JvmField jvmField) {
|
||||
JvmClass containingClass = jvmField.getContainingClass();
|
||||
if (containingClass == null) continue;
|
||||
context.fieldClass().add(containingClass.getQualifiedName());
|
||||
context.fieldPatterns().add(jvmField.getName());
|
||||
}
|
||||
}
|
||||
PsiFile[] files = filesToAnnotate(toAnnotate);
|
||||
WriteCommandAction.runWriteCommandAction(project, title, null, () -> {
|
||||
annotateInCode(project, toAnnotate, taintValueFactory, true, new ArrayList<>());
|
||||
}, files);
|
||||
});
|
||||
|
||||
UntaintedContext newContext = context.copy();
|
||||
|
||||
UndoManager.getInstance(project).undoableActionPerformed(new BasicUndoableAction() {
|
||||
@Override
|
||||
public void undo() {
|
||||
InspectionProfileModifiableModelKt.modifyAndCommitProjectProfile(project, model -> {
|
||||
copyContext(context, previousContext);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void redo() {
|
||||
InspectionProfileModifiableModelKt.modifyAndCommitProjectProfile(project, model -> {
|
||||
copyContext(context, newContext);
|
||||
});
|
||||
}
|
||||
|
||||
private static void copyContext(@NotNull UntaintedContext context, UntaintedContext newContext) {
|
||||
context.methodPatterns().clear();
|
||||
context.methodClass().clear();
|
||||
context.fieldClass().clear();
|
||||
context.fieldPatterns().clear();
|
||||
context.methodPatterns().addAll(newContext.methodPatterns());
|
||||
context.methodClass().addAll(newContext.methodClass());
|
||||
context.fieldPatterns().addAll(newContext.fieldPatterns());
|
||||
context.fieldClass().addAll(newContext.fieldClass());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static PsiElement getSourcePsi(@NotNull PsiElement element) {
|
||||
if (element.isPhysical()) {
|
||||
return element;
|
||||
}
|
||||
// It is possible that some elements are non-physical (e.g. when we resolved kotlin reference in java file we get light element).
|
||||
// In such cases we want to get the original physical file from this non-physical element so that we can add annotation later on.
|
||||
// The simplest way to do it is to make two conversions: non-physical element -> uast element -> source psi
|
||||
UElement uElement = UastContextKt.toUElement(element);
|
||||
if (uElement == null) return null;
|
||||
PsiElement sourcePsi = uElement.getSourcePsi();
|
||||
if (sourcePsi == null) {
|
||||
SourceToSinkProvider provider = SourceToSinkProvider.sourceToSinkLanguageProvider.forLanguage(element.getLanguage());
|
||||
if (provider == null) return null;
|
||||
sourcePsi = provider.getPhysicalForLightElement(element);
|
||||
}
|
||||
return sourcePsi;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiFile fileToAnnotate(@NotNull PsiElement element) {
|
||||
PsiElement sourcePsi = getSourcePsi(element);
|
||||
if (sourcePsi == null) return null;
|
||||
return sourcePsi.getContainingFile();
|
||||
}
|
||||
|
||||
private static PsiFile[] filesToAnnotate(@NotNull Collection<PsiElement> elements) {
|
||||
Set<PsiFile> files = new HashSet<>();
|
||||
for (PsiElement element : elements) {
|
||||
if (element.isPhysical()) {
|
||||
files.add(element.getContainingFile());
|
||||
continue;
|
||||
PsiFile psiFile = fileToAnnotate(element);
|
||||
if (psiFile != null) {
|
||||
files.add(psiFile);
|
||||
}
|
||||
// It is possible that some elements are non-physical (e.g. when we resolved kotlin reference in java file we get light element).
|
||||
// In such cases we want to get the original physical file from this non-physical element so that we can add annotation later on.
|
||||
// The simplest way to do it is to make two conversions: non-physical element -> uast element -> source psi
|
||||
UElement uElement = UastContextKt.toUElement(element);
|
||||
if (uElement == null) continue;
|
||||
PsiElement sourcePsi = uElement.getSourcePsi();
|
||||
if (sourcePsi == null) {
|
||||
SourceToSinkProvider provider = SourceToSinkProvider.sourceToSinkLanguageProvider.forLanguage(element.getLanguage());
|
||||
if (provider == null) continue;
|
||||
sourcePsi = provider.getPhysicalForLightElement(element);
|
||||
if (sourcePsi == null) continue;
|
||||
}
|
||||
files.add(sourcePsi.getContainingFile());
|
||||
}
|
||||
return files.toArray(PsiFile.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
private static void annotateExternally(@NotNull Project project, @NotNull Collection<PsiElement> toAnnotate) {
|
||||
ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(project);
|
||||
for (PsiElement element : toAnnotate) {
|
||||
PsiModifierListOwner owner = ObjectUtils.tryCast(element, PsiModifierListOwner.class);
|
||||
if (owner == null) continue;
|
||||
try {
|
||||
annotationsManager.annotateExternally(owner, DEFAULT_UNTAINTED_ANNOTATION, owner.getContainingFile(), null);
|
||||
}
|
||||
catch (ExternalAnnotationsManager.CanceledConfigurationException ignored) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void annotateInCode(@NotNull Project project, @NotNull Collection<PsiElement> toAnnotate,
|
||||
@NotNull TaintValueFactory taintValueFactory,
|
||||
boolean makeAction,
|
||||
|
||||
+61
-116
@@ -5,14 +5,10 @@ import com.intellij.analysis.JvmAnalysisBundle;
|
||||
import com.intellij.analysis.problemsView.toolWindow.ProblemsView;
|
||||
import com.intellij.codeInsight.highlighting.HighlightManager;
|
||||
import com.intellij.ide.highlighter.HighlighterFactory;
|
||||
import com.intellij.ide.util.PsiClassRenderingInfo;
|
||||
import com.intellij.ide.util.PsiElementRenderingInfo;
|
||||
import com.intellij.ide.util.PsiMethodRenderingInfo;
|
||||
import com.intellij.java.refactoring.JavaRefactoringBundle;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.editor.*;
|
||||
import com.intellij.openapi.editor.colors.EditorColors;
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager;
|
||||
@@ -22,29 +18,32 @@ import com.intellij.openapi.editor.highlighter.EditorHighlighter;
|
||||
import com.intellij.openapi.editor.markup.RangeHighlighter;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Splitter;
|
||||
import com.intellij.openapi.util.*;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.NlsActions;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.wm.ToolWindow;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
import com.intellij.psi.util.PsiFormatUtilBase;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiNameIdentifierOwner;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.ui.*;
|
||||
import com.intellij.ui.IdeBorderFactory;
|
||||
import com.intellij.ui.PopupHandler;
|
||||
import com.intellij.ui.ScrollPaneFactory;
|
||||
import com.intellij.ui.border.IdeaTitledBorder;
|
||||
import com.intellij.ui.content.Content;
|
||||
import com.intellij.ui.content.ContentManager;
|
||||
import com.intellij.ui.tree.AsyncTreeModel;
|
||||
import com.intellij.ui.tree.BaseTreeModel;
|
||||
import com.intellij.ui.treeStructure.Tree;
|
||||
import com.intellij.usageView.UsageViewBundle;
|
||||
import com.intellij.util.Alarm;
|
||||
import com.intellij.util.EditSourceOnDoubleClickHandler;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.concurrency.Invoker;
|
||||
import com.intellij.util.concurrency.InvokerSupplier;
|
||||
import com.intellij.util.ui.NamedColorUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.util.ui.tree.TreeUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
@@ -69,6 +68,8 @@ import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.intellij.openapi.actionSystem.PlatformCoreDataKeys.BGT_DATA_PROVIDER;
|
||||
|
||||
public class PropagateAnnotationPanel extends JPanel implements Disposable {
|
||||
|
||||
private final Tree myTree;
|
||||
@@ -78,17 +79,22 @@ public class PropagateAnnotationPanel extends JPanel implements Disposable {
|
||||
private final PropagateTreeListener myTreeSelectionListener;
|
||||
private final @NotNull Consumer<? super Collection<@NotNull TaintNode>> myCallback;
|
||||
private Content myContent;
|
||||
private final boolean mySupportRefactoring;
|
||||
|
||||
PropagateAnnotationPanel(@NotNull Project project, @NotNull TaintNode root, @NotNull Consumer<? super Collection<@NotNull TaintNode>> callback) {
|
||||
PropagateAnnotationPanel(@NotNull Project project,
|
||||
@NotNull TaintNode root,
|
||||
@NotNull Consumer<? super Collection<@NotNull TaintNode>> callback,
|
||||
boolean supportRefactoring) {
|
||||
super(new BorderLayout());
|
||||
myTree = PropagateTree.create(this, root);
|
||||
myRoot = root;
|
||||
myProject = project;
|
||||
myCallback = callback;
|
||||
mySupportRefactoring = supportRefactoring;
|
||||
|
||||
Editor usageEditor = createEditor();
|
||||
Editor memberEditor = createEditor();
|
||||
myTreeSelectionListener = new PropagateTreeListener(usageEditor, memberEditor, myRoot);
|
||||
myTreeSelectionListener = new PropagateTreeListener(usageEditor, memberEditor);
|
||||
myTree.getSelectionModel().addTreeSelectionListener(myTreeSelectionListener);
|
||||
|
||||
Splitter splitter = new Splitter(false, .6f);
|
||||
@@ -115,21 +121,23 @@ public class PropagateAnnotationPanel extends JPanel implements Disposable {
|
||||
|
||||
private @NotNull JPanel createToolbar() {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
String annotateText = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.toolwindow.annotate");
|
||||
JButton annotateButton = new JButton(annotateText);
|
||||
annotateButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ToolWindow toolWindow = ProblemsView.getToolWindow(myProject);
|
||||
if (toolWindow == null) return;
|
||||
ContentManager contentManager = toolWindow.getContentManager();
|
||||
contentManager.removeContent(myContent, true);
|
||||
myContent.release();
|
||||
Set<TaintNode> toAnnotate = getSelectedElements(myRoot, new HashSet<>());
|
||||
if (toAnnotate != null) myCallback.accept(toAnnotate);
|
||||
}
|
||||
});
|
||||
panel.add(annotateButton, BorderLayout.WEST);
|
||||
if (mySupportRefactoring) {
|
||||
String annotateText = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.toolwindow.annotate");
|
||||
JButton annotateButton = new JButton(annotateText);
|
||||
annotateButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
ToolWindow toolWindow = ProblemsView.getToolWindow(myProject);
|
||||
if (toolWindow == null) return;
|
||||
ContentManager contentManager = toolWindow.getContentManager();
|
||||
contentManager.removeContent(myContent, true);
|
||||
myContent.release();
|
||||
Set<TaintNode> toAnnotate = getSelectedElements(myRoot, new HashSet<>());
|
||||
if (toAnnotate != null) myCallback.accept(toAnnotate);
|
||||
}
|
||||
});
|
||||
panel.add(annotateButton, BorderLayout.WEST);
|
||||
}
|
||||
return panel;
|
||||
}
|
||||
|
||||
@@ -182,9 +190,9 @@ public class PropagateAnnotationPanel extends JPanel implements Disposable {
|
||||
return memberComponent;
|
||||
}
|
||||
|
||||
private static void addTreeActions(@NotNull Tree tree, @NotNull TaintNode root) {
|
||||
private void addTreeActions(@NotNull Tree tree, @NotNull TaintNode root) {
|
||||
DefaultActionGroup actionGroup = new DefaultActionGroup();
|
||||
if (root.myTaintValue != TaintValue.TAINTED) {
|
||||
if (root.myTaintValue != TaintValue.TAINTED && mySupportRefactoring) {
|
||||
actionGroup.addAll(createIncludeExcludeActions(tree));
|
||||
}
|
||||
actionGroup.add(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE));
|
||||
@@ -253,15 +261,12 @@ public class PropagateAnnotationPanel extends JPanel implements Disposable {
|
||||
|
||||
private final ElementEditor myUsageEditor;
|
||||
private final ElementEditor myMemberEditor;
|
||||
private final TaintNode myRoot;
|
||||
private final Alarm myAlarm = new Alarm();
|
||||
|
||||
private PropagateTreeListener(@NotNull Editor usageEditor,
|
||||
@NotNull Editor memberEditor,
|
||||
@NotNull TaintNode root) {
|
||||
myUsageEditor = new ElementEditor(usageEditor, "propagate.from.empty.text");
|
||||
myMemberEditor = new ElementEditor(memberEditor, "propagate.to.empty.text");
|
||||
myRoot = root;
|
||||
@NotNull Editor memberEditor) {
|
||||
myUsageEditor = new ElementEditor(usageEditor);
|
||||
myMemberEditor = new ElementEditor(memberEditor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -274,12 +279,12 @@ public class PropagateAnnotationPanel extends JPanel implements Disposable {
|
||||
}
|
||||
|
||||
void updateEditorTexts(@NotNull TaintNode taintNode) {
|
||||
if (taintNode == myRoot || taintNode.getParentDescriptor() == null) {
|
||||
myUsageEditor.show(null, null);
|
||||
myMemberEditor.show(null, null);
|
||||
return;
|
||||
}
|
||||
//clear all
|
||||
myUsageEditor.show(null, null);
|
||||
myMemberEditor.show(null, null);
|
||||
|
||||
PsiElement usage = taintNode.getRef();
|
||||
usage = MarkAsSafeFix.getSourcePsi(usage);
|
||||
if (usage == null) return;
|
||||
PsiElement parentPsi = getParentPsi(usage);
|
||||
if (parentPsi == null) return;
|
||||
@@ -289,6 +294,8 @@ public class PropagateAnnotationPanel extends JPanel implements Disposable {
|
||||
|
||||
PsiElement element = taintNode.getPsiElement();
|
||||
if (element == null) return;
|
||||
element = MarkAsSafeFix.getSourcePsi(element);
|
||||
if (element == null) return;
|
||||
PsiElement elementHighlight = getIdentifier(element);
|
||||
if (elementHighlight == null) return;
|
||||
myMemberEditor.show(element, elementHighlight);
|
||||
@@ -318,17 +325,14 @@ public class PropagateAnnotationPanel extends JPanel implements Disposable {
|
||||
|
||||
private final Editor myEditor;
|
||||
private final Collection<RangeHighlighter> myHighlighters = new ArrayList<>();
|
||||
private final String myEmptyText;
|
||||
|
||||
private ElementEditor(Editor editor, String emptyText) {
|
||||
private ElementEditor(Editor editor) {
|
||||
myEditor = editor;
|
||||
myEmptyText = emptyText;
|
||||
}
|
||||
|
||||
public void show(@Nullable PsiElement element, @Nullable PsiElement toHighlight) {
|
||||
if (element == null || toHighlight == null) {
|
||||
String text = JvmAnalysisBundle.message(myEmptyText);
|
||||
ApplicationManager.getApplication().runWriteAction(() -> myEditor.getDocument().setText(text));
|
||||
ApplicationManager.getApplication().runWriteAction(() -> myEditor.getDocument().setText(""));
|
||||
return;
|
||||
}
|
||||
ElementModel model = ElementModel.create(element, toHighlight);
|
||||
@@ -419,6 +423,13 @@ public class PropagateAnnotationPanel extends JPanel implements Disposable {
|
||||
|
||||
@Override
|
||||
public @Nullable Object getData(@NotNull String dataId) {
|
||||
if (BGT_DATA_PROVIDER.is(dataId)) {
|
||||
return (DataProvider)slowId -> getSlowData(slowId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable Object getSlowData(@NotNull String dataId) {
|
||||
if (!CommonDataKeys.PSI_ELEMENT.is(dataId)) return null;
|
||||
TaintNode[] selectedNodes = getSelectedNodes(TaintNode.class, null);
|
||||
if (selectedNodes.length != 1) return null;
|
||||
@@ -438,18 +449,17 @@ public class PropagateAnnotationPanel extends JPanel implements Disposable {
|
||||
PropagateTree tree = new PropagateTree(treeModel);
|
||||
tree.setRootVisible(false);
|
||||
tree.setShowsRootHandles(true);
|
||||
tree.setCellRenderer(new PropagateTree.PropagateTreeRenderer());
|
||||
TreeUtil.installActions(tree);
|
||||
EditSourceOnDoubleClickHandler.install(tree);
|
||||
return tree;
|
||||
}
|
||||
|
||||
private static class PropagateTreeModel extends BaseTreeModel<TaintNode> implements InvokerSupplier {
|
||||
|
||||
|
||||
private final TaintNode myRootWrapper;
|
||||
|
||||
private PropagateTreeModel(TaintNode wrapper) {
|
||||
myRootWrapper = wrapper;
|
||||
private PropagateTreeModel(TaintNode wrapper) {
|
||||
myRootWrapper = wrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -469,70 +479,5 @@ public class PropagateAnnotationPanel extends JPanel implements Disposable {
|
||||
return Invoker.forBackgroundThreadWithReadAction(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PropagateTreeRenderer extends ColoredTreeCellRenderer {
|
||||
|
||||
@Override
|
||||
public void customizeCellRenderer(@NotNull JTree tree,
|
||||
Object value,
|
||||
boolean selected,
|
||||
boolean expanded,
|
||||
boolean leaf,
|
||||
int row,
|
||||
boolean hasFocus) {
|
||||
TaintNode taintNode = ObjectUtils.tryCast(value, TaintNode.class);
|
||||
if (taintNode == null) return;
|
||||
PsiElement psiElement = taintNode.getPsiElement();
|
||||
if (psiElement == null) {
|
||||
append(UsageViewBundle.message("node.invalid"), SimpleTextAttributes.ERROR_ATTRIBUTES);
|
||||
return;
|
||||
}
|
||||
appendPsiElement(psiElement, taintNode);
|
||||
if (!taintNode.isTaintFlowRoot) return;
|
||||
String unsafeFlow = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.toolwindow.unsafe.flow");
|
||||
SimpleTextAttributes attributes = new SimpleTextAttributes(SimpleTextAttributes.STYLE_ITALIC, UIUtil.getLabelInfoForeground());
|
||||
append(unsafeFlow, attributes);
|
||||
}
|
||||
|
||||
private void appendPsiElement(@NotNull PsiElement psiElement, @NotNull TaintNode taintNode) {
|
||||
int flags = Iconable.ICON_FLAG_VISIBILITY | Iconable.ICON_FLAG_READ_STATUS;
|
||||
setIcon(ReadAction.compute(() -> psiElement.getIcon(flags)));
|
||||
int style = taintNode.isExcluded() ? SimpleTextAttributes.STYLE_STRIKEOUT : SimpleTextAttributes.STYLE_PLAIN;
|
||||
Color color;
|
||||
color = taintNode.myTaintValue == TaintValue.TAINTED ? NamedColorUtil.getErrorForeground() : null;
|
||||
SimpleTextAttributes attributes = new SimpleTextAttributes(style, color);
|
||||
PsiMethod psiMethod = ObjectUtils.tryCast(psiElement, PsiMethod.class);
|
||||
if (psiMethod != null) {
|
||||
PsiMethodRenderingInfo renderingInfo = new PsiMethodRenderingInfo(true);
|
||||
String text = renderingInfo.getPresentableText(psiMethod);
|
||||
append(text, attributes);
|
||||
return;
|
||||
}
|
||||
PsiVariable psiVariable = ObjectUtils.tryCast(psiElement, PsiVariable.class);
|
||||
if (psiVariable != null) {
|
||||
String varText =
|
||||
PsiFormatUtil.formatVariable(psiVariable, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_TYPE, PsiSubstitutor.EMPTY);
|
||||
append(varText, attributes);
|
||||
PsiNameIdentifierOwner parent = PsiTreeUtil.getParentOfType(psiVariable, PsiClass.class, PsiMethod.class);
|
||||
Color placeColor = attributes.getFgColor();
|
||||
if (placeColor == null) placeColor = UIUtil.getLabelInfoForeground();
|
||||
SimpleTextAttributes placeAttribute = new SimpleTextAttributes(SimpleTextAttributes.STYLE_ITALIC, placeColor);
|
||||
if (parent instanceof PsiMethod) {
|
||||
PsiMethodRenderingInfo renderingInfo = new PsiMethodRenderingInfo(true);
|
||||
append(": " + renderingInfo.getPresentableText((PsiMethod)parent), placeAttribute);
|
||||
}
|
||||
else if (parent instanceof PsiClass) {
|
||||
PsiElementRenderingInfo<PsiClass> renderingInfo = PsiClassRenderingInfo.INSTANCE;
|
||||
append(": " + renderingInfo.getPresentableText((PsiClass)parent), placeAttribute);
|
||||
}
|
||||
return;
|
||||
}
|
||||
PsiNamedElement namedElement = ObjectUtils.tryCast(psiElement, PsiNamedElement.class);
|
||||
if (namedElement == null) return;
|
||||
String name = namedElement.getName();
|
||||
if (name == null) return;
|
||||
append(name, attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-6
@@ -35,12 +35,16 @@ public class PropagateFix extends LocalQuickFixAndIntentionActionOnPsiElement {
|
||||
@NotNull
|
||||
private final TaintValueFactory myTaintValueFactory;
|
||||
|
||||
private final boolean supportRefactoring;
|
||||
|
||||
public PropagateFix(@NotNull PsiElement psiElement,
|
||||
@NotNull String name,
|
||||
@NotNull TaintValueFactory taintValueFactory) {
|
||||
@NotNull TaintValueFactory taintValueFactory,
|
||||
boolean supportRefactoring) {
|
||||
super(psiElement);
|
||||
myName = name;
|
||||
myTaintValueFactory = taintValueFactory;
|
||||
this.supportRefactoring = supportRefactoring;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,15 +80,15 @@ public class PropagateFix extends LocalQuickFixAndIntentionActionOnPsiElement {
|
||||
Set<TaintNode> toAnnotate = new HashSet<>();
|
||||
toAnnotate = PropagateAnnotationPanel.getSelectedElements(root, toAnnotate);
|
||||
if (toAnnotate == null || root.myTaintValue == TaintValue.TAINTED) return;
|
||||
annotate(project, toAnnotate, true);
|
||||
annotate(project, toAnnotate);
|
||||
return;
|
||||
}
|
||||
Consumer<Collection<TaintNode>> callback = toAnnotate -> {
|
||||
annotate(project, toAnnotate, false);
|
||||
annotate(project, toAnnotate);
|
||||
ToolWindow toolWindow = ProblemsViewToolWindowUtils.INSTANCE.getToolWindow(project);
|
||||
if (toolWindow != null) toolWindow.hide();
|
||||
};
|
||||
PropagateAnnotationPanel panel = new PropagateAnnotationPanel(project, root, callback);
|
||||
PropagateAnnotationPanel panel = new PropagateAnnotationPanel(project, root, callback, supportRefactoring);
|
||||
String title = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.toolwindow.title");
|
||||
ToolWindow toolWindow = ProblemsViewToolWindowUtils.INSTANCE.getToolWindow(project);
|
||||
if (toolWindow == null) return;
|
||||
@@ -113,11 +117,11 @@ public class PropagateFix extends LocalQuickFixAndIntentionActionOnPsiElement {
|
||||
return JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.family");
|
||||
}
|
||||
|
||||
private void annotate(@NotNull Project project, @NotNull Collection<TaintNode> toAnnotate, boolean isHeadlessMode) {
|
||||
private void annotate(@NotNull Project project, @NotNull Collection<TaintNode> toAnnotate) {
|
||||
List<TaintNode> nonMarkedNodes = ContainerUtil.filter(toAnnotate, this::isNonMarked);
|
||||
Set<PsiElement> psiElements = getPsiElements(nonMarkedNodes);
|
||||
if (psiElements == null) return;
|
||||
MarkAsSafeFix.markAsSafe(project, psiElements, isHeadlessMode, this.myTaintValueFactory);
|
||||
MarkAsSafeFix.markAsSafe(project, psiElements, this.myTaintValueFactory);
|
||||
}
|
||||
|
||||
private boolean isNonMarked(@NotNull TaintNode taintNode) {
|
||||
|
||||
+42
-5
@@ -9,9 +9,14 @@ import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.codeInspection.restriction.AnnotationContext;
|
||||
import com.intellij.codeInspection.restriction.StringFlowUtil;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.psiutils.MethodMatcher;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.uast.*;
|
||||
@@ -20,6 +25,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.intellij.codeInspection.sourceToSink.TaintValueFactory.*;
|
||||
|
||||
public class SourceToSinkFlowInspection extends AbstractBaseJavaLocalInspectionTool {
|
||||
|
||||
public List<String>
|
||||
@@ -27,6 +34,10 @@ public class SourceToSinkFlowInspection extends AbstractBaseJavaLocalInspectionT
|
||||
public List<String>
|
||||
untaintedAnnotations = new ArrayList<>(List.of("javax.annotation.Untainted", "org.checkerframework.checker.tainting.qual.Untainted"));
|
||||
|
||||
public final MethodMatcher myUntaintedMethodMatcher = new MethodMatcher().finishDefault();
|
||||
public final List<String> myUntaintedFieldClasses = new ArrayList<>();
|
||||
public final List<String> myUntaintedFieldNames = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return OptPane.pane(
|
||||
@@ -37,8 +48,16 @@ public class SourceToSinkFlowInspection extends AbstractBaseJavaLocalInspectionT
|
||||
OptPane.stringList("untaintedAnnotations",
|
||||
JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.untainted.annotations"),
|
||||
new JavaClassValidator().annotationsOnly()
|
||||
)
|
||||
);
|
||||
),
|
||||
myUntaintedMethodMatcher.getTable(JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.untainted.methods"))
|
||||
.prefix("myUntaintedMethodMatcher"),
|
||||
OptPane.table(JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.untainted.fields"),
|
||||
OptPane.column("myUntaintedFieldClasses",
|
||||
InspectionGadgetsBundle.message("result.of.method.call.ignored.class.column.title"),
|
||||
new JavaClassValidator()),
|
||||
OptPane.column("myUntaintedFieldNames",
|
||||
JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.untainted.fields.name"))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,8 +70,13 @@ public class SourceToSinkFlowInspection extends AbstractBaseJavaLocalInspectionT
|
||||
return PsiElementVisitor.EMPTY_VISITOR;
|
||||
}
|
||||
|
||||
TaintValueFactory factory =
|
||||
new TaintValueFactory(taintedAnnotations, untaintedAnnotations, firstAnnotation.orElse(null));
|
||||
UntaintedContext context =
|
||||
new UntaintedContext(taintedAnnotations, untaintedAnnotations, firstAnnotation.orElse(null),
|
||||
myUntaintedMethodMatcher.getClassNames(), myUntaintedMethodMatcher.getMethodNamePatterns(),
|
||||
myUntaintedFieldClasses, myUntaintedFieldNames);
|
||||
|
||||
|
||||
TaintValueFactory factory = new TaintValueFactory(context);
|
||||
return new PsiElementVisitor() {
|
||||
@Override
|
||||
public void visitElement(@NotNull PsiElement element) {
|
||||
@@ -73,7 +97,8 @@ public class SourceToSinkFlowInspection extends AbstractBaseJavaLocalInspectionT
|
||||
if (taintValue == TaintValue.UNKNOWN) {
|
||||
String name = getName((UResolvable)uExpression);
|
||||
if (name != null) {
|
||||
fixes = new LocalQuickFix[]{new MarkAsSafeFix(element, name, factory), new PropagateFix(element, name, factory)};
|
||||
fixes = new LocalQuickFix[]{new MarkAsSafeFix(element, name, factory),
|
||||
new PropagateFix(element, name, factory, true)};
|
||||
}
|
||||
}
|
||||
holder.registerProblem(element, errorMessage, fixes);
|
||||
@@ -86,6 +111,18 @@ public class SourceToSinkFlowInspection extends AbstractBaseJavaLocalInspectionT
|
||||
return "tainting";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSettings(@NotNull Element element) throws InvalidDataException {
|
||||
super.readSettings(element);
|
||||
myUntaintedMethodMatcher.readSettings(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element element) throws WriteExternalException {
|
||||
super.writeSettings(element);
|
||||
myUntaintedMethodMatcher.writeSettings(element);
|
||||
}
|
||||
|
||||
private static @Nullable String getName(@NotNull UResolvable uExpression) {
|
||||
PsiNamedElement namedElement = ObjectUtils.tryCast(uExpression.resolve(), PsiNamedElement.class);
|
||||
return namedElement == null ? null : namedElement.getName();
|
||||
|
||||
+3
-1
@@ -82,7 +82,9 @@ public class TaintAnalyzer {
|
||||
return codeBlock == null ? TaintValue.UNTAINTED : analyze(taintValue, codeBlock, psiVariable);
|
||||
}
|
||||
|
||||
private @NotNull TaintValue analyze(@NotNull TaintValue taintValue, @NotNull UBlockExpression codeBlock, @NotNull PsiVariable psiVariable) {
|
||||
private @NotNull TaintValue analyze(@NotNull TaintValue taintValue,
|
||||
@NotNull UBlockExpression codeBlock,
|
||||
@NotNull PsiVariable psiVariable) {
|
||||
class VarAnalyzer extends AbstractUastVisitor {
|
||||
private TaintValue myTaintValue;
|
||||
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.sourceToSink;
|
||||
|
||||
import com.intellij.analysis.JvmAnalysisBundle;
|
||||
import com.intellij.ide.projectView.PresentationData;
|
||||
import com.intellij.ide.util.PsiClassRenderingInfo;
|
||||
import com.intellij.ide.util.PsiElementRenderingInfo;
|
||||
import com.intellij.ide.util.PsiMethodRenderingInfo;
|
||||
import com.intellij.ide.util.treeView.PresentableNodeDescriptor;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.SmartPointerManager;
|
||||
import com.intellij.psi.SmartPsiElementPointer;
|
||||
import com.intellij.openapi.util.Iconable;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
import com.intellij.psi.util.PsiFormatUtilBase;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
import com.intellij.usageView.UsageViewBundle;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.ui.NamedColorUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
public class TaintNode extends PresentableNodeDescriptor<TaintNode> {
|
||||
@@ -24,6 +38,8 @@ public class TaintNode extends PresentableNodeDescriptor<TaintNode> {
|
||||
boolean isTaintFlowRoot;
|
||||
private boolean isExcluded;
|
||||
|
||||
@Nullable
|
||||
private final Icon myIcon;
|
||||
@Nullable
|
||||
private final TaintValueFactory myTaintValueFactory;
|
||||
|
||||
@@ -35,6 +51,8 @@ public class TaintNode extends PresentableNodeDescriptor<TaintNode> {
|
||||
myPsiElement = psiElement == null ? null : SmartPointerManager.createPointer(psiElement);
|
||||
myRef = ref == null ? null : SmartPointerManager.createPointer(ref);
|
||||
myTaintValueFactory = taintValueFactory;
|
||||
int flags = Iconable.ICON_FLAG_VISIBILITY | Iconable.ICON_FLAG_READ_STATUS;
|
||||
myIcon = psiElement == null ? null : psiElement.getIcon(flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,6 +106,66 @@ public class TaintNode extends PresentableNodeDescriptor<TaintNode> {
|
||||
return children;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull PresentationData createPresentation() {
|
||||
PresentationData data = new PresentationData();
|
||||
PsiElement psiElement = this.getPsiElement();
|
||||
if (psiElement == null) {
|
||||
append(data, UsageViewBundle.message("node.invalid"), SimpleTextAttributes.ERROR_ATTRIBUTES);
|
||||
return data;
|
||||
}
|
||||
appendPsiElement(data, psiElement);
|
||||
if (!this.isTaintFlowRoot) return data;
|
||||
String unsafeFlow = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.toolwindow.unsafe.flow");
|
||||
SimpleTextAttributes attributes = new SimpleTextAttributes(SimpleTextAttributes.STYLE_ITALIC, UIUtil.getLabelInfoForeground());
|
||||
append(data, unsafeFlow, attributes);
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void append(@NotNull PresentationData data,
|
||||
@NlsContexts.Label @NotNull String message, @NotNull SimpleTextAttributes attributes) {
|
||||
data.addText(message, attributes);
|
||||
}
|
||||
|
||||
private void appendPsiElement(@NotNull PresentationData data, @NotNull PsiElement psiElement) {
|
||||
TaintNode taintNode = this;
|
||||
data.setIcon(myIcon);
|
||||
int style = taintNode.isExcluded() ? SimpleTextAttributes.STYLE_STRIKEOUT : SimpleTextAttributes.STYLE_PLAIN;
|
||||
Color color = taintNode.myTaintValue == TaintValue.TAINTED ? NamedColorUtil.getErrorForeground() : null;
|
||||
SimpleTextAttributes attributes = new SimpleTextAttributes(style, color);
|
||||
PsiMethod psiMethod = ObjectUtils.tryCast(psiElement, PsiMethod.class);
|
||||
if (psiMethod != null) {
|
||||
PsiMethodRenderingInfo renderingInfo = new PsiMethodRenderingInfo(true);
|
||||
String text = renderingInfo.getPresentableText(psiMethod);
|
||||
append(data, text, attributes);
|
||||
return;
|
||||
}
|
||||
PsiVariable psiVariable = ObjectUtils.tryCast(psiElement, PsiVariable.class);
|
||||
if (psiVariable != null) {
|
||||
String varText =
|
||||
PsiFormatUtil.formatVariable(psiVariable, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_TYPE, PsiSubstitutor.EMPTY);
|
||||
append(data, varText, attributes);
|
||||
PsiNameIdentifierOwner parent = PsiTreeUtil.getParentOfType(psiVariable, PsiClass.class, PsiMethod.class);
|
||||
Color placeColor = attributes.getFgColor();
|
||||
if (placeColor == null) placeColor = UIUtil.getLabelInfoForeground();
|
||||
SimpleTextAttributes placeAttribute = new SimpleTextAttributes(SimpleTextAttributes.STYLE_ITALIC, placeColor);
|
||||
if (parent instanceof PsiMethod) {
|
||||
PsiMethodRenderingInfo renderingInfo = new PsiMethodRenderingInfo(true);
|
||||
append(data, ": " + renderingInfo.getPresentableText((PsiMethod)parent), placeAttribute);
|
||||
}
|
||||
else if (parent instanceof PsiClass) {
|
||||
PsiElementRenderingInfo<PsiClass> renderingInfo = PsiClassRenderingInfo.INSTANCE;
|
||||
append(data, ": " + renderingInfo.getPresentableText((PsiClass)parent), placeAttribute);
|
||||
}
|
||||
return;
|
||||
}
|
||||
PsiNamedElement namedElement = ObjectUtils.tryCast(psiElement, PsiNamedElement.class);
|
||||
if (namedElement == null) return;
|
||||
String name = namedElement.getName();
|
||||
if (name == null) return;
|
||||
append(data, name, attributes);
|
||||
}
|
||||
|
||||
private void markTainted() {
|
||||
myTaintValue = TaintValue.TAINTED;
|
||||
TaintNode parent = ObjectUtils.tryCast(getParentDescriptor(), TaintNode.class);
|
||||
|
||||
+28
-11
@@ -17,10 +17,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.uast.ULocalVariable;
|
||||
import org.jetbrains.uast.UastContextKt;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
class TaintValueFactory implements RestrictionInfoFactory<TaintValue> {
|
||||
|
||||
@@ -32,13 +29,14 @@ class TaintValueFactory implements RestrictionInfoFactory<TaintValue> {
|
||||
|
||||
@Nullable
|
||||
private final String firstAnnotation;
|
||||
@NotNull
|
||||
private final UntaintedContext myContext;
|
||||
|
||||
TaintValueFactory(@NotNull List<String> taintedAnnotations,
|
||||
@NotNull List<String> unTaintedAnnotations,
|
||||
@Nullable String firstAnnotation) {
|
||||
this.myTaintedAnnotations = new HashSet<>(taintedAnnotations);
|
||||
this.myUnTaintedAnnotations = new HashSet<>(unTaintedAnnotations);
|
||||
this.firstAnnotation = firstAnnotation;
|
||||
TaintValueFactory(@NotNull UntaintedContext context) {
|
||||
this.myTaintedAnnotations = new HashSet<>(context.taintedAnnotations);
|
||||
this.myUnTaintedAnnotations = new HashSet<>(context.unTaintedAnnotations);
|
||||
this.firstAnnotation = context.firstAnnotation();
|
||||
this.myContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,7 +105,7 @@ class TaintValueFactory implements RestrictionInfoFactory<TaintValue> {
|
||||
.findFirst().orElse(TaintValue.UNKNOWN);
|
||||
}
|
||||
|
||||
@NotNull TaintValue of(@NotNull PsiModifierListOwner annotationOwner) {
|
||||
private @NotNull TaintValue of(@NotNull PsiModifierListOwner annotationOwner) {
|
||||
HashSet<String> allNames = new HashSet<>();
|
||||
allNames.addAll(myUnTaintedAnnotations);
|
||||
allNames.addAll(myTaintedAnnotations);
|
||||
@@ -191,4 +189,23 @@ class TaintValueFactory implements RestrictionInfoFactory<TaintValue> {
|
||||
Set<PsiAnnotation.TargetType> targets = AnnotationTargetUtil.getAnnotationTargets(annotationClass);
|
||||
return targets == null ? Set.of() : targets;
|
||||
}
|
||||
|
||||
record UntaintedContext(@NotNull List<String> taintedAnnotations,
|
||||
@NotNull List<String> unTaintedAnnotations,
|
||||
@Nullable String firstAnnotation,
|
||||
@NotNull List<String> methodClass, @NotNull List<String> methodPatterns,
|
||||
@NotNull List<String> fieldClass, @NotNull List<String> fieldPatterns) {
|
||||
|
||||
public UntaintedContext copy() {
|
||||
return new UntaintedContext(new ArrayList<>(taintedAnnotations), new ArrayList<>(unTaintedAnnotations),
|
||||
firstAnnotation,
|
||||
new ArrayList<>(methodClass), new ArrayList<>(methodPatterns),
|
||||
new ArrayList<>(fieldClass), new ArrayList<>(fieldPatterns));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
UntaintedContext getContext() {
|
||||
return myContext;
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 'id'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 'id'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
+2
-10
@@ -1,9 +1,8 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
|
||||
@Untainted String field = baz();
|
||||
@Untainted String field = "";
|
||||
|
||||
void test(boolean b) {
|
||||
String s = b ? foo() : field;
|
||||
@@ -11,16 +10,9 @@ class Simple {
|
||||
}
|
||||
|
||||
@Untainted String foo() {
|
||||
return bar();
|
||||
return "";
|
||||
}
|
||||
|
||||
@Untainted String bar() {
|
||||
return baz();
|
||||
}
|
||||
|
||||
@Untainted String baz() {
|
||||
return foo();
|
||||
}
|
||||
|
||||
void sink(@Untainted String s) {}
|
||||
}
|
||||
+2
-10
@@ -1,9 +1,8 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
|
||||
String field = baz();
|
||||
String field = "";
|
||||
|
||||
void test(boolean b) {
|
||||
String s = b ? foo() : field;
|
||||
@@ -11,16 +10,9 @@ class Simple {
|
||||
}
|
||||
|
||||
String foo() {
|
||||
return bar();
|
||||
return "";
|
||||
}
|
||||
|
||||
String bar() {
|
||||
return baz();
|
||||
}
|
||||
|
||||
String baz() {
|
||||
return foo();
|
||||
}
|
||||
|
||||
void sink(@Untainted String s) {}
|
||||
}
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "false"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 'a'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 'a'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 'a'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 'a'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// "Propagate safe annotation from 's'" "false"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
|
||||
void test() {
|
||||
String s = foo();
|
||||
sink(s);
|
||||
}
|
||||
|
||||
@Untainted String foo() {
|
||||
return bar();
|
||||
}
|
||||
|
||||
@Untainted String bar() {
|
||||
return "safe";
|
||||
}
|
||||
|
||||
void sink(@Untainted String s) {}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
|
||||
String field = source();
|
||||
|
||||
void test() {
|
||||
String s = field;
|
||||
sink(<caret>s);
|
||||
}
|
||||
|
||||
void sink(@Untainted String s) {}
|
||||
|
||||
@Tainted String source() {
|
||||
return "unsafe";
|
||||
}
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
// "Propagate safe annotation from 's'" "true"
|
||||
import org.checkerframework.checker.tainting.qual.*;
|
||||
|
||||
class Simple {
|
||||
|
||||
void test() {
|
||||
String s = foo();
|
||||
sink(<caret>s);
|
||||
}
|
||||
|
||||
String foo() {
|
||||
return bar();
|
||||
}
|
||||
|
||||
String bar() {
|
||||
return "safe";
|
||||
}
|
||||
|
||||
void sink(@Untainted String s) {}
|
||||
}
|
||||
+24
@@ -74,4 +74,28 @@ class JavaMarkAsSafeFixSourceToSinkFlowInspectionTest : SourceToSinkFlowInspecti
|
||||
prepareJsr()
|
||||
myFixture.testQuickFix("CommonCasesJsr.java", "Mark 's1' as requiring validation", true)
|
||||
}
|
||||
|
||||
fun `test unknown field`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("UnknownField.java", "Mark 's' as requiring validation", true)
|
||||
}
|
||||
|
||||
fun `test unknown method`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("UnknownMethod.java", "Mark 's' as requiring validation", true)
|
||||
}
|
||||
|
||||
fun `test tainted method`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFixUnavailable("TaintedMethod.java", "Mark 's' as requiring validation")
|
||||
}
|
||||
|
||||
fun `test recursive 2 path`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("RecursiveTwoPaths.java", "Mark 's' as requiring validation", true)
|
||||
}
|
||||
fun `test recursive`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("Recursive.java", "Mark 'id' as requiring validation", true)
|
||||
}
|
||||
}
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package com.intellij.codeInspection.tests.java.sourceToSink
|
||||
|
||||
import com.intellij.codeInspection.tests.sourceToSink.SourceToSinkFlowInspectionTestBase
|
||||
import com.intellij.jvm.analysis.JavaJvmAnalysisTestUtil
|
||||
import com.intellij.testFramework.LightProjectDescriptor
|
||||
import com.intellij.testFramework.TestDataPath
|
||||
|
||||
private const val inspectionPath = "/codeInspection/sourceToSinkFlow/propagateSafe"
|
||||
|
||||
@TestDataPath("\$CONTENT_ROOT/testData$inspectionPath")
|
||||
class JavaPropagateFixSourceToSinkFlowInspectionTest : SourceToSinkFlowInspectionTestBase() {
|
||||
|
||||
override fun getProjectDescriptor(): LightProjectDescriptor {
|
||||
return JAVA_17
|
||||
}
|
||||
|
||||
override fun getBasePath(): String {
|
||||
return JavaJvmAnalysisTestUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH + inspectionPath
|
||||
}
|
||||
|
||||
fun `test ParameterMethodUntainted`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("ParameterMethodUntainted.java", "Show propagation tree from 'a'")
|
||||
}
|
||||
|
||||
fun `test tainted field`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFixUnavailable("TaintedField.java", "Show propagation tree from 'a'")
|
||||
}
|
||||
|
||||
fun `test ParameterParameterUntainted`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("ParameterParameterUntainted.java", "Show propagation tree from 'a'")
|
||||
}
|
||||
|
||||
fun `test ParameterMethodUnknown`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("ParameterMethodUnknown.java", "Show propagation tree from 's'")
|
||||
}
|
||||
|
||||
fun `test ParameterMethodTainted`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("ParameterMethodTainted.java", "Show propagation tree from 's'")
|
||||
}
|
||||
|
||||
fun `test ParameterFieldUnknown`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("ParameterFieldUnknown.java", "Show propagation tree from 's'")
|
||||
}
|
||||
|
||||
fun `test ParameterFieldTainted`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("ParameterFieldTainted.java", "Show propagation tree from 's'")
|
||||
}
|
||||
|
||||
fun `test Parameter`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("Parameter.java", "Show propagation tree from 's'")
|
||||
}
|
||||
|
||||
fun `test MethodMethodUnknown`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("MethodMethodUnknown.java", "Show propagation tree from 's'")
|
||||
}
|
||||
|
||||
fun `test MethodMethodTainted`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("MethodMethodTainted.java", "Show propagation tree from 's'")
|
||||
}
|
||||
|
||||
fun `test MethodFieldUnknown`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("MethodFieldUnknown.java", "Show propagation tree from 's'")
|
||||
}
|
||||
|
||||
fun `test MethodFieldTainted`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("MethodFieldTainted.java", "Show propagation tree from 's'")
|
||||
}
|
||||
|
||||
fun `test MethodFieldMethodUnknown`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("MethodFieldMethodUnknown.java", "Show propagation tree from 's'")
|
||||
}
|
||||
|
||||
fun `test MethodFieldMethodTainted`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("MethodFieldMethodTainted.java", "Show propagation tree from 's'")
|
||||
}
|
||||
|
||||
fun `test AnotherClassMethodCall`() {
|
||||
prepareCheckFramework()
|
||||
myFixture.testQuickFix("AnotherClassMethodCall.java", "Show propagation tree from 's'")
|
||||
}
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
package com.intellij.codeInspection.tests.java.sourceToSink;
|
||||
|
||||
public class PropagateFixTest extends SourceToSinkFixBaseTest {
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/codeInspection/sourceToSinkFlow/propagateSafe";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldBeAvailableAfterExecution() {
|
||||
return getTestName(true).contains("Tainted");
|
||||
}
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
package com.intellij.codeInspection.tests.java.sourceToSink;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.sourceToSink.SourceToSinkFlowInspection;
|
||||
import com.intellij.jvm.analysis.JavaJvmAnalysisTestUtil;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.roots.ContentEntry;
|
||||
import com.intellij.openapi.roots.ModifiableRootModel;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.MavenDependencyUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
abstract class SourceToSinkFixBaseTest extends LightQuickFixParameterizedTestCase {
|
||||
|
||||
@Override
|
||||
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{new SourceToSinkFlowInspection()};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
|
||||
return new DefaultLightProjectDescriptor() {
|
||||
@Override
|
||||
public void configureModule(@NotNull Module module,
|
||||
@NotNull ModifiableRootModel model,
|
||||
@NotNull ContentEntry contentEntry) {
|
||||
super.configureModule(module, model, contentEntry);
|
||||
MavenDependencyUtil.addFromMaven(model, "org.checkerframework:checker-qual:3.18.0");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull String getTestDataPath() {
|
||||
return PathManager.getCommunityHomePath() + JavaJvmAnalysisTestUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -23,7 +23,7 @@ class KotlinSourceToSinkFlowInspectionTest : SourceToSinkFlowInspectionTestBase(
|
||||
fun testKotlinPropertyPropagateFix() {
|
||||
prepareCheckFramework()
|
||||
myFixture.configureByFile("Property.kt")
|
||||
val propagateAction = myFixture.getAvailableIntention("Propagate safe annotation from 'getF'")!!
|
||||
val propagateAction = myFixture.getAvailableIntention("Show propagation tree from 'getF'")!!
|
||||
myFixture.launchAction(propagateAction)
|
||||
myFixture.checkResultByFile("Property.after.kt")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user