[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:
Mikhail Pyltsin
2023-04-25 18:46:31 +00:00
committed by intellij-monorepo-bot
parent bdfa6f8ccb
commit 5032fa91dd
59 changed files with 518 additions and 473 deletions
@@ -1,8 +0,0 @@
// "Add 'checker-qual' to classpath" "true"
package x;
public class AddCheckerFrameworkAnnotations {
public @Ta<caret>inted String source() {
return "unsafe";
}
}
@@ -1,9 +0,0 @@
// "Add 'checker-qual' to classpath" "true"
package x;
public class AddCheckerFrameworkAnnotations {
@Untaint<caret>ed
public void safe() {
}
}
@@ -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
@@ -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;
}
}
@@ -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,
@@ -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);
}
}
}
}
@@ -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) {
@@ -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();
@@ -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);
@@ -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,4 +1,3 @@
// "Propagate safe annotation from 'id'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 'id'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -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) {}
}
@@ -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,4 +1,3 @@
// "Propagate safe annotation from 's'" "false"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 'a'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -1,4 +1,3 @@
// "Propagate safe annotation from 's'" "false"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@@ -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) {}
}
@@ -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";
}
}
@@ -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) {}
}
@@ -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)
}
}
@@ -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'")
}
}
@@ -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");
}
}
@@ -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;
}
}
@@ -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")
}