mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
SourceToSinkFlowInspection: added fix to propagate safe annotation
GitOrigin-RevId: 03b5b73ec32510624858245e9024152a7341b3ff
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d7345dde4f
commit
40936f24bf
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.intellij.codeInspection.tests.java.sourceToSink;
|
||||
|
||||
public class MarkAsSafeFixTest extends SourceToSinkFixBaseTest {
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/codeInspection/sourceToSinkFlow/markAsSafe";
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user