SourceToSinkFlowInspection: added fix to propagate safe annotation

GitOrigin-RevId: 03b5b73ec32510624858245e9024152a7341b3ff
This commit is contained in:
Artemiy Sartakov
2021-10-01 16:45:17 +07:00
committed by intellij-monorepo-bot
parent d7345dde4f
commit 40936f24bf
68 changed files with 2117 additions and 466 deletions

View File

@@ -62,6 +62,16 @@ jvm.inspections.source.to.sink.flow.returned.unsafe=Unsafe string is returned fr
jvm.inspections.source.to.sink.flow.returned.unknown=Unknown string is returned from safe method
jvm.inspections.source.unsafe.to.sink.flow.mark.as.safe.family=Mark as safe
jvm.inspections.source.unsafe.to.sink.flow.mark.as.safe.text=Mark ''{0}'' as safe
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.progress.title=Propagating annotations...
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.toolwindow.conflicts.title=Detected Unsafe Flows
jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.toolwindow.annotate=Annotate
propagated.from=Reason to Mark as Safe
propagated.to=Target to Mark as Safe
propagate.from.empty.text=Reason to mark as safe would be shown here
propagate.to.empty.text=Target to mark as safe would be shown here
jvm.inspections.testonly.display.name=Test-only usage in production code
jvm.inspections.testonly.class.reference=Test-only class is referenced in production code

View File

@@ -2,17 +2,29 @@
package com.intellij.codeInspection.sourceToSink;
import com.intellij.analysis.JvmAnalysisBundle;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.LocalQuickFixOnPsiElement;
import com.intellij.codeInspection.UntaintedAnnotationProvider;
import com.intellij.lang.jvm.JvmModifiersOwner;
import com.intellij.lang.jvm.actions.AnnotationRequest;
import com.intellij.lang.jvm.actions.AnnotationRequestsKt;
import com.intellij.lang.jvm.actions.JvmElementActionFactories;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.UReferenceExpression;
import org.jetbrains.uast.UastContextKt;
import java.util.List;
public class MarkAsSafeFix extends LocalQuickFixOnPsiElement {
private final String myName;
@@ -42,6 +54,19 @@ public class MarkAsSafeFix extends LocalQuickFixOnPsiElement {
TaintAnalyzer taintAnalyzer = new TaintAnalyzer();
TaintValue taintValue = taintAnalyzer.analyze(uExpression);
if (taintValue != TaintValue.UNKNOWN) return;
taintAnalyzer.getNonMarkedElements().forEach(owner -> new UntaintedAnnotationProvider().createFix(owner).applyFix());
taintAnalyzer.getNonMarkedElements().forEach(owner -> markAsSafe(project, owner.myNonMarked));
}
public static void markAsSafe(@NotNull Project project, @NotNull PsiModifierListOwner owner) {
AnnotationRequest request = AnnotationRequestsKt.annotationRequest(UntaintedAnnotationProvider.DEFAULT_UNTAINTED_ANNOTATION);
PsiFile file = owner.getContainingFile();
VirtualFile virtualFile = file.getVirtualFile();
TextEditor textEditor = ObjectUtils.tryCast(FileEditorManager.getInstance(project).getSelectedEditor(virtualFile), TextEditor.class);
if (textEditor == null) return;
Editor editor = textEditor.getEditor();
// TODO: support for kotlin type use annotations
List<IntentionAction> actions = JvmElementActionFactories.createAddAnnotationActions((JvmModifiersOwner)owner, request);
if (actions.size() == 1) actions.get(0).invoke(project, editor, file);
CodeStyleManager.getInstance(project).reformat(owner);
}
}

View File

@@ -0,0 +1,32 @@
// 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.psi.PsiElement;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UReferenceExpression;
import org.jetbrains.uast.UResolvable;
public class NonMarkedElement {
public final PsiModifierListOwner myNonMarked;
public final PsiElement myRef;
NonMarkedElement(@NotNull PsiModifierListOwner marked, @NotNull PsiElement ref) {
myNonMarked = marked;
myRef = ref;
}
static @Nullable NonMarkedElement create(@Nullable UElement uElement) {
if (!(uElement instanceof UCallExpression || uElement instanceof UReferenceExpression)) return null;
UResolvable uResolvable = (UResolvable)uElement;
PsiElement ref = uElement.getSourcePsi();
if (ref == null) return null;
PsiModifierListOwner target = ObjectUtils.tryCast(uResolvable.resolve(), PsiModifierListOwner.class);
return target == null ? null : new NonMarkedElement(target, ref);
}
}

View File

@@ -7,9 +7,8 @@ import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.restriction.AnnotationContext;
import com.intellij.codeInspection.restriction.StringFlowUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiNamedElement;
import com.intellij.codeInspection.sourceToSink.propagate.PropagateFix;
import com.intellij.psi.*;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -25,6 +24,8 @@ public class SourceToSinkFlowInspection extends AbstractBaseJavaLocalInspectionT
public void visitElement(@NotNull PsiElement element) {
UExpression uExpression = UastContextKt.toUElementOfExpectedTypes(element, UCallExpression.class, UReferenceExpression.class);
if (uExpression == null) return;
PsiType expressionType = uExpression.getExpressionType();
if (expressionType == null || !expressionType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) return;
UExpression usage = StringFlowUtil.goUp(uExpression, true, TaintValueFactory.INSTANCE);
AnnotationContext annotationContext = AnnotationContext.fromExpression(usage);
TaintValue contextValue = TaintValueFactory.INSTANCE.of(annotationContext);
@@ -34,19 +35,20 @@ public class SourceToSinkFlowInspection extends AbstractBaseJavaLocalInspectionT
taintValue = taintValue.join(contextValue);
if (taintValue == TaintValue.UNTAINTED) return;
String errorMessage = JvmAnalysisBundle.message(taintValue.getErrorMessage(annotationContext));
LocalQuickFix fix = taintValue == TaintValue.UNKNOWN ? createFix(element, uExpression) : null;
holder.registerProblem(element, errorMessage, fix);
LocalQuickFix[] fixes = null;
if (taintValue == TaintValue.UNKNOWN) {
String name = getName((UResolvable)uExpression);
if (name != null) {
fixes = new LocalQuickFix[]{new MarkAsSafeFix(element, name), new PropagateFix(element, name)};
}
}
holder.registerProblem(element, errorMessage, fixes);
}
};
}
private static @Nullable LocalQuickFix createFix(@NotNull PsiElement element, @NotNull UExpression uExpression) {
UResolvable uResolvable = ObjectUtils.tryCast(uExpression, UResolvable.class);
if (uResolvable == null) return null;
PsiNamedElement namedElement = ObjectUtils.tryCast(uResolvable.resolve(), PsiNamedElement.class);
if (namedElement == null) return null;
String name = namedElement.getName();
if (name == null) return null;
return new MarkAsSafeFix(element, name);
private static @Nullable String getName(@NotNull UResolvable uExpression) {
PsiNamedElement namedElement = ObjectUtils.tryCast(uExpression.resolve(), PsiNamedElement.class);
return namedElement == null ? null : namedElement.getName();
}
}

View File

@@ -2,6 +2,7 @@
package com.intellij.codeInspection.sourceToSink;
import com.intellij.psi.*;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.ExpressionUtils;
import one.util.streamex.MoreCollectors;
@@ -11,50 +12,185 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.*;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.stream.Collector;
class TaintAnalyzer {
public class TaintAnalyzer {
private final Set<UElement> myVisited = new HashSet<>();
private final List<PsiModifierListOwner> myNonMarkedElements = new ArrayList<>();
private final Set<PsiElement> myVisited = new HashSet<>();
private final List<NonMarkedElement> myNonMarkedElements = new ArrayList<>();
@NotNull TaintValue analyze(@NotNull UExpression expression) {
if (myVisited.contains(expression)) return TaintValue.UNTAINTED;
public @NotNull TaintValue analyze(@NotNull UExpression expression) {
UResolvable uResolvable = ObjectUtils.tryCast(expression, UResolvable.class);
// ignore possible plus operator overload in kotlin
if (uResolvable == null || isPlus(expression)) return TaintValue.UNTAINTED;
return analyseResolvableExpression(uResolvable);
PsiElement ref = expression.getSourcePsi();
if (ref == null) return TaintValue.UNTAINTED;
PsiElement target = uResolvable.resolve();
return fromElement(target, ref, false);
}
private @NotNull TaintValue analyseResolvableExpression(@NotNull UResolvable uResolvable) {
PsiElement target = uResolvable.resolve();
TaintValue taintValue = fromAnnotation(uResolvable, target);
public @NotNull TaintValue fromElement(@Nullable PsiElement target, @NotNull PsiElement ref, boolean processRecursively) {
if (target == null || !myVisited.add(target)) return TaintValue.UNTAINTED;
PsiType type = getType(target);
if (type == null) return TaintValue.UNTAINTED;
TaintValue taintValue = fromAnnotation(target, type);
if (taintValue != TaintValue.UNKNOWN) return taintValue;
taintValue = fromLocalVar(target);
taintValue = fromModifierListOwner(target, ref, processRecursively);
return taintValue == null ? TaintValue.UNTAINTED : taintValue;
}
public List<NonMarkedElement> getNonMarkedElements() {
return myNonMarkedElements;
}
private @Nullable TaintValue fromModifierListOwner(@NotNull PsiElement target, @NotNull PsiElement ref, boolean processRecursively) {
PsiModifierListOwner owner = ObjectUtils.tryCast(target, PsiModifierListOwner.class);
if (owner == null) return null;
TaintValue taintValue = fromLocalVar(owner);
if (taintValue != null) return taintValue;
if (!(target instanceof PsiModifierListOwner)) return TaintValue.UNTAINTED;
myNonMarkedElements.add((PsiModifierListOwner)target);
if (processRecursively) {
taintValue = fromMethod(owner);
if (taintValue != null) return taintValue;
taintValue = fromField(owner);
if (taintValue != null) return taintValue;
taintValue = fromParam(owner);
if (taintValue != null) return taintValue;
return TaintValue.UNTAINTED;
}
myNonMarkedElements.add(new NonMarkedElement(owner, ref));
return TaintValue.UNKNOWN;
}
private @Nullable TaintValue fromLocalVar(@Nullable PsiElement target) {
PsiLocalVariable variable = ObjectUtils.tryCast(target, PsiLocalVariable.class);
if (variable == null) return null;
ULocalVariable uVariable = UastContextKt.toUElement(variable, ULocalVariable.class);
private @Nullable TaintValue fromLocalVar(@NotNull PsiElement target) {
PsiLocalVariable psiVariable = ObjectUtils.tryCast(target, PsiLocalVariable.class);
if (psiVariable == null) return null;
ULocalVariable uVariable = UastContextKt.toUElement(psiVariable, ULocalVariable.class);
if (uVariable == null) return null;
UBlockExpression codeBlock = UastUtils.getParentOfType(uVariable, UBlockExpression.class);
if (codeBlock == null) return TaintValue.UNKNOWN;
if (!myVisited.add(uVariable)) return TaintValue.UNTAINTED;
UExpression uInitializer = uVariable.getUastInitializer();
TaintValue taintValue = fromExpression(uInitializer, true);
if (taintValue == TaintValue.TAINTED) return taintValue;
VariableAnalyzer taintValueVisitor = new VariableAnalyzer(variable, taintValue);
codeBlock.accept(taintValueVisitor);
return taintValueVisitor.myTaintValue;
UBlockExpression codeBlock = UastUtils.getParentOfType(uVariable, UBlockExpression.class);
return codeBlock == null ? TaintValue.UNTAINTED : analyze(taintValue, codeBlock, psiVariable);
}
private TaintValue analyze(@NotNull TaintValue taintValue, @NotNull UBlockExpression codeBlock, @NotNull PsiVariable psiVariable) {
class VarAnalyzer extends AbstractUastVisitor {
private TaintValue myTaintValue;
VarAnalyzer(TaintValue taintValue) {
myTaintValue = taintValue;
}
@Override
public boolean visitBlockExpression(@NotNull UBlockExpression node) {
for (UExpression expression : node.getExpressions()) {
expression.accept(this);
if (taintValue == TaintValue.TAINTED) return true;
}
return true;
}
@Override
public boolean visitBinaryExpression(@NotNull UBinaryExpression node) {
UastBinaryOperator operator = node.getOperator();
if (operator != UastBinaryOperator.ASSIGN && operator != UastBinaryOperator.PLUS_ASSIGN) {
return super.visitBinaryExpression(node);
}
UReferenceExpression lhs = ObjectUtils.tryCast(node.getLeftOperand(), UReferenceExpression.class);
if (lhs == null || !psiVariable.equals(lhs.resolve())) return super.visitBinaryExpression(node);
UExpression rhs = node.getRightOperand();
myTaintValue = myTaintValue.join(fromExpression(rhs, true));
return super.visitBinaryExpression(node);
}
}
VarAnalyzer varAnalyzer = new VarAnalyzer(taintValue);
codeBlock.accept(varAnalyzer);
return varAnalyzer.myTaintValue;
}
private @Nullable TaintValue fromParam(@Nullable PsiElement target) {
UParameter uParameter = UastContextKt.toUElement(target, UParameter.class);
if (uParameter == null) return null;
PsiParameter psiParameter = ObjectUtils.tryCast(uParameter.getSourcePsi(), PsiParameter.class);
if (psiParameter == null) return null;
UMethod uMethod = ObjectUtils.tryCast(uParameter.getUastParent(), UMethod.class);
if (uMethod == null) return TaintValue.UNTAINTED;
UBlockExpression uBlock = ObjectUtils.tryCast(uMethod.getUastBody(), UBlockExpression.class);
if (uBlock == null) return TaintValue.UNTAINTED;
// default parameter value
UExpression uInitializer = uParameter.getUastInitializer();
TaintValue taintValue = fromExpression(uInitializer, true);
if (taintValue == TaintValue.TAINTED) return taintValue;
taintValue = analyze(taintValue, uBlock, psiParameter);
if (taintValue == TaintValue.TAINTED) return taintValue;
int paramIdx = uMethod.getUastParameters().indexOf(uParameter);
PsiMethod psiMethod = ObjectUtils.tryCast(uMethod.getSourcePsi(), PsiMethod.class);
// TODO: handle varargs
if (psiMethod == null || psiMethod.isVarArgs()) return TaintValue.UNTAINTED;
Collection<NonMarkedElement> args = findArgs(psiMethod, paramIdx);
if (args.isEmpty()) return taintValue;
myNonMarkedElements.addAll(args);
return TaintValue.UNKNOWN;
}
private @Nullable TaintValue fromField(@NotNull PsiElement target) {
UField uField = UastContextKt.toUElement(target, UField.class);
if (uField == null) return null;
List<NonMarkedElement> children = new ArrayList<>();
NonMarkedElement initializer = NonMarkedElement.create(uField.getUastInitializer());
if (initializer != null) children.add(initializer);
children.addAll(findAssignments(target));
if (children.isEmpty()) return TaintValue.UNTAINTED;
myNonMarkedElements.addAll(children);
return TaintValue.UNKNOWN;
}
private @Nullable TaintValue fromMethod(@NotNull PsiElement target) {
UMethod uMethod = UastContextKt.toUElement(target, UMethod.class);
if (uMethod == null) return null;
return analyze(uMethod);
}
private @NotNull TaintValue analyze(@NotNull UMethod uMethod) {
class MethodAnalyzer extends AbstractUastVisitor {
private final List<NonMarkedElement> myChildren = new ArrayList<>();
@Override
public boolean visitBlockExpression(@NotNull UBlockExpression node) {
for (UExpression expression : node.getExpressions()) {
expression.accept(this);
}
return true;
}
@Override
public boolean visitReturnExpression(@NotNull UReturnExpression node) {
UExpression returnExpression = node.getReturnExpression();
if (returnExpression == null) return true;
returnExpression.accept(new AbstractUastVisitor() {
@Override
public boolean visitElement(@NotNull UElement node) {
NonMarkedElement nonMarked = NonMarkedElement.create(node);
if (nonMarked == null) return super.visitElement(node);
myChildren.add(nonMarked);
return true;
}
});
return super.visitReturnExpression(node);
}
}
UBlockExpression methodBody = ObjectUtils.tryCast(uMethod.getUastBody(), UBlockExpression.class);
if (methodBody == null) return TaintValue.UNTAINTED;
MethodAnalyzer methodAnalyzer = new MethodAnalyzer();
methodBody.accept(methodAnalyzer);
List<NonMarkedElement> children = methodAnalyzer.myChildren;
if (children.isEmpty()) return TaintValue.UNTAINTED;
myNonMarkedElements.addAll(children);
return TaintValue.UNKNOWN;
}
private @NotNull TaintValue fromExpression(@Nullable UExpression uExpression, boolean goDeep) {
@@ -72,15 +208,45 @@ class TaintAnalyzer {
.collect(joining(false));
}
private Collector<UExpression, ?, TaintValue> joining(boolean goDeep) {
private @NotNull Collector<UExpression, ?, TaintValue> joining(boolean goDeep) {
return MoreCollectors.mapping((UExpression e) -> fromExpression(e, goDeep), TaintValue.joining());
}
List<PsiModifierListOwner> getNonMarkedElements() {
return myNonMarkedElements;
private static @NotNull Collection<NonMarkedElement> findArgs(PsiMethod psiMethod, int paramIdx) {
Collection<PsiReference> all = ReferencesSearch.search(psiMethod, psiMethod.getUseScope()).findAll();
return ReferencesSearch.search(psiMethod, psiMethod.getUseScope())
.mapping(r -> ObjectUtils.tryCast(r.getElement().getParent(), PsiMethodCallExpression.class))
.mapping(call -> call == null ? null : call.getArgumentList().getExpressions())
.filtering(args -> args != null && args.length > paramIdx)
.mapping(args -> UastContextKt.toUElement(args[paramIdx]))
.mapping(arg -> NonMarkedElement.create(arg))
.filtering(arg -> arg != null)
.findAll();
}
private static @NotNull TaintValue fromAnnotation(@NotNull UResolvable uResolvable, @Nullable PsiElement target) {
private static @NotNull Collection<NonMarkedElement> findAssignments(@NotNull PsiElement target) {
return ReferencesSearch.search(target, target.getUseScope())
.mapping(u -> UastContextKt.getUastParentOfType(u.getElement(), UBinaryExpression.class))
.filtering(binary -> isLhs(binary, target))
.mapping(binary -> NonMarkedElement.create(binary.getRightOperand()))
.filtering(e -> e != null)
.findAll();
}
private static boolean isLhs(@Nullable UBinaryExpression uBinary, @NotNull PsiElement target) {
if (uBinary == null) return false;
UastBinaryOperator operator = uBinary.getOperator();
if (operator != UastBinaryOperator.ASSIGN && operator != UastBinaryOperator.PLUS_ASSIGN) return false;
UResolvable leftOperand = ObjectUtils.tryCast(UastUtils.skipParenthesizedExprDown(uBinary.getLeftOperand()), UResolvable.class);
return leftOperand != null && leftOperand.resolve() == target;
}
private static @Nullable PsiType getType(@Nullable PsiElement element) {
if (element instanceof PsiMethod) return ((PsiMethod)element).getReturnType();
return element instanceof PsiVariable ? ((PsiVariable)element).getType() : null;
}
private static @NotNull TaintValue fromAnnotation(@Nullable PsiElement target, @NotNull PsiType type) {
if (target instanceof PsiClass) return TaintValue.UNTAINTED;
if (target instanceof PsiModifierListOwner) {
PsiModifierListOwner owner = (PsiModifierListOwner)target;
@@ -88,8 +254,6 @@ class TaintAnalyzer {
if (taintValue == TaintValue.UNKNOWN) taintValue = TaintValueFactory.of(owner);
if (taintValue != TaintValue.UNKNOWN) return taintValue;
}
PsiType type = ((UExpression)uResolvable).getExpressionType();
if (type == null) return TaintValue.UNKNOWN;
return TaintValueFactory.INSTANCE.fromAnnotationOwner(type);
}
@@ -104,38 +268,4 @@ class TaintAnalyzer {
PsiElement sourcePsi = expression.getSourcePsi();
return sourcePsi != null && UastBinaryOperator.PLUS.getText().equals(sourcePsi.getText());
}
private class VariableAnalyzer extends AbstractUastVisitor {
private final PsiLocalVariable myVariable;
private TaintValue myTaintValue;
private VariableAnalyzer(@NotNull PsiLocalVariable variable, @Nullable TaintValue taintValue) {
myVariable = variable;
myTaintValue = taintValue;
}
@Override
public boolean visitBlockExpression(@NotNull UBlockExpression node) {
for (UExpression expression : node.getExpressions()) {
expression.accept(this);
if (myTaintValue == TaintValue.TAINTED) return true;
}
return true;
}
@Override
public boolean visitBinaryExpression(@NotNull UBinaryExpression node) {
UastBinaryOperator operator = node.getOperator();
if (operator != UastBinaryOperator.ASSIGN && operator != UastBinaryOperator.PLUS_ASSIGN) {
return super.visitBinaryExpression(node);
}
UReferenceExpression lhs = ObjectUtils.tryCast(node.getLeftOperand(), UReferenceExpression.class);
if (lhs == null || !myVariable.equals(lhs.resolve())) return super.visitBinaryExpression(node);
UExpression rhs = node.getRightOperand();
TaintValue taintValue = fromExpression(rhs, true);
myTaintValue = myTaintValue.join(taintValue);
return super.visitBinaryExpression(node);
}
}
}

View File

@@ -12,11 +12,11 @@ import org.jetbrains.annotations.NotNull;
import java.util.Set;
import java.util.stream.Collector;
enum TaintValue implements RestrictionInfo {
public enum TaintValue implements RestrictionInfo {
UNTAINTED(UntaintedAnnotationProvider.DEFAULT_UNTAINTED_ANNOTATION, RestrictionInfoKind.KNOWN) {
@Override
@NotNull
TaintValue join(@NotNull TaintValue other) {
public TaintValue join(@NotNull TaintValue other) {
return other;
}
@@ -28,7 +28,7 @@ enum TaintValue implements RestrictionInfo {
TAINTED(UntaintedAnnotationProvider.DEFAULT_TAINTED_ANNOTATION, RestrictionInfoKind.KNOWN) {
@Override
@NotNull
TaintValue join(@NotNull TaintValue other) {
public TaintValue join(@NotNull TaintValue other) {
return this;
}
@@ -42,7 +42,7 @@ enum TaintValue implements RestrictionInfo {
UNKNOWN(UntaintedAnnotationProvider.DEFAULT_POLY_TAINTED_ANNOTATION, RestrictionInfoKind.UNKNOWN) {
@Override
@NotNull
TaintValue join(@NotNull TaintValue other) {
public TaintValue join(@NotNull TaintValue other) {
return other == TAINTED ? other : this;
}
@@ -64,7 +64,7 @@ enum TaintValue implements RestrictionInfo {
this.myKind = kind;
}
abstract @NotNull TaintValue join(@NotNull TaintValue other);
public abstract @NotNull TaintValue join(@NotNull TaintValue other);
@NotNull String getAnnotationName() {
return myName;

View File

@@ -0,0 +1,449 @@
// 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.propagate;
import com.intellij.CommonBundle;
import com.intellij.analysis.JvmAnalysisBundle;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.codeInspection.sourceToSink.TaintValue;
import com.intellij.ide.highlighter.HighlighterFactory;
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.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.ex.EditorEx;
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.Disposer;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.NlsActions;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
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.*;
import com.intellij.ui.content.Content;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usageView.UsageViewContentManager;
import com.intellij.util.Alarm;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.tree.TreeUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UField;
import org.jetbrains.uast.UMethod;
import org.jetbrains.uast.UastContextKt;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class PropagateAnnotationPanel extends JPanel implements Disposable {
private final Tree myTree;
private final Project myProject;
private final TaintNode myRoot;
private final PropagateTreeListener myTreeSelectionListener;
private final Consumer<@NotNull Collection<PsiModifierListOwner>> myCallback;
private Content myContent;
PropagateAnnotationPanel(Project project, @NotNull TaintNode root, @NotNull Consumer<Collection<@NotNull PsiModifierListOwner>> callback) {
super(new BorderLayout());
myTree = PropagateTree.create(root);
myRoot = root;
myProject = project;
myCallback = callback;
Editor usageEditor = createEditor();
Editor memberEditor = createEditor();
myTreeSelectionListener = new PropagateTreeListener(usageEditor, memberEditor, myRoot);
myTree.getSelectionModel().addTreeSelectionListener(myTreeSelectionListener);
JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree);
Splitter splitter = new Splitter(false, (float)0.6);
splitter.setFirstComponent(scrollPane);
Disposer.register(this, new Disposable() {
@Override
public void dispose() {
splitter.dispose();
}
});
JComponent callSitesViewer = createCallSitesViewer(usageEditor, memberEditor);
TreePath selectionPath = myTree.getSelectionPath();
if (selectionPath == null) {
selectionPath = new TreePath(myRoot.getPath());
myTree.getSelectionModel().addSelectionPath(selectionPath);
}
TaintNode node = (TaintNode)selectionPath.getLastPathComponent();
myTreeSelectionListener.updateEditorTexts(node);
splitter.setSecondComponent(callSitesViewer);
add(splitter);
addTreeActions(myTree, root);
addToolbar(root);
}
public void setContent(@NotNull Content content) {
myContent = content;
Disposer.register(content, this);
}
private void addToolbar(@NotNull TaintNode root) {
if (root.myTaintValue == TaintValue.TAINTED) return;
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gc = new GridBagConstraints(GridBagConstraints.RELATIVE, 0, 1, 1, 0, 1, GridBagConstraints.NORTHWEST,
GridBagConstraints.NONE, JBUI.insets(5, 10, 5, 0), 0, 0);
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) {
UsageViewContentManager.getInstance(myProject).closeContent(myContent);
Set<PsiModifierListOwner> toAnnotate = getSelectedElements();
if (toAnnotate != null) myCallback.accept(toAnnotate);
}
});
panel.add(annotateButton, gc);
JButton closeButton = new JButton(CommonBundle.getCancelButtonText());
closeButton.addActionListener(e -> UsageViewContentManager.getInstance(myProject).closeContent(myContent));
panel.add(closeButton, gc);
add(panel, BorderLayout.SOUTH);
}
public Tree getTree() {
return myTree;
}
private @NotNull Editor createEditor() {
EditorFactory editorFactory = EditorFactory.getInstance();
Document document = editorFactory.createDocument("");
return editorFactory.createViewer(document, myProject);
}
@Override
public void dispose() {
if (myTree != null) {
Disposer.dispose(myTreeSelectionListener);
myTree.removeTreeSelectionListener(myTreeSelectionListener);
}
}
public @Nullable Set<PsiModifierListOwner> getSelectedElements() {
return getSelectedElements(myRoot, myRoot, new HashSet<>());
}
static @Nullable Set<PsiModifierListOwner> getSelectedElements(@NotNull TaintNode root,
@NotNull TaintNode taintNode,
@NotNull Set<PsiModifierListOwner> elements) {
PsiModifierListOwner element = taintNode.getElement();
if (element == null) return null;
if (taintNode.isExcluded()) return elements;
if (taintNode != root) elements.add(element);
if (taintNode.myChildren == null) return elements;
for (TaintNode child : taintNode.myChildren) {
if (getSelectedElements(root, child, elements) == null) return null;
}
return elements;
}
private static @NotNull JComponent createCallSitesViewer(@NotNull Editor usageEditor, @NotNull Editor memberEditor) {
Splitter splitter = new Splitter(true);
JComponent usageComponent = usageEditor.getComponent();
usageComponent.setBorder(IdeBorderFactory.createTitledBorder(JvmAnalysisBundle.message("propagated.from"), false));
splitter.setFirstComponent(usageComponent);
JComponent memberComponent = memberEditor.getComponent();
memberComponent.setBorder(IdeBorderFactory.createTitledBorder(JvmAnalysisBundle.message("propagated.to"), false));
splitter.setSecondComponent(memberComponent);
splitter.setBorder(IdeBorderFactory.createRoundedBorder());
return splitter;
}
private static void addTreeActions(@NotNull Tree tree, @NotNull TaintNode root) {
DefaultActionGroup actionGroup = new DefaultActionGroup();
if (root.myTaintValue != TaintValue.TAINTED) {
actionGroup.addAll(createIncludeExcludeActions(tree));
}
actionGroup.add(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE));
PopupHandler.installPopupMenu(tree, actionGroup, "PropagateAnnotationPanelPopup");
}
@Contract("_ -> new")
private static AnAction @NotNull [] createIncludeExcludeActions(@NotNull Tree tree) {
class IncludeExcludeAction extends AnAction {
private final boolean myExclude;
private IncludeExcludeAction(@NlsActions.ActionText String name, boolean exclude) {
super(name);
myExclude = exclude;
}
@Override
public void update(@NotNull AnActionEvent e) {
TreePath[] selectionPaths = tree.getSelectionPaths();
if (selectionPaths == null || selectionPaths.length <= 0) return;
TaintNode[] roots = tree.getSelectedNodes(TaintNode.class, null);
e.getPresentation().setEnabled(nodes(roots).anyMatch(n -> n.isExcluded() != myExclude));
}
private Stream<TaintNode> nodes(TaintNode[] roots) {
return StreamEx.of(roots)
.flatMap(root -> StreamEx.ofTree(root, n -> StreamEx.of(n.myChildren == null ? Collections.emptySet() : n.myChildren)));
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
TaintNode[] roots = tree.getSelectedNodes(TaintNode.class, null);
nodes(roots).forEach(n -> n.setExcluded(myExclude));
tree.repaint();
}
}
return new AnAction[]{
new IncludeExcludeAction(JavaRefactoringBundle.message("type.migration.include.action.text"), false),
new IncludeExcludeAction(JavaRefactoringBundle.message("type.migration.exclude.action.text"), true)
};
}
private static class PropagateTreeListener implements TreeSelectionListener, 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;
}
@Override
public void valueChanged(@NotNull TreeSelectionEvent e) {
TreePath path = e.getPath();
if (path == null) return;
TaintNode taintNode = (TaintNode)path.getLastPathComponent();
myAlarm.cancelAllRequests();
myAlarm.addRequest(() -> updateEditorTexts(taintNode), 300);
}
void updateEditorTexts(@NotNull TaintNode taintNode) {
if (taintNode == myRoot || taintNode.getParent() == null) {
myUsageEditor.show(null);
myMemberEditor.show(null);
return;
}
PsiElement usage = taintNode.getRef();
if (usage == null) return;
PsiElement parentPsi = getParentPsi(usage);
if (parentPsi == null) return;
myUsageEditor.show(parentPsi);
PsiModifierListOwner element = taintNode.getElement();
if (element == null) return;
myMemberEditor.show(element);
PsiElement usageHighlight = PsiTreeUtil.isAncestor(parentPsi, usage, true) ? usage : getIdentifier(parentPsi);
if (usageHighlight != null) myUsageEditor.highlight(parentPsi, usageHighlight);
PsiElement elementHighlight = getIdentifier(element);
if (elementHighlight != null) myMemberEditor.highlight(element, elementHighlight);
}
@Override
public void dispose() {
Disposer.dispose(myUsageEditor);
Disposer.dispose(myMemberEditor);
}
private static class ElementEditor implements Disposable {
private final Editor myEditor;
private final Collection<RangeHighlighter> myHighlighters = new ArrayList<>();
private final String myEmptyText;
private ElementEditor(Editor editor, String emptyText) {
myEditor = editor;
myEmptyText = emptyText;
}
public void show(@Nullable PsiElement element) {
String elementText = getText(element);
String text = elementText == null ? JvmAnalysisBundle.message(myEmptyText) : elementText;
ApplicationManager.getApplication().runWriteAction(() -> myEditor.getDocument().setText(text));
if (element == null) return;
EditorHighlighter highlighter = createHighlighter(element);
((EditorEx)myEditor).setHighlighter(highlighter);
}
public void highlight(@NotNull PsiElement elementInEditor, @NotNull PsiElement toHighlight) {
Project project = elementInEditor.getProject();
HighlightManager highlighter = HighlightManager.getInstance(project);
myHighlighters.forEach(h -> highlighter.removeSegmentHighlighter(myEditor, h));
myHighlighters.clear();
TextRange range = rangeInParent(elementInEditor, toHighlight);
if (range == null) return;
highlighter.addRangeHighlight(myEditor, range.getStartOffset(), range.getEndOffset(),
EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES, false, myHighlighters);
}
@Override
public void dispose() {
EditorFactory.getInstance().releaseEditor(myEditor);
}
private static @Nullable TextRange rangeInParent(@NotNull PsiElement parent, @NotNull PsiElement child) {
int parentStart = getStartOffset(parent);
if (parentStart == -1) return null;
TextRange childRange = child.getTextRange();
return childRange.shiftLeft(parentStart);
}
private static int getStartOffset(@NotNull PsiElement owner) {
PsiFile file = owner.getContainingFile();
Document document = PsiDocumentManager.getInstance(owner.getProject()).getDocument(file);
if (document == null) return -1;
int lineNumber = document.getLineNumber(owner.getTextRange().getStartOffset());
return document.getLineStartOffset(lineNumber);
}
private static @Nullable String getText(@Nullable PsiElement element) {
if (element == null) return null;
PsiFile file = element.getContainingFile();
if (file == null) return null;
Document document = PsiDocumentManager.getInstance(element.getProject()).getDocument(file);
if (document == null) return null;
TextRange textRange = element.getTextRange();
if (textRange == null) return null;
int start = document.getLineStartOffset(document.getLineNumber(textRange.getStartOffset()));
int end = document.getLineEndOffset(document.getLineNumber(textRange.getEndOffset()));
return document.getText().substring(start, end);
}
private static @NotNull EditorHighlighter createHighlighter(@NotNull PsiElement element) {
EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
Project project = element.getProject();
VirtualFile virtualFile = element.getContainingFile().getVirtualFile();
return HighlighterFactory.createHighlighter(virtualFile, scheme, project);
}
}
private static @Nullable PsiElement getIdentifier(@Nullable PsiElement element) {
PsiNameIdentifierOwner namedElement = ObjectUtils.tryCast(element, PsiNameIdentifierOwner.class);
if (namedElement == null) return null;
return namedElement.getNameIdentifier();
}
private static @Nullable PsiElement getParentPsi(@NotNull PsiElement usage) {
@SuppressWarnings("unchecked")
Class<? extends UElement>[] types = new Class[]{UMethod.class, UField.class};
UElement uElement = UastContextKt.getUastParentOfTypes(usage, types);
if (uElement == null) return null;
return uElement.getSourcePsi();
}
}
private static class PropagateTree extends Tree implements DataProvider {
private PropagateTree(TaintNode root) {
super(root);
this.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
this.getSelectionModel().setSelectionPath(new TreePath(root.getPath()));
}
@Override
public @Nullable Object getData(@NotNull String dataId) {
if (!CommonDataKeys.PSI_ELEMENT.is(dataId)) return null;
DefaultMutableTreeNode[] selectedNodes = getSelectedNodes(DefaultMutableTreeNode.class, null);
if (selectedNodes.length != 1) return null;
return selectedNodes[0].getUserObject();
}
@Contract("_ -> new")
private static @NotNull PropagateTree create(@NotNull TaintNode root) {
TaintNode rootWrapper = new TaintNode();
rootWrapper.add(root);
PropagateTree tree = new PropagateTree(rootWrapper);
tree.setRootVisible(false);
tree.setShowsRootHandles(true);
tree.setCellRenderer(new PropagateTree.PropagateTreeRenderer());
TreeUtil.installActions(tree);
TreeUtil.expand(tree, 2);
SmartExpander.installOn(tree);
EditSourceOnDoubleClickHandler.install(tree);
new TreeSpeedSearch(tree);
return tree;
}
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;
PsiModifierListOwner owner = taintNode.getElement();
if (owner == null) {
append(UsageViewBundle.message("node.invalid"), SimpleTextAttributes.ERROR_ATTRIBUTES);
return;
}
int flags = Iconable.ICON_FLAG_VISIBILITY | Iconable.ICON_FLAG_READ_STATUS;
setIcon(ReadAction.compute(() -> owner.getIcon(flags)));
int style = taintNode.isExcluded() ? SimpleTextAttributes.STYLE_STRIKEOUT : SimpleTextAttributes.STYLE_PLAIN;
SimpleTextAttributes attributes = new SimpleTextAttributes(style, null);
PsiMethod psiMethod = ObjectUtils.tryCast(owner, PsiMethod.class);
// TODO: kotlin methods?
if (psiMethod != null) {
PsiMethodRenderingInfo renderingInfo = new PsiMethodRenderingInfo(true);
append(renderingInfo.getPresentableText(psiMethod), attributes);
return;
}
PsiVariable psiVariable = ObjectUtils.tryCast(owner, PsiVariable.class);
if (psiVariable != null) {
String varText = PsiFormatUtil.formatVariable(psiVariable,
PsiFormatUtilBase.SHOW_NAME |
PsiFormatUtilBase.SHOW_TYPE |
PsiFormatUtilBase.TYPE_AFTER,
PsiSubstitutor.EMPTY);
append(varText, attributes);
return;
}
PsiNamedElement namedElement = ObjectUtils.tryCast(owner, PsiNamedElement.class);
if (namedElement == null) return;
String name = namedElement.getName();
if (name == null) return;
append(name, attributes);
}
}
}
}

View File

@@ -0,0 +1,193 @@
// 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.propagate;
import com.intellij.analysis.JvmAnalysisBundle;
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
import com.intellij.codeInspection.sourceToSink.MarkAsSafeFix;
import com.intellij.codeInspection.sourceToSink.TaintAnalyzer;
import com.intellij.codeInspection.sourceToSink.TaintValue;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.ui.content.Content;
import com.intellij.usageView.UsageViewContentManager;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.*;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class PropagateFix extends LocalQuickFixAndIntentionActionOnPsiElement {
private final String myName;
public PropagateFix(@NotNull PsiElement psiElement, @NotNull String name) {
super(psiElement);
myName = name;
}
@Override
public @NotNull String getText() {
return JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.text", myName);
}
@Override
public void invoke(@NotNull Project project,
@NotNull PsiFile file,
@Nullable Editor editor,
@NotNull PsiElement startElement, @NotNull PsiElement endElement) {
UExpression uExpression = UastContextKt.toUElementOfExpectedTypes(startElement, UCallExpression.class, UReferenceExpression.class);
if (uExpression == null) return;
PsiElement reportedElement = uExpression.getSourcePsi();
if (reportedElement == null) return;
TaintAnalyzer analyzer = new TaintAnalyzer();
if (analyzer.analyze(uExpression) != TaintValue.UNKNOWN) return;
PsiModifierListOwner target = ObjectUtils.tryCast(((UResolvable)uExpression).resolve(), PsiModifierListOwner.class);
if (target == null) return;
// TODO: won't work if we start from kotlin
PsiMethod method = PsiTreeUtil.getParentOfType(reportedElement, PsiMethod.class);
if (method == null) return;
TaintNode root = getTree(project, method, target, reportedElement);
if (root == null) return;
String title = JvmAnalysisBundle.message(root.myTaintValue == TaintValue.TAINTED ?
"jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.toolwindow.conflicts.title" :
"jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.toolwindow.title");
if (ApplicationManager.getApplication().isHeadlessEnvironment()) {
if (root.myTaintValue == TaintValue.TAINTED) return;
Set<PsiModifierListOwner> toAnnotate = new HashSet<>();
toAnnotate = PropagateAnnotationPanel.getSelectedElements(root, root, toAnnotate);
if (toAnnotate == null) return;
if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, toAnnotate, false)) return;
annotate(project, title, toAnnotate);
return;
}
Consumer<Collection<PsiModifierListOwner>> callback = toAnnotate -> annotate(project, title, toAnnotate);
PropagateAnnotationPanel panel = new PropagateAnnotationPanel(project, root, callback);
Content content = UsageViewContentManager.getInstance(project).addContent(title, false, panel, true, true);
panel.setContent(content);
ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.FIND);
if (toolWindow != null) toolWindow.activate(null);
}
private static void annotate(Project project, @NlsSafe String actionTitle, @NotNull Collection<PsiModifierListOwner> toAnnotate) {
WriteCommandAction.runWriteCommandAction(project, actionTitle, null,
() -> toAnnotate.forEach(owner -> MarkAsSafeFix.markAsSafe(project, owner)));
}
private static @Nullable TaintNode getTree(Project project, PsiMethod method, PsiModifierListOwner target, PsiElement reportedElement) {
String title = JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.progress.title");
return ProgressManager.getInstance().runProcessWithProgressSynchronously(
() -> ReadAction.compute(() -> skipLocal(setRoot(method, buildTree(target, reportedElement)))),
title, true, project);
}
@Override
public boolean startInWriteAction() {
return false;
}
@Override
public @NotNull String getFamilyName() {
return JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.family");
}
private static @NotNull TaintNode buildTree(@NotNull PsiModifierListOwner target, @NotNull PsiElement ref) {
Deque<TaintNode> elements = new ArrayDeque<>();
TaintNode root = new TaintNode(target, ref, null, null);
for (TaintNode taintNode = root; taintNode != null; taintNode = elements.poll()) {
if (taintNode.myTaintValue != null) continue;
PsiModifierListOwner element = taintNode.getElement();
if (element == null) continue;
PsiElement elementRef = taintNode.getRef();
if (elementRef == null) continue;
TaintAnalyzer taintAnalyzer = new TaintAnalyzer();
TaintValue taintValue = taintAnalyzer.fromElement(element, elementRef, true);
taintNode.myTaintValue = taintValue;
if (taintValue == TaintValue.UNTAINTED) continue;
if (taintValue == TaintValue.TAINTED) {
propagateTaintedUp(taintNode.myParent);
continue;
}
Set<PsiModifierListOwner> parents = collectParents(taintNode);
TaintNode parentNode = taintNode;
Set<TaintNode> children = taintAnalyzer.getNonMarkedElements().stream()
.filter(c -> !parents.contains(c.myNonMarked))
.map(c -> new TaintNode(c.myNonMarked, c.myRef, parentNode, null))
.collect(Collectors.toSet());
elements.addAll(children);
parentNode.myChildren = children;
}
if (root.myTaintValue == TaintValue.TAINTED) skipNonTainted(root);
return root;
}
private static @NotNull Set<PsiModifierListOwner> collectParents(@NotNull TaintNode taintNode) {
Set<PsiModifierListOwner> parents = new HashSet<>();
while (taintNode != null) {
PsiModifierListOwner parent = taintNode.getElement();
if (parent != null) parents.add(parent);
taintNode = taintNode.myParent;
}
return parents;
}
private static void skipNonTainted(@NotNull TaintNode root) {
Set<TaintNode> children = root.myChildren;
if (children == null) return;
children.removeIf(c -> c.myTaintValue != TaintValue.TAINTED);
children.forEach(c -> skipNonTainted(c));
}
private static @Nullable TaintNode skipLocal(@NotNull TaintNode root) {
Deque<TaintNode> taintNodes = new ArrayDeque<>();
for (TaintNode taintNode = root; taintNode != null; taintNode = taintNodes.poll()) {
if (taintNode.myChildren == null) continue;
Set<TaintNode> children = new HashSet<>();
Deque<TaintNode> workList = new ArrayDeque<>(taintNode.myChildren);
for (TaintNode childElement = workList.poll(); childElement != null; childElement = workList.poll()) {
PsiModifierListOwner child = childElement.getElement();
if (child == null) return null;
if (child instanceof PsiLocalVariable) {
if (childElement.myChildren != null) workList.addAll(childElement.myChildren);
}
else {
childElement.myParent = taintNode;
childElement.setParent(taintNode);
children.add(childElement);
taintNodes.add(childElement);
}
}
taintNode.myChildren = children;
}
return root;
}
private static @NotNull TaintNode setRoot(@NotNull PsiModifierListOwner element, @NotNull TaintNode root) {
TaintNode newRoot = new TaintNode(element, null, null, root.myTaintValue.join(TaintValue.UNKNOWN));
root.myParent = newRoot;
newRoot.myChildren = new HashSet<>();
newRoot.myChildren.add(root);
return newRoot;
}
private static void propagateTaintedUp(@Nullable TaintNode taintNode) {
while (taintNode != null) {
taintNode.myTaintValue = TaintValue.TAINTED;
taintNode = taintNode.myParent;
}
}
}

View File

@@ -0,0 +1,88 @@
// 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.propagate;
import com.intellij.codeInspection.sourceToSink.TaintValue;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import java.util.Set;
import java.util.Vector;
// TODO: equals and hashcode
class TaintNode extends DefaultMutableTreeNode {
private final SmartPsiElementPointer<PsiModifierListOwner> myElement;
private final SmartPsiElementPointer<PsiElement> myRef;
TaintNode myParent;
Set<TaintNode> myChildren;
TaintValue myTaintValue;
private boolean isExcluded;
TaintNode() {
myRef = null;
myElement = null;
}
TaintNode(@NotNull PsiModifierListOwner element, @Nullable PsiElement ref,
@Nullable TaintNode parent, @Nullable TaintValue taintValue) {
super(element);
myElement = SmartPointerManager.createPointer(element);
myRef = ref == null ? null : SmartPointerManager.createPointer(ref);
myParent = parent;
myTaintValue = taintValue;
}
private void buildChildren() {
if (children == null) {
//noinspection UseOfObsoleteCollectionType
children = myChildren == null ? new Vector<>() : new Vector<>(myChildren);
}
}
@Nullable PsiModifierListOwner getElement() {
return myElement == null ? null : myElement.getElement();
}
@Nullable PsiElement getRef() {
return myRef == null ? null : myRef.getElement();
}
@Override
public TreeNode getChildAt(int index) {
buildChildren();
return super.getChildAt(index);
}
@Override
public int getChildCount() {
buildChildren();
return super.getChildCount();
}
@Override
public boolean isLeaf() {
if (children == null) {
return false;
}
return super.isLeaf();
}
@Override
public int getIndex(TreeNode aChild) {
buildChildren();
return super.getIndex(aChild);
}
boolean isExcluded() {
return isExcluded;
}
void setExcluded(boolean excluded) {
isExcluded = excluded;
}
}

View File

@@ -2,81 +2,80 @@ package org.checkerframework.checker.tainting.qual;
public class LocalInference {
void simpleInit() {
String s1 = source();
String s = s1;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void simpleInit() {
String s1 = source();
String s = s1;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void concatInit() {
String s1 = "foo" + source() + "bar";
String s = "foo" + s1 + "bar";
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void concatInit() {
String s1 = "foo" + source() + "bar";
String s = "foo" + s1 + "bar";
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void recursive() {
String s1 = source();
String s = s1;
s1 = s;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void recursive() {
String s1 = source();
String s = s1;
s1 = s;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void ternary(boolean b) {
String s1 = b ? source() : "";
String s = s1;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void ternary(boolean b) {
String s1 = b ? source() : "";
String s = s1;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void nestedTernary(boolean b, boolean c) {
String s1 = b ? c ? "" : (source()) : "";
String s = s1;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void nestedTernary(boolean b, boolean c) {
String s1 = b ? c ? "" : (source()) : "";
String s = s1;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void blocks() {
String s1;
{
s1 = source();
void blocks() {
String s1;
{
String s = s1;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
s1 = source();
{
String s = s1;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
}
}
}
void transitive(boolean b) {
String s2;
{
s2 = b ? b ? (source() + source()) : "" : "";
void transitive(boolean b) {
String s2;
{
s2 = b ? b ? (source() + source()) : "" : "";
}
String s1 = ((s2) + (safe()));
String s = s1;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
String s1 = ((s2) + (safe()));
String s = s1;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void transitiveRecursive(boolean b) {
String s = "";
String s1 = s;
String s2 = s1 + foo();
String s3 = s2;
if (b) s = s3;
sink(<warning descr="Unknown string is passed to safe method">s</warning>);
}
void transitiveRecursive(boolean b) {
String s = "";
String s1 = s;
String s2 = s1 + foo();
String s3 = s2;
if (b) s = s3;
sink(<warning descr="Unknown string is passed to safe method">s</warning>);
}
String foo() {
return "";
}
String foo() {
return "";
}
@Untainted
String safe() {
return "safe";
}
@Untainted
String safe() {
return "safe";
}
@Tainted
String source() {
return "tainted";
}
@Tainted
String source() {
return "tainted";
}
void sink(@Untainted String s1) {
}
void sink(@Untainted String s1) {}
}

View File

@@ -2,123 +2,123 @@ package org.checkerframework.checker.tainting.qual;
public class Simple {
void simple() {
String s = source();
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void simple() {
String s = source();
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void alias() {
String s1 = source();
String s = s1;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void alias() {
String s1 = source();
String s = s1;
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void unknown() {
String s = foo();
sink(<warning descr="Unknown string is passed to safe method">s</warning>);
}
void unknown() {
String s = foo();
sink(<warning descr="Unknown string is passed to safe method">s</warning>);
}
void literalOnly() {
String s = null;
s = "safe";
sink(s);
}
void literalOnly() {
String s = null;
s = "safe";
sink(s);
}
void safeCall() {
String s = "safe";
s = safe();
sink(s);
}
void safeCall() {
String s = "safe";
s = safe();
sink(s);
}
void sourceCallToSink() {
sink(<warning descr="Unsafe string is passed to safe method">source()</warning>);
}
void sourceCallToSink() {
sink(<warning descr="Unsafe string is passed to safe method">source()</warning>);
}
void safeCallToSink() {
sink(safe());
}
void safeCallToSink() {
sink(safe());
}
void sourceFromClass() {
String s = (new WithSourceParent()).source();
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void sourceFromChildClass() {
WithSourceChild child = new WithSourceChild();
String s = child.source();
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void sourceFromClass() {
String s = (new WithSourceParent()).source();
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void withParenthesis() {
String s1 = (source());
s1 = (foo());
String s = (s1);
sink((<warning descr="Unsafe string is passed to safe method">s</warning>));
}
@Untainted String unsafeReturn() {
return <warning descr="Unsafe string is returned from safe method">source()</warning>;
}
void sourceToSafeString() {
@Untainted String s = "safe";
s = <warning descr="Unsafe string is passed to safe method">source()</warning>;
}
void sourceFromChildClass() {
WithSourceChild child = new WithSourceChild();
String s = child.source();
sink(<warning descr="Unsafe string is passed to safe method">s</warning>);
}
void unsafeConcat() {
@Tainted String s = source();
String s1 = "safe";
String s2 = "safe2";
sink(s1 + <warning descr="Unsafe string is passed to safe method">s</warning> + s2);
}
void unsafeTernary(boolean b) {
@Tainted String s = source();
sink(b ? <warning descr="Unsafe string is passed to safe method">s</warning> : null);
}
void withParenthesis() {
String s1 = (source());
s1 = (foo());
String s = (s1);
sink((<warning descr="Unsafe string is passed to safe method">s</warning>));
}
void fieldFromGetter() {
String s = getField();
sink(<warning descr="Unknown string is passed to safe method">s</warning>);
}
private final String field = foo();
public String getField() {
return field;
}
String callSource() {
return source();
}
@Untainted String unsafeReturn() {
return <warning descr="Unsafe string is returned from safe method">source()</warning>;
}
String foo() {
return "some";
}
@Untainted
String safe() {
return "safe";
}
@Tainted
String source() {
return "tainted";
}
void sink(@Untainted String s1) {}
void sourceToSafeString() {
@Untainted String s = "safe";
s = <warning descr="Unsafe string is passed to safe method">source()</warning>;
}
void unsafeConcat() {
@Tainted String s = source();
String s1 = "safe";
String s2 = "safe2";
sink(s1 + <warning descr="Unsafe string is passed to safe method">s</warning> + s2);
}
void unsafeTernary(boolean b) {
@Tainted String s = source();
sink(b ? <warning descr="Unsafe string is passed to safe method">s</warning> : null);
}
void fieldFromGetter() {
String s = getField();
sink(<warning descr="Unknown string is passed to safe method">s</warning>);
}
private final String field = foo();
public String getField() {
return field;
}
String callSource() {
return source();
}
String foo() {
return "some";
}
@Untainted
String safe() {
return "safe";
}
class WithSourceParent {
@Tainted
String source() {
return "tainted";
}
}
class WithSourceChild extends WithSourceParent {
@Override
String source() {
return super.source();
void sink(@Untainted String s1) {}
class WithSourceParent {
@Tainted
String source() {
return "tainted";
}
}
class WithSourceChild extends WithSourceParent {
@Override
String source() {
return super.source();
}
}
}
}

View File

@@ -3,16 +3,16 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
String s = foo();
String alias = s;
sink(alias);
}
void simple() {
String s = foo();
String alias = s;
sink(alias);
}
@Untainted String foo() {
return "foo";
}
@Untainted String foo() {
return "foo";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -3,17 +3,17 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
String s = foo();
String s1 = s;
String s2 = s1;
sink(s2);
}
void simple() {
String s = foo();
String s1 = s;
String s2 = s1;
sink(s2);
}
@Untainted String foo() {
return "foo";
}
@Untainted String foo() {
return "foo";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -3,17 +3,18 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
private @Untainted String s = foo();
@Untainted
private String s = foo();
void simple() {
String s2 = s;
sink(s2);
}
void simple() {
String s2 = s;
sink(s2);
}
String foo() {
return "foo";
}
String foo() {
return "foo";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -3,14 +3,14 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
sink(foo());
}
void simple() {
sink(foo());
}
@Untainted String foo() {
return "foo";
}
@Untainted String foo() {
return "foo";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -3,21 +3,21 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
String s1 = other();
String s2 = s1;
s2 = foo();
sink(s2);
}
void simple() {
String s1 = other();
String s2 = s1;
s2 = foo();
sink(s2);
}
@Untainted String foo() {
return "foo";
}
@Untainted String other() {
return "other";
}
@Untainted String foo() {
return "foo";
}
void sink(@Untainted String s1) {}
@Untainted String other() {
return "other";
}
void sink(@Untainted String s1) {}
}

View File

@@ -3,15 +3,14 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
String s = foo();
sink(s);
}
@Untainted String foo() {
return "foo";
}
void simple() {
String s = foo();
sink(s);
}
void sink(@Untainted String s1) {}
@Untainted String foo() {
return "foo";
}
void sink(@Untainted String s1) {}
}

View File

@@ -3,16 +3,16 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
String s = foo();
String alias = s;
sink(<caret>alias);
}
void simple() {
String s = foo();
String alias = s;
sink(<caret>alias);
}
String foo() {
return "foo";
}
String foo() {
return "foo";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -3,17 +3,17 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
String s = foo();
String s1 = s;
String s2 = s1;
sink(<caret>s2);
}
void simple() {
String s = foo();
String s1 = s;
String s2 = s1;
sink(<caret>s2);
}
String foo() {
return "foo";
}
String foo() {
return "foo";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -3,17 +3,17 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
private String s = foo();
private String s = foo();
void simple() {
String s2 = s;
sink(<caret>s2);
}
void simple() {
String s2 = s;
sink(<caret>s2);
}
String foo() {
return "foo";
}
String foo() {
return "foo";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -3,14 +3,14 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
sink(<caret>foo());
}
void simple() {
sink(<caret>foo());
}
String foo() {
return "foo";
}
String foo() {
return "foo";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -3,21 +3,21 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
String s1 = other();
String s2 = s1;
s2 = foo();
sink(<caret>s2);
}
void simple() {
String s1 = other();
String s2 = s1;
s2 = foo();
sink(<caret>s2);
}
String foo() {
return "foo";
}
String foo() {
return "foo";
}
String other() {
return "other";
}
String other() {
return "other";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -3,18 +3,18 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
String s1 = foo();
String s = foo();
s = "test";
s = s1;
sink(<caret>s);
}
void simple() {
String s1 = foo();
String s = foo();
s = "test";
s = s1;
sink(<caret>s);
}
@Untainted String foo() {
return "foo";
}
@Untainted String foo() {
return "foo";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -3,15 +3,14 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
String s = foo();
sink(<caret>s);
}
void simple() {
String s = foo();
sink(<caret>s);
}
String foo() {
return "foo";
}
String foo() {
return "foo";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -3,18 +3,18 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
sink(<caret>source());
}
void simple() {
sink(<caret>source());
}
String foo() {
return "foo";
}
String foo() {
return "foo";
}
@Tainted String source() {
return "source";
}
@Tainted String source() {
return "source";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -3,21 +3,21 @@ package org.checkerframework.checker.tainting.qual;
class Simple {
void simple() {
String s1 = source();
String s2 = s1;
s2 = foo();
sink(<caret>s2);
}
void simple() {
String s1 = source();
String s2 = s1;
s2 = foo();
sink(<caret>s2);
}
String foo() {
return "foo";
}
String foo() {
return "foo";
}
@Tainted String source() {
return "source";
}
@Tainted String source() {
return "source";
}
void sink(@Untainted String s1) {}
void sink(@Untainted String s1) {}
}

View File

@@ -0,0 +1,28 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
void test() {
String s = callAnother();
sink(s);
}
@Untainted String callAnother() {
Another another = new Another();
return ((another.foo()) + (another.foo()));
}
@Untainted
static String bar() {
return "safe";
}
void sink(@Untainted String s) {}
}
class Another {
@Untainted String foo() {
return Simple.bar();
}
}

View File

@@ -0,0 +1,26 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
String field = "safe";
void test() {
String s = foo();
sink(<caret>s);
}
String foo() {
return field;
}
void setFieldToSource() {
this.field = source();
}
@Tainted String source() {
return "unsafe";
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,26 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@Untainted String field = "safe";
void test() {
String s = foo();
sink(s);
}
@Untainted String foo() {
return field;
}
void setFieldToBar() {
this.field = bar();
}
@Untainted String bar() {
return "safe";
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@Tainted String field = "safe";
void test() {
String s = foo();
sink(<caret>s);
}
String foo() {
return field;
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@Untainted String field = "safe";
void test() {
String s = foo();
sink(s);
}
@Untainted String foo() {
return field;
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,20 @@
// "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 source();
}
@Tainted String source() {
return "unsafe";
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,20 @@
// "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) {}
}

View File

@@ -0,0 +1,11 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
void test(@Untainted String s) {
sink(s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@Tainted String field = "unsafe";
void callTest() {
String s = field;
test(s);
}
void test(String s) {
sink(s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@Untainted String field = "safe";
void callTest() {
String s = field;
test(s);
}
void test(@Untainted String s) {
sink(s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,20 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
void callTest() {
String s = source();
test(s);
}
@Tainted String source() {
return "unsafe";
}
void test(String s) {
sink(<caret>s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,20 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
void callTest() {
String s = foo();
test(s);
}
@Untainted String foo() {
return "safe";
}
void test(@Untainted String s) {
sink(s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 'id'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
void test() {
String s = "";
String s1 = id(id(s));
s += s1;
sink(id(s));
}
@Untainted String id(String s) {
return id(s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,26 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@Untainted String field = baz();
void test(boolean b) {
String s = b ? foo() : field;
sink(s);
}
@Untainted String foo() {
return bar();
}
@Untainted String bar() {
return baz();
}
@Untainted String baz() {
return foo();
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,20 @@
// "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) {}
}

View File

@@ -0,0 +1,18 @@
// "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";
}
}

View File

@@ -0,0 +1,14 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@Untainted String field = "safe";
void test() {
String s = field;
sink(s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
String field = foo();
void test() {
String s = foo();
sink(s);
}
@Untainted String foo() {
return "safe";
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,27 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
void test() {
String s = callAnother();
sink(<caret>s);
}
String callAnother() {
Another another = new Another();
return ((another.foo()) + (another.foo()));
}
static String bar() {
return "safe";
}
void sink(@Untainted String s) {}
}
class Another {
String foo() {
return Simple.bar();
}
}

View File

@@ -0,0 +1,26 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
String field = "safe";
void test() {
String s = foo();
sink(<caret>s);
}
String foo() {
return field;
}
void setFieldToSource() {
this.field = source();
}
@Tainted String source() {
return "unsafe";
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,26 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
String field = "safe";
void test() {
String s = foo();
sink(<caret>s);
}
String foo() {
return field;
}
void setFieldToBar() {
this.field = bar();
}
String bar() {
return "safe";
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@Tainted String field = "safe";
void test() {
String s = foo();
sink(<caret>s);
}
String foo() {
return field;
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
String field = "safe";
void test() {
String s = foo();
sink(<caret>s);
}
String foo() {
return field;
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,20 @@
// "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 source();
}
@Tainted String source() {
return "unsafe";
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,20 @@
// "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) {}
}

View File

@@ -0,0 +1,11 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
void test(String s) {
sink(<caret>s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@Tainted String field = "unsafe";
void callTest() {
String s = field;
test(s);
}
void test(String s) {
sink(<caret>s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
String field = "safe";
void callTest() {
String s = field;
test(s);
}
void test(String s) {
sink(<caret>s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,20 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
void callTest() {
String s = source();
test(s);
}
@Tainted String source() {
return "unsafe";
}
void test(String s) {
sink(<caret>s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,20 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
void callTest() {
String s = foo();
test(s);
}
String foo() {
return "safe";
}
void test(String s) {
sink(<caret>s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 'id'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
void test() {
String s = "";
String s1 = id(id(s));
s += s1;
sink(id(<caret>s));
}
String id(String s) {
return id(s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,26 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
String field = baz();
void test(boolean b) {
String s = b ? foo() : field;
sink(<caret>s);
}
String foo() {
return bar();
}
String bar() {
return baz();
}
String baz() {
return foo();
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,20 @@
// "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) {}
}

View File

@@ -0,0 +1,14 @@
// "Propagate safe annotation from 's'" "false"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
@Tainted String field = "unsafe";
void test() {
String s = field;
sink(<caret>s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 's'" "false"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
String field = foo();
void test() {
String s = source();
sink(<caret>s);
}
@Tainted String source() {
return "unsafe";
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,14 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
String field = "safe";
void test() {
String s = field;
sink(<caret>s);
}
void sink(@Untainted String s) {}
}

View File

@@ -0,0 +1,18 @@
// "Propagate safe annotation from 's'" "true"
import org.checkerframework.checker.tainting.qual.*;
class Simple {
String field = foo();
void test() {
String s = foo();
sink(<caret>s);
}
String foo() {
return "safe";
}
void sink(@Untainted String s) {}
}

View File

@@ -1,65 +0,0 @@
package com.intellij.codeInspection.tests.java;
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.command.WriteCommandAction;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
public class MarkAsSafeFixTest extends LightQuickFixParameterizedTestCase {
@Override
protected LocalInspectionTool @NotNull [] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new SourceToSinkFlowInspection()};
}
@Override
protected void setUp() throws Exception {
super.setUp();
WriteCommandAction.runWriteCommandAction(getProject(), new Runnable() {
@Override
public void run() {
try {
VirtualFile annotations = getSourceRoot()
.createChildDirectory(this, "org")
.createChildDirectory(this, "checkerframework")
.createChildDirectory(this, "checker")
.createChildDirectory(this, "tainting")
.createChildDirectory(this, "qual");
VirtualFile untainted = annotations.createChildData(this, "Untainted.java");
VfsUtil.saveText(untainted, " package org.checkerframework.checker.tainting.qual;\n" +
" import java.lang.annotation.ElementType;\n" +
" import java.lang.annotation.Target;\n" +
" @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})\n" +
" public @interface Untainted {\n" +
" }");
VirtualFile tainted = annotations.createChildData(this, "Tainted.java");
VfsUtil.saveText(tainted, " package org.checkerframework.checker.tainting.qual;\n" +
" import java.lang.annotation.ElementType;\n" +
" import java.lang.annotation.Target;\n" +
" @Target({ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.METHOD})\n" +
" public @interface Tainted {\n" +
" }");
}
catch (IOException ignored) {
}
}
});
}
@Override
protected String getBasePath() {
return "/codeInspection/sourceToSinkFlow/markAsSafe";
}
@Override
protected @NotNull String getTestDataPath() {
return PathManager.getCommunityHomePath() + JavaJvmAnalysisTestUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH;
}
}

View File

@@ -0,0 +1,8 @@
package com.intellij.codeInspection.tests.java.sourceToSink;
public class MarkAsSafeFixTest extends SourceToSinkFixBaseTest {
@Override
protected String getBasePath() {
return "/codeInspection/sourceToSinkFlow/markAsSafe";
}
}

View File

@@ -0,0 +1,13 @@
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");
}
}

View File

@@ -0,0 +1,41 @@
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;
}
}

View File

@@ -1,5 +1,5 @@
// 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.tests.java;
package com.intellij.codeInspection.tests.java.sourceToSink;
import com.intellij.codeInspection.sourceToSink.SourceToSinkFlowInspection;
import com.intellij.jvm.analysis.JavaJvmAnalysisTestUtil;