diff --git a/java/java-analysis-api/src/com/intellij/codeInspection/problemHolderUtil.kt b/java/java-analysis-api/src/com/intellij/codeInspection/problemHolderUtil.kt
index 76c696ef98d1..2e16ff0859aa 100644
--- a/java/java-analysis-api/src/com/intellij/codeInspection/problemHolderUtil.kt
+++ b/java/java-analysis-api/src/com/intellij/codeInspection/problemHolderUtil.kt
@@ -2,10 +2,7 @@
package com.intellij.codeInspection
import com.intellij.codeInspection.util.InspectionMessage
-import org.jetbrains.uast.UAnchorOwner
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UDeclaration
-import org.jetbrains.uast.UReferenceExpression
+import org.jetbrains.uast.*
@JvmOverloads
fun ProblemsHolder.registerUProblem(
@@ -49,4 +46,15 @@ fun ProblemsHolder.registerUProblem(
) {
val anchor = element.referenceNameElement?.sourcePsi ?: return
registerProblem(anchor, descriptionTemplate, highlightType, *fixes)
+}
+
+@JvmOverloads
+fun ProblemsHolder.registerUProblem(
+ element: UExpression,
+ descriptionTemplate: @InspectionMessage String,
+ vararg fixes: LocalQuickFix,
+ highlightType: ProblemHighlightType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING) {
+ val anchor = element.sourcePsi ?: return
+ if (anchor.textLength == 0) return
+ registerProblem(anchor, descriptionTemplate, highlightType, *fixes)
}
\ No newline at end of file
diff --git a/jvm/jvm-analysis-impl/resources/inspectionDescriptions/SourceToSinkFlow.html b/jvm/jvm-analysis-impl/resources/inspectionDescriptions/SourceToSinkFlow.html
index 63de49f98c4e..edbf9a9c2efe 100644
--- a/jvm/jvm-analysis-impl/resources/inspectionDescriptions/SourceToSinkFlow.html
+++ b/jvm/jvm-analysis-impl/resources/inspectionDescriptions/SourceToSinkFlow.html
@@ -1,8 +1,8 @@
Reports cases when non-safe string is passed to a method with parameter marked with @Untainted annotations, returned from annotated methods
-or assigned to annotated fields, parameters or local variables.
+or assigned to annotated fields, parameters or local variables. Kotlin `set` and `get` methods for fields are not supported as entry points.
- Safe object is:
+Safe object is:
- a string literal, interface instance or enum object
- a result of a call of a method that is marked as
@Untainted
diff --git a/jvm/jvm-analysis-impl/resources/messages/JvmAnalysisBundle.properties b/jvm/jvm-analysis-impl/resources/messages/JvmAnalysisBundle.properties
index 1b2fd865fe52..716b36e50d3f 100644
--- a/jvm/jvm-analysis-impl/resources/messages/JvmAnalysisBundle.properties
+++ b/jvm/jvm-analysis-impl/resources/messages/JvmAnalysisBundle.properties
@@ -76,6 +76,7 @@ jvm.inspections.source.to.sink.flow.assigned.unsafe=Unsafe string is assigned to
jvm.inspections.source.to.sink.flow.assigned.unknown=Unknown string is assigned to safe variable
jvm.inspections.source.to.sink.flow.common.unsafe=Unsafe string is used in a safe context
jvm.inspections.source.to.sink.flow.common.unknown=Unknown string is used in a safe context
+jvm.inspections.source.to.sink.flow.too.complex=Too complex to check that the string is safe in a safe context
jvm.inspections.source.unsafe.to.sink.flow.mark.as.safe.family=Mark as requiring validation
jvm.inspections.source.unsafe.to.sink.flow.mark.as.safe.text=Mark elements as requiring validation
jvm.inspections.source.unsafe.to.sink.flow.mark.as.safe.command.name=Mark as Requiring Validation
diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingStringPartEvaluator.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingStringPartEvaluator.kt
index ec285e433903..de9a7f50edb1 100644
--- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingStringPartEvaluator.kt
+++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/logging/LoggingStringPartEvaluator.kt
@@ -109,7 +109,7 @@ internal class LoggingStringPartEvaluator {
val visitor = object : AbstractUastVisitor() {
val used: MutableSet = mutableSetOf()
override fun visitBinaryExpression(node: UBinaryExpression): Boolean {
- if (node.operator == UastBinaryOperator.ASSIGN) {
+ if (node.operator is UastBinaryOperator.AssignOperator) {
val leftOperand = node.leftOperand
if (leftOperand is USimpleNameReferenceExpression) {
val resolveToUElement = leftOperand.resolveToUElement()
diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/MarkAsSafeFix.java b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/MarkAsSafeFix.java
index c2c1eeb0e7f4..6c738594faa4 100644
--- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/MarkAsSafeFix.java
+++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/MarkAsSafeFix.java
@@ -16,10 +16,7 @@ import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.command.undo.BasicUndoableAction;
import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.project.Project;
-import com.intellij.psi.PsiAnnotation;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiModifierListOwner;
+import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.refactoring.ui.ConflictsDialog;
import com.intellij.util.ObjectUtils;
@@ -28,9 +25,7 @@ import com.intellij.util.containers.MultiMap;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.jetbrains.uast.UElement;
-import org.jetbrains.uast.UExpression;
-import org.jetbrains.uast.UastContextKt;
+import org.jetbrains.uast.*;
import java.util.*;
import java.util.stream.Collectors;
@@ -78,13 +73,21 @@ class MarkAsSafeFix extends LocalQuickFixOnPsiElement {
@Nullable
private List getElementsToMark(@NotNull UExpression uExpression) {
TaintAnalyzer taintAnalyzer = new TaintAnalyzer(myTaintValueFactory);
- TaintValue taintValue = taintAnalyzer.analyzeExpression(uExpression, false);
- if (taintValue != TaintValue.UNKNOWN) return null;
- return ContainerUtil.map(taintAnalyzer.getNonMarkedElements(), e -> e.myNonMarked);
+ try {
+ TaintValue taintValue = taintAnalyzer.analyzeExpression(uExpression, false);
+ if (taintValue != TaintValue.UNKNOWN) return null;
+ }
+ catch (DeepTaintAnalyzerException e) {
+ return null;
+ }
+ List elements = new ArrayList<>(ContainerUtil.map(taintAnalyzer.getNonMarkedElements(), e -> e.myNonMarked));
+ ContainerUtil.removeDuplicates(elements);
+ return elements;
}
@Override
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) {
+ //noinspection unchecked
UExpression uExpression =
(UExpression)UastContextKt.getUastParentOfTypes(previewDescriptor.getStartElement(), new Class[]{UExpression.class});
@@ -335,6 +338,12 @@ class MarkAsSafeFix extends LocalQuickFixOnPsiElement {
return true;
}
}
+ UElement sourceUElement = UastContextKt.toUElement(element);
+ if (element instanceof PsiMethod &&
+ sourceUElement != null &&
+ UastContextKt.toUElement(sourceUElement.getSourcePsi()) instanceof UField uField) {
+ element = uField.getJavaPsi();
+ }
JvmModifiersOwner jvmModifiersOwner = ObjectUtils.tryCast(element, JvmModifiersOwner.class);
if (jvmModifiersOwner == null) return false;
AnnotationRequest request = AnnotationRequestsKt.annotationRequest(annotation);
diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/PropagateFix.java b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/PropagateFix.java
index b3a984d83b36..b2f3eaa275e4 100644
--- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/PropagateFix.java
+++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/PropagateFix.java
@@ -34,9 +34,9 @@ public class PropagateFix extends LocalQuickFixAndIntentionActionOnPsiElement {
private final boolean supportRefactoring;
- PropagateFix(@NotNull PsiElement sourcePsi,
- @NotNull TaintValueFactory taintValueFactory,
- boolean supportRefactoring) {
+ PropagateFix(@NotNull PsiElement sourcePsi,
+ @NotNull TaintValueFactory taintValueFactory,
+ boolean supportRefactoring) {
super(sourcePsi);
myTaintValueFactory = taintValueFactory;
this.supportRefactoring = supportRefactoring;
@@ -68,7 +68,13 @@ public class PropagateFix extends LocalQuickFixAndIntentionActionOnPsiElement {
PsiElement reportedElement = uExpression.getSourcePsi();
if (reportedElement == null) return;
TaintAnalyzer analyzer = new TaintAnalyzer(myTaintValueFactory);
- if (analyzer.analyzeExpression(uExpression, false) != TaintValue.UNKNOWN) return;
+ try {
+ TaintValue value = analyzer.analyzeExpression(uExpression, false);
+ if (value != TaintValue.UNKNOWN) return;
+ }
+ catch (DeepTaintAnalyzerException e) {
+ return;
+ }
PsiElement target = ((UResolvable)uExpression).resolve();
TaintNode root = new TaintNode(null, target, reportedElement, myTaintValueFactory, true);
if (ApplicationManager.getApplication().isUnitTestMode()) {
diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/SourceToSinkFlowInspection.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/SourceToSinkFlowInspection.kt
index fdcc83cee344..f771dfde097e 100644
--- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/SourceToSinkFlowInspection.kt
+++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/SourceToSinkFlowInspection.kt
@@ -75,7 +75,7 @@ class SourceToSinkFlowInspection : AbstractBaseUastLocalInspectionTool() {
isOnTheFly: Boolean,
session: LocalInspectionToolSession): PsiElementVisitor {
val scope = GlobalSearchScope.allScope(holder.project)
- val firstAnnotation: String = untaintedAnnotations.firstOrNull() {
+ val firstAnnotation: String = untaintedAnnotations.firstOrNull {
it != null && JavaPsiFacade.getInstance(holder.project).findClass(it, scope) != null
} ?: return PsiElementVisitor.EMPTY_VISITOR
@@ -94,7 +94,9 @@ class SourceToSinkFlowInspection : AbstractBaseUastLocalInspectionTool() {
UReturnExpression::class.java,
UBinaryExpression::class.java,
ULocalVariable::class.java,
- UField::class.java),
+ UField::class.java,
+ UDeclarationsExpression::class.java,
+ UParameter::class.java),
directOnly = true)
@@ -115,6 +117,22 @@ class SourceToSinkFlowInspection : AbstractBaseUastLocalInspectionTool() {
return super.visitReturnExpression(node)
}
+ override fun visitDeclarationsExpression(node: UDeclarationsExpression): Boolean {
+ node.declarations.forEach {
+ when (it) {
+ is UParameter -> processExpression(it.uastInitializer)
+ is ULocalVariable -> processExpression(it.uastInitializer)
+ is UField -> processExpression(it.uastInitializer)
+ }
+ }
+ return super.visitDeclarationsExpression(node)
+ }
+
+ override fun visitParameter(node: UParameter): Boolean {
+ processExpression(node.uastInitializer)
+ return super.visitParameter(node)
+ }
+
override fun visitLocalVariable(node: ULocalVariable): Boolean {
processExpression(node.uastInitializer)
return super.visitLocalVariable(node)
@@ -140,7 +158,14 @@ class SourceToSinkFlowInspection : AbstractBaseUastLocalInspectionTool() {
val contextValue: TaintValue = factory.of(annotationContext)
if (contextValue !== TaintValue.UNTAINTED) return
val taintAnalyzer = TaintAnalyzer(factory)
- var taintValue = taintAnalyzer.analyzeExpression(expression, false)
+ var taintValue = try {
+ taintAnalyzer.analyzeExpression(expression, false)
+ }
+ catch (e: DeepTaintAnalyzerException) {
+ val errorMessage: String = JvmAnalysisBundle.message("jvm.inspections.source.to.sink.flow.too.complex")
+ holder.registerUProblem(expression, errorMessage, *arrayOf(), highlightType = ProblemHighlightType.WEAK_WARNING)
+ return
+ }
taintValue = taintValue.join(contextValue)
if (taintValue === TaintValue.UNTAINTED) return
val errorMessage = JvmAnalysisBundle.message(taintValue.getErrorMessage(annotationContext))
diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/TaintAnalyzer.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/TaintAnalyzer.kt
index a5869eca55e4..4df32dda9fb4 100644
--- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/TaintAnalyzer.kt
+++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/TaintAnalyzer.kt
@@ -9,13 +9,13 @@ import com.intellij.psi.util.InheritanceUtil
import com.intellij.psi.util.PsiUtil
import com.intellij.util.SmartList
import com.siyeh.ig.psiutils.ClassUtils
-import com.siyeh.ig.psiutils.ExpressionUtils
import one.util.streamex.MoreCollectors
import one.util.streamex.StreamEx
import org.jetbrains.uast.*
import org.jetbrains.uast.UastBinaryOperator.AssignOperator
import org.jetbrains.uast.visitor.AbstractUastVisitor
import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
import java.util.stream.Collector
class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
@@ -24,15 +24,15 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
private val myVisitedMethods: MutableMap>, TaintValue> = HashMap()
private val myNonMarkedElements: MutableList = ArrayList()
- //todo add test for new feature
private val skipClasses: Set = myTaintValueFactory.getConfiguration()
.skipClasses
.filterNotNull()
.toSet()
+ @Throws(DeepTaintAnalyzerException::class)
fun analyzeExpression(expression: UExpression, processRecursively: Boolean): TaintValue {
val file = expression.getContainingUFile() ?: return TaintValue.UNKNOWN
- val context = AnalyzeContext(file, processRecursively, false, 0, 20, true)
+ val context = AnalyzeContext.create(file, processRecursively, false, 1, 30, 2, true)
return fromExpressionInner(expression, context)
}
@@ -40,26 +40,70 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
val collectUsages: Boolean,
val processOnlyConstant: Boolean,
val depthOutside: Int,
- val depthInside: Int,
+ private val depthInside: AtomicInteger,
+ private val depthNestedMethods: Int,
val next: Boolean) {
+ companion object {
+ fun create(file: UFile,
+ collectUsages: Boolean,
+ processOnlyConstant: Boolean,
+ depthOutside: Int,
+ depthInside: Int,
+ depthNestedMethods: Int,
+ next: Boolean): AnalyzeContext {
+ return AnalyzeContext(file, collectUsages, processOnlyConstant, depthOutside, AtomicInteger(depthInside),
+ depthNestedMethods, next)
+ }
+ }
+
+ fun minusMethod(): AnalyzeContext {
+ val depth = depthNestedMethods - 1
+ if (depth < 0) {
+ throw DeepTaintAnalyzerException()
+ }
+ return copy(depthNestedMethods = depth)
+ }
+
fun minusInside(): AnalyzeContext {
- return this.copy(depthInside = depthInside - 1)
+ val depth = depthInside.decrementAndGet()
+ if (depth < 0) {
+ throw DeepTaintAnalyzerException()
+ }
+ return this
+ }
+
+ fun minusInside(size: Int): AnalyzeContext {
+ val previous = depthInside.get()
+ val next = previous - size
+ depthInside.set(next)
+ if (next < 0) {
+ throw DeepTaintAnalyzerException()
+ }
+ return this
+ }
+
+ fun notCollect(): AnalyzeContext {
+ if (collectUsages) {
+ return this.copy(collectUsages = false)
+ }
+ return this
}
fun minusOutside(): AnalyzeContext {
return this.copy(processOnlyConstant = true, depthOutside = depthOutside - 1)
}
- fun notCollect(): AnalyzeContext {
- return this.copy(collectUsages = false)
- }
-
- fun minusInside(size: Int): AnalyzeContext {
- return this.copy(depthInside = depthInside - size)
- }
-
fun notNext(): AnalyzeContext {
- return this.copy(next = false)
+ if (next) {
+ return this.copy(next = false)
+ }
+ return this
+ }
+
+ fun checkInside(size: Int) {
+ if (depthInside.get() - size < 0) {
+ throw DeepTaintAnalyzerException()
+ }
}
}
@@ -67,22 +111,86 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
val uResolvable = (expression as? UResolvable) ?: return TaintValue.UNTAINTED
if (expression.sourcePsi == null) return TaintValue.UNTAINTED
val sourceTarget = uResolvable.resolve()
- return fromElement(sourceTarget, expression, analyzeContext)
+ if (sourceTarget == null) {
+ return fromCall(expression, analyzeContext) ?: TaintValue.UNKNOWN
+ }
+ val resolvedUElement = uResolvable.resolveToUElement()
+ val sourcePsi = resolvedUElement?.sourcePsi
+ if (sourcePsi != null) {
+ val previousValue = myVisitedTemporary[sourcePsi]
+ if (previousValue != null) {
+ return previousValue
+ }
+ }
+ var taintValue: TaintValue = myTaintValueFactory.fromAnnotation(sourceTarget) ?: return TaintValue.UNTAINTED
+ if (taintValue !== TaintValue.UNKNOWN) return taintValue
+ val value = checkAndPrepareVisited(resolvedUElement)
+ if (value != null) return value
+ taintValue = fromModifierListOwner(sourceTarget, expression, analyzeContext) ?: TaintValue.UNTAINTED
+ addToVisited(resolvedUElement, taintValue)
+ return taintValue
}
- private fun fromCall(sourceExpression: UExpression, sourceAnalyzeContext: AnalyzeContext): TaintValue? {
- var analyzeContext = sourceAnalyzeContext
+ private fun checkAndPrepareVisited(uElement: UElement?, prepare: Boolean = true): TaintValue? {
+ if (uElement == null) return null
+ val sourcePsi = uElement.sourcePsi
+ if (sourcePsi != null) {
+ val value = myVisited[sourcePsi]
+ if (value != null) {
+ return value
+ }
+ if (prepare) {
+ addToVisited(uElement, TaintValue.UNKNOWN)
+ }
+ }
+ return null
+ }
+
+ private fun addToVisited(uElement: UElement?, result: TaintValue?) {
+ val sourcePsi = uElement?.sourcePsi
+ if (sourcePsi == null) return
+ if (result == null) {
+ myVisited.remove(sourcePsi)
+ return
+ }
+ if (uElement is UVariable) {
+ myVisited[sourcePsi] = result
+ }
+ }
+
+ private fun fromCall(sourceExpression: UExpression, analyzeContext: AnalyzeContext): TaintValue? {
var expression = sourceExpression
if (analyzeContext.processOnlyConstant) {
return null
}
+
+ //fields as methods
+ if (expression is UQualifiedReferenceExpression && expression.selector is UReferenceExpression) {
+ val uMethod = expression.resolveToUElement()
+ if (uMethod is UMethod) {
+ val equalFiles = equalFiles(analyzeContext, uMethod)
+ if (myTaintValueFactory.getConfiguration().processMethodAsQualifierAndArguments && !equalFiles) {
+ val fromReceiver = fromExpressionWithoutCollection(expression.receiver, analyzeContext)
+ if (fromReceiver == TaintValue.UNTAINTED && uMethod.uastParameters.isEmpty()) {
+ return fromReceiver
+ }
+ }
+ }
+ }
+
if (expression is UQualifiedReferenceExpression && expression.selector is UCallExpression) {
expression = expression.selector
}
+ if (expression is UArrayAccessExpression) {
+ analyzeContext.checkInside(1 + expression.indices.size)
+ var result = fromExpressionWithoutCollection(expression.receiver, analyzeContext)
+ expression.indices.forEach { result = result.join(fromExpressionWithoutCollection(it, analyzeContext)) }
+ return result
+ }
if (expression !is UCallExpression) {
return null
}
- analyzeContext = analyzeContext.minusInside()
+
val fromReceiver = fromExpressionWithoutCollection(expression.receiver, analyzeContext)
val uMethod = expression.resolveToUElement()
if (uMethod is UMethod && equalFiles(analyzeContext, uMethod) && !uMethod.isConstructor) {
@@ -91,7 +199,7 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
(jvmModifiersOwner.hasModifier(JvmModifier.FINAL) || jvmModifiersOwner.hasModifier(JvmModifier.STATIC) ||
jvmModifiersOwner.hasModifier(JvmModifier.PRIVATE))) {
val values: MutableList = mutableListOf()
- analyzeContext = analyzeContext.minusInside(expression.valueArguments.size)
+ analyzeContext.checkInside(expression.valueArguments.size)
expression.valueArguments.forEach { argument ->
values.add(fromExpressionWithoutCollection(argument, analyzeContext))
}
@@ -107,49 +215,33 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
}
if (myTaintValueFactory.getConfiguration().processMethodAsQualifierAndArguments) {
var taintValue = fromReceiver
+ analyzeContext.checkInside(expression.valueArguments.size)
expression.valueArguments.forEach { argument ->
- taintValue = taintValue.join(fromExpressionWithoutCollection(argument, analyzeContext.minusInside()))
+ taintValue = taintValue.join(fromExpressionWithoutCollection(argument, analyzeContext))
}
return taintValue
}
return TaintValue.UNKNOWN
}
- private fun fromElement(sourcePsiTarget: PsiElement?, expression: UExpression, analyzeContext: AnalyzeContext): TaintValue {
- if (sourcePsiTarget == null) {
- val value = fromCall(expression, analyzeContext)
- return value ?: TaintValue.UNKNOWN
- }
- var value = myVisited[sourcePsiTarget]
- if (value != null) return value
- value = myVisitedTemporary[sourcePsiTarget]
- if (value != null) return value
- var taintValue: TaintValue = myTaintValueFactory.fromAnnotation(sourcePsiTarget) ?: return TaintValue.UNTAINTED
- if (taintValue !== TaintValue.UNKNOWN) return taintValue
- myVisited[sourcePsiTarget] = TaintValue.UNKNOWN
- taintValue = fromModifierListOwner(sourcePsiTarget, expression, analyzeContext) ?: TaintValue.UNTAINTED
- myVisited[sourcePsiTarget] = taintValue
- return taintValue
- }
-
val nonMarkedElements: List
- get() = myNonMarkedElements.toList()
+ get() = myNonMarkedElements.distinct().toList()
private fun fromModifierListOwner(sourcePsiTarget: PsiElement,
expression: UExpression,
- sourceAnalyzeContext: AnalyzeContext): TaintValue? {
- var analyzeContext = sourceAnalyzeContext
+ analyzeContext: AnalyzeContext): TaintValue? {
+
val owner = (sourcePsiTarget as? PsiModifierListOwner) ?: return null
- analyzeContext = analyzeContext.minusInside()
- if (analyzeContext.depthInside < 0) return null
var taintValue = fromLocalVar(expression, owner, analyzeContext)
- if (taintValue != null) return taintValue
- taintValue = fromField(owner, analyzeContext)
+ if (taintValue != null) {
+ return taintValue
+ }
+ taintValue = fromField(expression, owner, analyzeContext)
if (taintValue == null) {
taintValue = fromCall(expression, analyzeContext)
}
if (taintValue == null) {
- taintValue = fromParam(owner, analyzeContext)
+ taintValue = fromParam(expression, owner, analyzeContext)
}
if (taintValue == null) {
//it might be kotlin param, for example
@@ -174,8 +266,8 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
if (sourceTarget !is PsiLocalVariable) return null
val localVariable = (expression as? UResolvable)?.resolveToUElement() as? ULocalVariable ?: return null
val containingMethod = localVariable.getContainingUMethod() ?: return null
- val checkLocalAfterUsing = possibleToSkipCheckAfterReference(expression, containingMethod)
- return fromVar(sourceTarget, analyzeContext, if (checkLocalAfterUsing) expression else null)
+ val skipAfterReference = possibleToSkipCheckAfterReference(expression, containingMethod)
+ return fromVar(sourceTarget, analyzeContext, expression, skipAfterReference)
}
//Kotlin allows use non-effective-final variables in lambdas
@@ -192,9 +284,11 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
return checkLocalAfterUsing
}
- private fun fromVar(sourceTarget: PsiElement, analyzeContext: AnalyzeContext, usedReference: UExpression? = null): TaintValue? {
- val psiVariable = (sourceTarget as? PsiVariable) ?: return null
- val uVariable = psiVariable.toUElement(UVariable::class.java) ?: return null
+ private fun fromVar(sourceTarget: PsiElement,
+ analyzeContext: AnalyzeContext,
+ usedReference: UExpression?,
+ skipAfterReference: Boolean): TaintValue? {
+ val uVariable = sourceTarget.toUElement(UVariable::class.java) ?: return null
val uInitializer = uVariable.uastInitializer
val taintValue = fromExpressionWithoutCollection(uInitializer, analyzeContext)
if (taintValue == TaintValue.TAINTED) return taintValue
@@ -205,14 +299,15 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
else if (uVariable is UField) {
codeBlock = uVariable.uastParent
}
- return codeBlock?.let { analyzeVar(taintValue, it, psiVariable, analyzeContext, usedReference) } ?: taintValue
+ return codeBlock?.let { analyzeVar(taintValue, it, uVariable, analyzeContext, usedReference, skipAfterReference) } ?: taintValue
}
private fun analyzeVar(taintValue: TaintValue,
codeBlock: UElement,
- psiVariable: PsiVariable,
+ uVariable: UVariable,
analyzeContext: AnalyzeContext,
- usedReference: UExpression? = null): TaintValue {
+ usedReference: UExpression?,
+ skipAfterReference: Boolean): TaintValue {
class VarAnalyzer(var myTaintValue: TaintValue) : AbstractUastVisitor() {
@@ -221,13 +316,15 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
for (expression in node.expressions) {
expression.accept(this)
if (stopAnalysis) return true
- if (myTaintValue == TaintValue.TAINTED) return true
+ if (myTaintValue == TaintValue.TAINTED) {
+ return true
+ }
}
return true
}
override fun visitExpression(node: UExpression): Boolean {
- if (usedReference == node) {
+ if (skipAfterReference && usedReference == node) {
stopAnalysis = true
return true
}
@@ -244,10 +341,10 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
}
private fun checkUsages(expressions: List): TaintValue {
- if (skipClass(psiVariable.type)) {
+ if (skipClass(uVariable.type)) {
return TaintValue.UNTAINTED
}
- if (ClassUtils.isImmutable(psiVariable.type)) {
+ if (ClassUtils.isImmutable(uVariable.type)) {
return TaintValue.UNTAINTED
}
for (expression in expressions) {
@@ -258,13 +355,21 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
expression.accept(object : AbstractUastVisitor() {
override fun visitExpression(node: UExpression): Boolean {
if (node is UReferenceExpression) {
- if (node.resolve() == psiVariable) {
+ if ((usedReference == null || node.sourcePsi != usedReference.sourcePsi) &&
+ uVariable.sourcePsi != null && node.resolveToUElement()?.sourcePsi == uVariable.sourcePsi) {
hasUsage.set(true)
+ return true
}
- return true
}
return super.visitExpression(node)
}
+
+ override fun visitElement(node: UElement): Boolean {
+ if (hasUsage.get()) {
+ return true
+ }
+ return super.visitElement(node)
+ }
})
if (hasUsage.get()) {
return TaintValue.UNKNOWN
@@ -275,8 +380,9 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
override fun visitBinaryExpression(node: UBinaryExpression): Boolean {
if (node.operator !is AssignOperator) return super.visitBinaryExpression(node)
- val lhs = (node.leftOperand as? UReferenceExpression)
- if (lhs == null || psiVariable != lhs.resolve()) return super.visitBinaryExpression(node)
+ val lhs = (node.leftOperand as? UReferenceExpression) ?: return super.visitBinaryExpression(node)
+ val uElement = lhs.resolveToUElement()
+ if (uElement == null || uVariable.sourcePsi != uElement.sourcePsi) return super.visitBinaryExpression(node)
val rhs = node.rightOperand
myTaintValue = myTaintValue.join(fromExpressionWithoutCollection(rhs, analyzeContext))
return if (myTaintValue == TaintValue.TAINTED) true else super.visitBinaryExpression(node)
@@ -288,16 +394,17 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
return varAnalyzer.myTaintValue
}
- private fun fromParam(target: PsiElement?, analyzeContext: AnalyzeContext): TaintValue? {
+ private fun fromParam(expression: UExpression, target: PsiElement?, analyzeContext: AnalyzeContext): TaintValue? {
val psiParameter = (target as? PsiParameter) ?: return null
val uParameter = target.toUElement(UParameter::class.java) ?: return null
// default parameter value
val uInitializer = uParameter.uastInitializer
- var taintValue = fromExpressionWithoutCollection(uInitializer, analyzeContext.minusInside())
+ var taintValue = fromExpressionWithoutCollection(uInitializer, analyzeContext)
if (taintValue == TaintValue.TAINTED) return taintValue
val uMethod = (uParameter.uastParent as? UMethod) ?: return TaintValue.UNTAINTED
val uBlock = (uMethod.uastBody as? UBlockExpression)
- if (uBlock != null) taintValue = analyzeVar(taintValue, uBlock, psiParameter, analyzeContext)
+ if (uBlock != null) taintValue = analyzeVar(taintValue, uBlock, uParameter, analyzeContext, expression,
+ possibleToSkipCheckAfterReference(expression, uMethod))
if (taintValue == TaintValue.TAINTED) return taintValue
val nonMarkedElements = SmartList()
// this might happen when we analyze kotlin primary constructor parameter
@@ -311,30 +418,27 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
return TaintValue.UNKNOWN
}
- private fun fromField(target: PsiElement, analyzeContext: AnalyzeContext): TaintValue? {
+ private fun fromField(expression: UExpression?, target: PsiElement, analyzeContext: AnalyzeContext): TaintValue? {
if (analyzeContext.depthOutside < 0) return null
val uElement = target.toUElement() as? UField ?: return null
val sourcePsi = uElement.sourcePsi ?: return null
val jvmModifiersOwner: JvmModifiersOwner = uElement
- if (jvmModifiersOwner.hasModifier(JvmModifier.FINAL) || (jvmModifiersOwner.hasModifier(
- JvmModifier.PRIVATE) && canFieldAssignOnlyInConstructors(
- target))) {
- val result: TaintValue? = if (!equalFiles(analyzeContext, uElement)) {
- fromVar(sourcePsi, analyzeContext.minusOutside().notCollect())
- }
- else {
- fromVar(sourcePsi, analyzeContext.notCollect())
- }
-
- if (result != null) {
- return result
- }
- return if (jvmModifiersOwner.hasModifier(JvmModifier.FINAL)) {
- TaintValue.UNTAINTED
- }
- else {
- TaintValue.UNKNOWN
- }
+ val equalFiles = equalFiles(analyzeContext, uElement)
+ if (!equalFiles &&
+ jvmModifiersOwner.hasModifier(JvmModifier.FINAL) &&
+ expression is UQualifiedReferenceExpression &&
+ skipClass(expression.receiver.getExpressionType())) {
+ return TaintValue.UNTAINTED
+ }
+ if (equalFiles &&
+ (jvmModifiersOwner.hasModifier(JvmModifier.FINAL) ||
+ (jvmModifiersOwner.hasModifier(JvmModifier.PRIVATE) && fieldAssignedOnlyWithLiterals(target, analyzeContext)))) {
+ val result: TaintValue? = fromVar(sourcePsi, analyzeContext.notCollect().minusInside(), expression, false)
+ return result ?: TaintValue.UNKNOWN
+ }
+ if (!equalFiles && jvmModifiersOwner.hasModifier(JvmModifier.FINAL) && uElement.uastInitializer != null) {
+ return fromExpressionWithoutCollection(uElement.uastInitializer,
+ analyzeContext.notNext().minusOutside())
}
if (analyzeContext.processOnlyConstant) {
return null
@@ -362,38 +466,43 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
return analyzeMethod(uMethod, analyzeContext, values)
}
- private fun analyzeMethod(uMethod: UMethod, analyzeContext: AnalyzeContext, arguments: List): TaintValue {
- if (!equalFiles(analyzeContext, uMethod)) return TaintValue.UNKNOWN
+ private fun analyzeMethod(uMethod: UMethod, sourceContext: AnalyzeContext, arguments: List): TaintValue {
+ if (!equalFiles(sourceContext, uMethod)) return TaintValue.UNKNOWN
val psiElement = uMethod.sourcePsi ?: return TaintValue.UNKNOWN
val key = Pair(psiElement, arguments)
val value = myVisitedMethods[key]
- if (value != null) return value
- class MethodAnalyzer : AbstractUastVisitor() {
- var myTaintValue = TaintValue.UNTAINTED
- override fun visitBlockExpression(node: UBlockExpression): Boolean {
- for (expression in node.expressions) {
- expression.accept(this)
- }
- return true
- }
-
- override fun visitReturnExpression(node: UReturnExpression): Boolean {
- val returnExpression = node.returnExpression ?: return true
- myTaintValue = myTaintValue.join(fromExpressionWithoutCollection(returnExpression, analyzeContext.minusInside()))
- return if (myTaintValue == TaintValue.TAINTED) true else super.visitReturnExpression(node)
- }
+ if (value != null) {
+ return value
}
+ var analyzeContext = sourceContext
+
val methodBody = (uMethod.uastBody as? UBlockExpression)
if (methodBody == null) {
// maybe it is a generated kotlin property getter or setter
val sourcePsi = uMethod.sourcePsi ?: return TaintValue.UNTAINTED
- val taintValue = fromField(sourcePsi, analyzeContext)
+ val taintValue = fromField(null, sourcePsi, analyzeContext.minusInside())
return taintValue ?: TaintValue.UNTAINTED
}
+
+ analyzeContext = analyzeContext.minusInside().minusMethod()
+
+
+ class MethodAnalyzer : AbstractUastVisitor() {
+ var myTaintValue = TaintValue.UNTAINTED
+ override fun visitReturnExpression(node: UReturnExpression): Boolean {
+ val returnExpression = node.returnExpression ?: return true
+ myTaintValue = myTaintValue.join(fromExpressionWithoutCollection(returnExpression, analyzeContext))
+ return if (myTaintValue == TaintValue.TAINTED) true else super.visitReturnExpression(node)
+ }
+ }
+
val methodAnalyzer = MethodAnalyzer()
myVisitedMethods[key] = TaintValue.UNKNOWN
- val previous = HashMap(myVisitedTemporary)
+
+ val previousVisited = HashMap(myVisited)
+ val previousTemporary = HashMap(myVisitedTemporary)
+
if (!arguments.isEmpty()) {
val parameters = uMethod.uastParameters
for (i in parameters.indices) {
@@ -401,60 +510,93 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
myVisitedTemporary[sourcePsi] = if (arguments.size <= i) arguments[arguments.size - 1] else arguments[i]
}
}
+
+ //prevent recursion always
+ myVisited[psiElement] = TaintValue.UNKNOWN
+
methodBody.accept(methodAnalyzer)
val returnValue = methodAnalyzer.myTaintValue
+
myVisitedTemporary.clear()
- myVisitedTemporary.putAll(previous)
+ myVisitedTemporary.putAll(previousTemporary)
+
+ myVisited.clear()
+ myVisited.putAll(previousVisited)
+
myVisitedMethods[key] = returnValue
return returnValue
}
- private fun fromExpressionWithoutCollection(uExpression: UExpression?, sourceAnalyzeContext: AnalyzeContext): TaintValue {
- var analyzeContext = sourceAnalyzeContext
- analyzeContext = analyzeContext.notCollect()
+ private fun fromExpressionWithoutCollection(uExpression: UExpression?, analyzeContext: AnalyzeContext): TaintValue {
return fromExpressionInner(uExpression, analyzeContext.notCollect())
}
private fun fromExpressionInner(sourceUExpression: UExpression?, analyzeContext: AnalyzeContext): TaintValue {
var uExpression = sourceUExpression ?: return TaintValue.UNTAINTED
- if (analyzeContext.depthInside < 0) return TaintValue.UNKNOWN
+ if (analyzeContext.depthOutside < 0) return TaintValue.UNKNOWN
val type = uExpression.getExpressionType()
if (type != null && skipClass(type)) return TaintValue.UNTAINTED
- if (uExpression is UThisExpression) return TaintValue.UNTAINTED
uExpression = uExpression.skipParenthesizedExprDown()
- if (uExpression is ULiteralExpression) return TaintValue.UNTAINTED
- if (uExpression is UResolvable) return analyzeInner(uExpression, analyzeContext)
val uConcatenation = getConcatenation(uExpression)
if (uConcatenation != null) {
- val size = uConcatenation.operands.size
- return StreamEx.of(uConcatenation.operands).collect(joining(analyzeContext.minusInside(size)))
+ val operands = uConcatenation.operands
+ val size = operands.filter { it !is ULiteralExpression && it !is UPolyadicExpression }.size
+ return StreamEx.of(operands).collect(joining(analyzeContext.minusInside(size)))
}
- val uIfExpression = (uExpression as? UIfExpression)
- if (uIfExpression != null) {
- return StreamEx.of(uIfExpression.thenExpression, uIfExpression.elseExpression)
- .collect(joining(analyzeContext.minusInside(2)))
+ when (uExpression) {
+ is UUnknownExpression -> {
+ return TaintValue.UNKNOWN
+ }
+ is UThisExpression -> {
+ return TaintValue.UNTAINTED
+ }
+ is ULiteralExpression -> {
+ return TaintValue.UNTAINTED
+ }
+ is ULambdaExpression -> {
+ return TaintValue.UNKNOWN
+ }
+ is UClassLiteralExpression -> {
+ return TaintValue.UNTAINTED
+ }
+ is UResolvable -> {
+ val resolved = uExpression.resolveToUElement()
+ val visited = checkAndPrepareVisited(resolved, prepare = false)
+ if (visited != null) {
+ return visited
+ }
+ return analyzeInner(uExpression, analyzeContext.minusInside())
+ }
+ is UUnaryExpression -> {
+ return fromExpressionWithoutCollection(uExpression.operand, analyzeContext.minusInside())
+ }
+ is UBinaryExpression -> {
+ return StreamEx.of(uExpression.leftOperand, uExpression.rightOperand)
+ .collect(joining(analyzeContext.minusInside(2)))
+ }
+ is ULabeledExpression -> {
+ return fromExpressionWithoutCollection(uExpression.expression, analyzeContext.minusInside())
+ }
+ is UIfExpression, is USwitchExpression, is UBlockExpression -> {
+ val nonStructuralChildren = nonStructuralChildren(uExpression).toList()
+ return StreamEx.of(nonStructuralChildren)
+ .filter { it != null }
+ .collect(joining(analyzeContext.minusInside(nonStructuralChildren.size)))
+ }
+ else -> {
+ return TaintValue.UNKNOWN
+ }
}
- val switchExpression = (uExpression as? USwitchExpression)
- if (switchExpression != null) {
- val size = switchExpression.body.expressions.size
- return StreamEx.of(switchExpression.body.expressions)
- .collect(joining(analyzeContext.minusInside(size)))
- }
- if (uExpression is ULambdaExpression) return TaintValue.UNKNOWN
- val javaPsi = (uExpression.javaPsi as? PsiExpression) ?: return TaintValue.UNTAINTED
- val list = ExpressionUtils.nonStructuralChildren(javaPsi).toList()
- return StreamEx.of(list)
- .map { e: PsiExpression -> e.toUElement(UExpression::class.java) }
- .collect(joining(analyzeContext.minusInside(list.size)))
}
- private fun skipClass(type: PsiType): Boolean {
+ private fun skipClass(type: PsiType?): Boolean {
+ if (type == null) return false
val aClass = PsiUtil.resolveClassInClassTypeOnly(type)
return skipClasses.contains(type.canonicalText) ||
skipClasses.any { cl: String? ->
cl != null && InheritanceUtil.isInheritor(type, cl)
} ||
- (aClass != null && (aClass.isInterface || aClass.isEnum))
+ (aClass != null && (aClass.isAnnotationType || aClass.isEnum))
}
private fun joining(analyzeContext: AnalyzeContext): Collector {
@@ -468,30 +610,42 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
return file != null && analyzeContext.file.sourcePsi == file.sourcePsi
}
- private fun canFieldAssignOnlyInConstructors(target: PsiElement): Boolean {
+ private fun fieldAssignedOnlyWithLiterals(target: PsiElement, analyzeContext: AnalyzeContext): Boolean {
val uElement = target.toUElement()
if (uElement == null) return false
val containingUClass = uElement.getContainingUClass() ?: return false
class FieldAssignmentAnalyzer : AbstractUastVisitor() {
- var assignOutsideConstructor = false
+ var assignOnlyWithLiteral = true
override fun visitBinaryExpression(node: UBinaryExpression): Boolean {
if (node.operator !is AssignOperator) return super.visitBinaryExpression(node)
val lhs = (node.leftOperand as? UReferenceExpression)
if (lhs == null || target != lhs.resolve()) return super.visitBinaryExpression(node)
- val containingUMethod = node.getContainingUMethod() ?: return super.visitBinaryExpression(node)
- if (!containingUMethod.isConstructor) {
- assignOutsideConstructor = true
+ if (node.rightOperand !is ULiteralExpression) {
+ //don't go further, consider that it is already untidy
+ assignOnlyWithLiteral = false
return true
}
return super.visitBinaryExpression(node)
}
}
- return containingUClass.methods.none {
+ val methods = listOf(containingUClass, *containingUClass.innerClasses)
+ .map { it.methods.toList() }
+ .flatten()
+ analyzeContext.minusInside(methods.size)
+ return methods.all {
val visitor = FieldAssignmentAnalyzer()
- it.accept(visitor)
- !it.isConstructor && visitor.assignOutsideConstructor
+ if (it.javaPsi.isPhysical) {
+ it.accept(visitor)
+ return@all visitor.assignOnlyWithLiteral
+ }
+ val body = it.javaPsi.body.toUElement()
+ if (body != null) {
+ body.accept(visitor)
+ return@all visitor.assignOnlyWithLiteral
+ }
+ return@all !(it.sourcePsi == target && it.javaPsi.parameters.isNotEmpty())
}
}
@@ -541,3 +695,5 @@ class TaintAnalyzer(private val myTaintValueFactory: TaintValueFactory) {
}
}
}
+
+class DeepTaintAnalyzerException : RuntimeException()
\ No newline at end of file
diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/TaintNode.java b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/TaintNode.java
index f3d7d7b90b0a..c82efe56b7b6 100644
--- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/TaintNode.java
+++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/TaintNode.java
@@ -98,7 +98,13 @@ public class TaintNode extends PresentableNodeDescriptor {
TaintAnalyzer taintAnalyzer = new TaintAnalyzer(myTaintValueFactory);
UExpression uExpression = UastContextKt.toUElementOfExpectedTypes(elementRef, UCallExpression.class, UReferenceExpression.class);
if(uExpression == null) return Collections.emptyList();
- TaintValue taintValue = taintAnalyzer.analyzeExpression(uExpression, true);
+ TaintValue taintValue;
+ try {
+ taintValue = taintAnalyzer.analyzeExpression(uExpression, true);
+ }
+ catch (DeepTaintAnalyzerException e) {
+ return Collections.emptyList();
+ }
myTaintValue = taintValue;
if (taintValue == TaintValue.UNTAINTED) return Collections.emptyList();
if (taintValue == TaintValue.TAINTED) {
diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/TaintValueFactory.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/TaintValueFactory.kt
index dac58307cdba..d8cc503abe2b 100644
--- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/TaintValueFactory.kt
+++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/sourceToSink/TaintValueFactory.kt
@@ -5,13 +5,15 @@ import com.intellij.codeInsight.AnnotationTargetUtil
import com.intellij.codeInsight.AnnotationUtil
import com.intellij.codeInsight.ExternalAnnotationsManager
import com.intellij.codeInspection.restriction.AnnotationContext
-import com.intellij.codeInspection.restriction.RestrictionInfo
+import com.intellij.codeInspection.restriction.RestrictionInfo.RestrictionInfoKind
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiUtil
import com.siyeh.ig.psiutils.MethodMatcher
+import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.ULocalVariable
+import org.jetbrains.uast.UVariable
import org.jetbrains.uast.toUElement
import kotlin.streams.asSequence
@@ -72,19 +74,25 @@ class TaintValueFactory(private val myConfiguration: UntaintedConfiguration) {
}
}
}
- if (info.kind != RestrictionInfo.RestrictionInfoKind.KNOWN) {
+ if (info.kind != RestrictionInfoKind.KNOWN) {
info = context.secondaryItems().asSequence()
- .map { fromAnnotationOwner(it.modifierList) }
- .filter { it !== TaintValue.UNKNOWN }
+ .flatMap { listOf(fromAnnotation(it), fromAnnotationOwner(it.modifierList)) }
+ .filter { it != null && it !== TaintValue.UNKNOWN }
.firstOrNull() ?: info
}
- if (info === TaintValue.UNKNOWN) {
+ if (info == TaintValue.UNKNOWN) {
val obj: Any = if (owner is PsiParameter) owner.declarationScope else owner
val member = (obj as? PsiMember)
if (member != null) {
info = of(member)
}
}
+ val toUElement = owner.toUElement()
+ if (info == TaintValue.UNKNOWN && toUElement is UVariable) {
+ return toUElement.uAnnotations
+ .mapNotNull { fromUAnnotation(it) }
+ .firstOrNull { it != TaintValue.UNKNOWN } ?: info
+ }
return info
}
@@ -126,7 +134,20 @@ class TaintValueFactory(private val myConfiguration: UntaintedConfiguration) {
if (myTaintedAnnotations.contains(annotationQualifiedName)) {
return TaintValue.TAINTED
}
- return if (myUnTaintedAnnotations.contains(annotationQualifiedName)) {
+ return if (myUnTaintedAnnotations.contains(annotationQualifiedName) && annotationQualifiedName != JAVAX_ANNOTATION_UNTAINTED) {
+ TaintValue.UNTAINTED
+ }
+ else null
+ }
+
+ private fun fromUAnnotation(annotation: UAnnotation): TaintValue? {
+ val annotationQualifiedName = annotation.qualifiedName
+ val fromJsr = processUJsr(annotationQualifiedName, annotation)
+ if (fromJsr != null) return fromJsr
+ if (myTaintedAnnotations.contains(annotationQualifiedName)) {
+ return TaintValue.TAINTED
+ }
+ return if (myUnTaintedAnnotations.contains(annotationQualifiedName) && annotationQualifiedName != JAVAX_ANNOTATION_UNTAINTED) {
TaintValue.UNTAINTED
}
else null
@@ -138,8 +159,20 @@ class TaintValueFactory(private val myConfiguration: UntaintedConfiguration) {
!myUnTaintedAnnotations.contains(JAVAX_ANNOTATION_UNTAINTED)) {
return null
}
- val whenAttribute = annotation.findAttributeValue("when") ?: return null
- return if (whenAttribute.textMatches("ALWAYS")) TaintValue.UNTAINTED else null
+ val whenAttribute = annotation.findAttributeValue("when") ?: return TaintValue.UNTAINTED
+ return if (whenAttribute.textMatches("ALWAYS") || whenAttribute.textMatches(
+ "javax.annotation.meta.When.ALWAYS")) TaintValue.UNTAINTED
+ else null
+ }
+
+ private fun processUJsr(qualifiedName: String?, annotation: UAnnotation): TaintValue? {
+ if (qualifiedName == null ||
+ qualifiedName != JAVAX_ANNOTATION_UNTAINTED ||
+ !myUnTaintedAnnotations.contains(JAVAX_ANNOTATION_UNTAINTED)) {
+ return null
+ }
+ val whenAttribute = annotation.findAttributeValue("when") ?: return TaintValue.UNTAINTED
+ return if ((whenAttribute.evaluate() as? Pair<*, *>)?.second.toString() == "ALWAYS") TaintValue.UNTAINTED else null
}
fun fromAnnotation(target: PsiElement?): TaintValue? {
diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Call.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Call.java
new file mode 100644
index 000000000000..32559cba56dd
--- /dev/null
+++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Call.java
@@ -0,0 +1,37 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+class CallsCheck {
+
+ public void testCall(String dirty, @Untainted String clean) {
+ sink(dirty); //warn
+ sink("");
+ sink(cleanMethod());
+ sink(publicMethod()); //warn
+ sink(publicFinalMethod());
+ sink(privateDirty(dirty)); //warn
+ sink(dirty.toLowerCase()); //warn
+ sink(dirty.getClass().getName());
+ sink(dirty.replace("1", "2")); //warn
+ sink(clean);
+ sink(clean.replace("1", dirty)); //warn
+ }
+
+ private String privateDirty(String dirty) {
+ return dirty;
+ }
+
+ public String publicMethod() {
+ return "1";
+ }
+ public final String publicFinalMethod() {
+ return "1";
+ }
+
+ private String cleanMethod() {
+ return "null";
+ }
+
+ public void sink(@Untainted String clean) {
+
+ }
+}
diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/DifferentExpression.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/DifferentExpression.java
new file mode 100644
index 000000000000..a5881685bf0e
--- /dev/null
+++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/DifferentExpression.java
@@ -0,0 +1,25 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class DifferentExpression {
+
+ public void test() {
+ sink(this.toString());
+ Runnable r = () -> {
+ };
+ sink(r.toString()); //warn
+ sink(DifferentExpression.class.toString());
+ sink("test" + (1 - 1));
+ int x = 1;
+ sink("test" + (++x));
+ sink(param2("1", )); //warn
+ }
+
+
+ public static void sink(@Untainted String string) {
+
+ }
+
+ public static String param2(String t, String t1) {
+ return t1;
+ }
+}
diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/EnumAnnotations.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/EnumAnnotations.java
new file mode 100644
index 000000000000..7df19b4c5cdb
--- /dev/null
+++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/EnumAnnotations.java
@@ -0,0 +1,24 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+class LocalCheck {
+
+
+ public enum State {
+ OFF, ON
+ }
+
+ public @interface InterfaceSomething {
+
+ }
+
+ void test(@Untainted String clean, String dirty, State state, InterfaceSomething interfaceSomething) {
+ sink(clean);
+ sink(dirty); //warn
+ sink(state.name());
+ sink(interfaceSomething.toString());
+ }
+
+ void sink(@Untainted String clean) {
+
+ }
+}
diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Fields.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Fields.java
new file mode 100644
index 000000000000..0ea7bde472ba
--- /dev/null
+++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Fields.java
@@ -0,0 +1,34 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+class FieldsCheck {
+ final String constant = "1";
+ private String clean = "1";
+ private String notClean = "1";
+
+ final String finalAppliedFromConstructor;
+ private String appliedFromConstructor;
+ private String clean2;
+
+ public FieldsCheck(String finalAppliedFromConstructor, String appliedFromConstructor) {
+ this.finalAppliedFromConstructor = finalAppliedFromConstructor;
+ this.appliedFromConstructor = appliedFromConstructor;
+ clean2 = "2";
+ }
+
+ public void setNotClean(String notClean) {
+ this.notClean = notClean;
+ }
+
+ public void test() {
+ sink(constant);
+ sink(clean);
+ sink(clean2);
+ sink(notClean); //warn
+ sink(finalAppliedFromConstructor); //warn
+ sink(appliedFromConstructor); //warn
+ }
+
+ void sink(@Untainted String s) {
+ }
+
+}
diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Limits.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Limits.java
new file mode 100644
index 000000000000..fbe30f2386a8
--- /dev/null
+++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Limits.java
@@ -0,0 +1,149 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+class Limit {
+
+ public final static String fromAnotherFile = Limit2.fromAnotherFile;
+ public final static String fromAnotherFile2 = Limit2.fromAnotherFile2;
+ public final static String fromAnotherFile3 = Limit2.fromAnotherFile3;
+ public final static String fromAnotherFile4 = Limit2.fromAnotherFile4;
+ public final static String fromAnotherFile5 = new Limit2().fromAnotherFile5;
+ public final static String fromAnotherFile6 = new Limit2().fromAnotherFile6;
+
+ public static void test(@Untainted String clear, String dirty) {
+ sink(dirty); //warn
+ sink(next(next(next(next(next(next(next(next(next(next(next(next(next(next(next(dirty)))))))))))))))); //warn complex
+ sink(next(next(next(next(next(next(next(dirty)))))))); //warn
+ sink(next(next(next(next(next(next(next(clear))))))));
+ sink(next(next(next(next(next(next(next(clear))))))) +
+ next(next(next(next(next(next(next(clear))))))) +
+ next(next(next(next(next(next(next(clear))))))) +
+ next(next(next(next(next(next(next(clear))))))) +
+ next(next(next(next(next(next(next(clear))))))) +
+ next(next(next(next(next(next(next(clear))))))));
+ sink(next(next(next(next(next(next(next(clear))))))) +
+ next(next(next(next(next(next(next(clear))))))) +
+ next(next(next(next(next(next(next(clear))))))) +
+ next(next(next(next(next(next(next(clear))))))) +
+ next(next(next(next(next(next(next(clear))))))) +
+ next(next(next(next(next(next(next(dirty)))))))); //warn
+ sink(fromAnotherFile);
+ sink(fromAnotherFile2); //warn
+ sink(fromAnotherFile3); //warn
+ sink(fromAnotherFile4); //warn
+ sink(fromAnotherFile5);
+ sink(fromAnotherFile6); //warn
+ String cleanLongString = "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ clear +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh";
+ sink(cleanLongString);
+ sink(cleanLongString + dirty); //warn
+ String dirtyLongString = "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ dirty +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ clear +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh";
+ sink(dirtyLongString); //warn
+
+ String a1 = clear + 1 + clear + clear + clear + clear + clear + clear + clear;
+ String a2 = a1 + 1 + a1 + a1 + a1 + a1 + a1 + a1 + a1 + a1 + a1 + a1 + a1;
+ String a3 = a2 + 1 + a2 + a2 + a2 + a2 + a2 + a2 + a2 + a2 + a2 + a2 + a2;
+ String a4 = a3 + 1 + a3 + a3 + a3 + a3 + a3 + a3 + a3 + a3 + a3 + a3 + a3;
+ String a5 = a4 + 1 + a4 + a4 + a4 + a4 + a4 + a4 + a4 + a4 + a4 + a4 + a4;
+ String a6 = a5 + 1 + a5 + a5 + a5 + a5 + a5 + a5 + a5 + a5 + a5 + a5 + a5;
+ String a7 = a6 + 1 + a6 + a6 + a6 + a6 + a6 + a6 + a6 + a6 + a6 + a6 + a6;
+ String a8 = a7 + 1 + a7 + a7 + a7 + a7 + a7 + a7 + a7 + a7 + a7 + a7 + a7;
+ String a9 = a8 + 1 + a8 + a8 + a8 + a8 + a8 + a8 + a8 + a8 + a8 + a8 + a8;
+ String a10 = a9 + 1 + a9 + a9 + a9 + a9 + a9 + a9 + a9 + a9 + a9 + a9 + a9;
+ sink(a10); //warn
+ sink(a2);
+ }
+
+ public static String next(String next) {
+ return next;
+ }
+
+ public static void sink(@Untainted String string) {
+
+ }
+}
diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/LocalVariable.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/LocalVariable.java
new file mode 100644
index 000000000000..3f93b9079cce
--- /dev/null
+++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/LocalVariable.java
@@ -0,0 +1,46 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+import java.util.List;
+
+class LocalCheck {
+
+
+ public void test(@Untainted List clean, @Untainted List cleanList2, @Untainted String t, String dirty) {
+ sink(t);
+ sink(clean.get(0));
+ List list1 = clean;
+ List list2 = clean;
+ update(list1); //not highlighted in current realisation, might be changed
+ list2.add(dirty); //not highlighted in current realisation, might be changed
+ sink(list1.get(0)); //warn
+ sink(list2.get(0)); //warn
+ sink(clean.get(0));
+ List list3 = cleanList2;
+ sink(list3.get(0));
+ sink(dirty); //warn
+
+ String clean2 = t;
+ sink(t);
+ clean2 = dirty;
+ sink(clean2); //warn
+
+ String toDirty = t;
+ sink(toDirty);
+
+ new Runnable() {
+ @Override
+ public void run() {
+ sink(toDirty);
+ }
+ };
+
+ Runnable runnable = () -> sink(toDirty);
+ }
+
+ private void update(List list) {
+ }
+
+ public void sink(@Untainted String clean) {
+
+ }
+}
diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/MethodPropagation.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/MethodPropagation.java
new file mode 100644
index 000000000000..dc2fa5f8bd3f
--- /dev/null
+++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/MethodPropagation.java
@@ -0,0 +1,70 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+public class MethodPropagation {
+
+ public void test1(String dirty, @Untainted String clean) {
+ sink(dirty); //warn
+ sink(clean);
+ }
+
+ private String recursive(String dirty, @Untainted String clean) {
+ if (clean == "") {
+ String a = recursive(dirty, clean);
+ sink(a); //warn
+ return recursive(dirty, clean);
+ }
+ return recursive(clean, clean);
+ }
+
+
+ public void test2(String dirty, @Untainted String clean) {
+ sink(next(dirty)); //warn
+ sink(next(clean));
+
+ sink(nextPublic(dirty)); //warn
+ sink(nextPublic(clean)); //warn (public)
+
+ sink(alwaysClean(dirty));
+ sink(alwaysClean(clean));
+
+ sink(staticNext(dirty)); //warn
+ sink(staticNext(clean));
+
+ sink(next(next(clean)));
+ sink(next(next(next(next(next(next(next(next(clean)))))))));
+ sink(next(next(next(next(next(next(next(next(dirty))))))))); //warn
+
+ sink(alwaysClean(next(next(next(next(next(next(next(clean)))))))));
+ sink(alwaysClean(next(next(next(next(next(next(next(dirty)))))))));
+
+ sink(next(next(next(next(next(next(next(alwaysClean(clean)))))))));
+ sink(next(next(next(next(next(next(next(alwaysClean(dirty)))))))));
+ sink(next(alwaysClean(clean)));
+ sink(next((alwaysClean(dirty))));
+
+ String alwaysClean = alwaysClean(next(next(next(next(next(next(next(clean))))))));
+ sink(alwaysClean);
+ String alwaysClean2 = alwaysClean(next(next(next(next(next(next(next(dirty))))))));
+ sink(alwaysClean2);
+ }
+
+ private String next(String next) {
+ return next;
+ }
+
+ public static String staticNext(String next) {
+ return next;
+ }
+
+ private String alwaysClean(String next) {
+ return "next";
+ }
+
+ public String nextPublic(String next) {
+ return next;
+ }
+
+ public static void sink(@Untainted String string) {
+
+ }
+}
diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Recursive.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Recursive.java
new file mode 100644
index 000000000000..822ccc33a01a
--- /dev/null
+++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Recursive.java
@@ -0,0 +1,17 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+class MethodPropagation {
+
+ private String recursive(String dirty, @Untainted String clean) {
+ if (clean == "") {
+ String a = recursive(dirty,);
+ sink(a);
+ return recursive(clean, clean);
+ }
+ return recursive(clean, clean);
+ }
+
+ public static void sink(@Untainted String string) {
+
+ }
+}
diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Sink.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Sink.java
new file mode 100644
index 000000000000..79913fe51a48
--- /dev/null
+++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Sink.java
@@ -0,0 +1,48 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class SinkTest {
+
+ public void test(String string) {
+ sink(string); //warn
+ }
+
+ @Untainted
+ public String returnDirty(String dirty) {
+ return dirty; //warn
+ }
+
+ void sink(@Untainted String clear) {
+
+ }
+
+ void assignDirty(@Untainted String clear, String dirty) {
+ clear = dirty; //warn
+ }
+
+ @Untainted String dirty = getFromStatic(); //warn
+
+ static List list = new ArrayList<>();
+
+ private static String getFromStatic() {
+ return list.get(0);
+ }
+
+ @Untainted
+ static String clear = "";
+
+ static void spoil(String dirty) {
+ clear = dirty; //warn
+ }
+
+ static void testLocal(String dirty) {
+ @Untainted String clean = dirty; //warn
+ }
+
+ static void testLocal2(String dirty) {
+ @Untainted String clean = "";
+ clean = dirty; //warn
+ }
+}
diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/SinkJsr.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/SinkJsr.java
new file mode 100644
index 000000000000..cdd408d4a5e2
--- /dev/null
+++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/SinkJsr.java
@@ -0,0 +1,48 @@
+import javax.annotation.Untainted;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class SinkTest {
+
+ public void test(String string) {
+ sink(string); //warn
+ }
+
+ @Untainted
+ public String returnDirty(String dirty) {
+ return dirty; //warn
+ }
+
+ void sink(@Untainted String clear) {
+
+ }
+
+ void assignDirty(@Untainted String clear, String dirty) {
+ clear = dirty; //warn
+ }
+
+ @Untainted String dirty = getFromStatic(); //warn
+
+ static List list = new ArrayList<>();
+
+ private static String getFromStatic() {
+ return list.get(0);
+ }
+
+ @Untainted
+ static String clear = "";
+
+ static void spoil(String dirty) {
+ clear = dirty; //warn
+ }
+
+ static void testLocal(String dirty) {
+ @Untainted String clean = dirty; //warn
+ }
+
+ static void testLocal2(String dirty) {
+ @Untainted String clean = "";
+ clean = dirty; //warn
+ }
+}
diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Structure.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Structure.java
new file mode 100644
index 000000000000..b700c7560e61
--- /dev/null
+++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/sourceToSinkFlow/Structure.java
@@ -0,0 +1,66 @@
+import org.checkerframework.checker.tainting.qual.Untainted;
+
+
+class A {
+
+ void test(@Untainted String clean, String dirty) {
+ sink(clean);
+ sink(dirty); //warn
+ String cleanFromIf;
+ if (1 == 1) {
+ cleanFromIf = clean;
+ } else {
+ cleanFromIf = "1";
+ }
+ sink(cleanFromIf);
+ String dirtyFromIf;
+ if (1 == 1) {
+ dirtyFromIf = clean;
+ } else {
+ dirtyFromIf = dirty;
+ }
+ sink(dirtyFromIf); //warn
+ sink(1 == 1 ? dirty : clean); //warn
+ String cleanFromSwitch = switch (dirty) {
+ default -> "1";
+ };
+ sink(cleanFromSwitch);
+ String dirtyFromSwitch = switch (dirty) {
+ default -> dirty;
+ };
+ sink(dirtyFromSwitch); //warn
+ String cleanFromSwitchStatement;
+ switch (dirty) {
+ case "1":
+ cleanFromSwitchStatement = "2";
+ break;
+ default:
+ cleanFromSwitchStatement = "3";
+ }
+ sink(cleanFromSwitchStatement);
+ String dirtyFromSwitchStatement;
+ switch (dirty) {
+ case "1":
+ dirtyFromSwitchStatement = "2";
+ break;
+ default:
+ dirtyFromSwitchStatement = dirty;
+ }
+ sink(dirtyFromSwitchStatement); //warn
+ String cleanLoop = "1";
+ while (true) {
+ cleanLoop = "2";
+ break;
+ }
+ sink(cleanLoop);
+ String dirtyLoop = "1";
+ while (true) {
+ dirtyLoop = dirty;
+ break;
+ }
+ sink(dirtyLoop); //warn
+ }
+
+ void sink(@Untainted String s) {
+ }
+}
diff --git a/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/sourceToSink/JavaSourceToSinkFlowInspectionTest.kt b/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/sourceToSink/JavaSourceToSinkFlowInspectionTest.kt
index ef439479312f..4927598125d2 100644
--- a/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/sourceToSink/JavaSourceToSinkFlowInspectionTest.kt
+++ b/jvm/jvm-analysis-java-tests/testSrc/com/intellij/codeInspection/tests/java/sourceToSink/JavaSourceToSinkFlowInspectionTest.kt
@@ -5,12 +5,12 @@ import com.intellij.codeInspection.tests.sourceToSink.SourceToSinkFlowInspection
import com.intellij.jvm.analysis.JavaJvmAnalysisTestUtil
import com.intellij.testFramework.TestDataPath
-private const val inspectionPath = "/codeInspection/sourceToSinkFlow"
+private const val INSPECTION_PATH = "/codeInspection/sourceToSinkFlow"
-@TestDataPath("\$CONTENT_ROOT/testData$inspectionPath")
+@TestDataPath("\$CONTENT_ROOT/testData$INSPECTION_PATH")
class JavaSourceToSinkFlowInspectionTest : SourceToSinkFlowInspectionTestBase() {
override fun getBasePath(): String {
- return JavaJvmAnalysisTestUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH + inspectionPath
+ return JavaJvmAnalysisTestUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH + INSPECTION_PATH
}
fun testSimple() {
@@ -27,4 +27,77 @@ class JavaSourceToSinkFlowInspectionTest : SourceToSinkFlowInspectionTestBase()
prepareJsr()
myFixture.testHighlighting("JsrSimple.java")
}
+
+ fun testSink() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("Sink.java")
+ }
+ fun testSinkJsr() {
+ prepareJsr()
+ myFixture.testHighlighting("SinkJsr.java")
+ }
+
+ fun testCall() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("Call.java")
+ }
+
+ fun testLocalVariables() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("LocalVariable.java")
+ }
+
+ fun testEnumAnnotations() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("EnumAnnotations.java")
+ }
+ fun testFields() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("Fields.java")
+ }
+
+ fun testStructure() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("Structure.java")
+ }
+
+ fun testRecursive() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("Recursive.java")
+ }
+
+ fun testMethodPropagation() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("MethodPropagation.java")
+ }
+
+ fun testDifferentExpression() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("DifferentExpression.java")
+ }
+
+ fun testLimits() {
+ prepareCheckFramework()
+ myFixture.addClass("""
+ @SuppressWarnings({"FieldMayBeStatic", "StaticNonFinalField", "RedundantSuppression"})
+ public class Limit2 {
+
+ public final static String fromAnotherFile = "1";
+ public final static String fromAnotherFile2 = fromAnotherFile;
+ public final static String fromAnotherFile3 = fromMethod();
+ public static String fromAnotherFile4 = "1";
+ public final String fromAnotherFile5 = "1";
+ public final String fromAnotherFile6;
+
+ public Limit2() {
+ this.fromAnotherFile6 = "";
+ }
+
+ private static String fromMethod() {
+ return "null";
+ }
+ }
+ """.trimIndent())
+ myFixture.testHighlighting("Limits.java")
+ }
}
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Call.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Call.kt
new file mode 100644
index 000000000000..31fd2fb8a03d
--- /dev/null
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Call.kt
@@ -0,0 +1,31 @@
+import org.checkerframework.checker.tainting.qual.Untainted
+
+class CallsCheck {
+ fun testCall(dirty: String, clean: @Untainted String) {
+ sink(dirty) //warn
+ sink("")
+ sink(cleanMethod())
+ sink(publicMethod()) //warn
+ sink(publicFinalMethod())
+ sink(privateDirty(dirty)) //warn
+ sink(clean)
+ }
+
+ private fun privateDirty(dirty: String): String {
+ return dirty
+ }
+
+ open fun publicMethod(): String {
+ return "1"
+ }
+
+ fun publicFinalMethod(): String {
+ return "1"
+ }
+
+ private fun cleanMethod(): String {
+ return "null"
+ }
+
+ fun sink(clean: @Untainted String?) {}
+}
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/DifferentExpressions.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/DifferentExpressions.kt
new file mode 100644
index 000000000000..eee74ba3a179
--- /dev/null
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/DifferentExpressions.kt
@@ -0,0 +1,21 @@
+import org.checkerframework.checker.tainting.qual.Untainted
+
+class DifferentExpression {
+ fun test() {
+ sink(this.toString())
+ val r = Runnable {}
+ sink(r.toString()) //warn
+ sink(DifferentExpression::class.toString())
+ sink("test" + (1 - 1))
+ var x = 1
+ sink("test" + ++x)
+ sink(param2("1", )) //warn
+ }
+
+ companion object {
+ fun sink(string: @Untainted String?) {}
+ fun param2(t: String?, t1: String): String {
+ return t1
+ }
+ }
+}
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/EnumAnnotations.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/EnumAnnotations.kt
new file mode 100644
index 000000000000..0f7e9ed9f155
--- /dev/null
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/EnumAnnotations.kt
@@ -0,0 +1,21 @@
+@file:Suppress("UNUSED_PARAMETER")
+
+import org.checkerframework.checker.tainting.qual.Untainted
+
+internal class LocalCheck {
+ enum class State {
+ OFF,
+ ON
+ }
+
+ annotation class InterfaceSomething
+
+ fun test(clean: @Untainted String?, dirty: String?, state: State, interfaceSomething: InterfaceSomething) {
+ sink(clean)
+ sink(dirty) //warn
+ sink(state.name)
+ sink(interfaceSomething.toString())
+ }
+
+ fun sink(clean: @Untainted String?) {}
+}
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Fields.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Fields.kt
new file mode 100644
index 000000000000..d5e73b7f9abf
--- /dev/null
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Fields.kt
@@ -0,0 +1,37 @@
+
+import org.checkerframework.checker.tainting.qual.Untainted
+
+val cleanOuter = "2"
+var notCleanOuter = "2"
+
+class FieldsCheck(val property1: String, private val property2: String) {
+ val constant = "1"
+ private val clean = "1"
+ private var notClean = "1"
+ private var clean3 = "1"
+ private val clean2 = "2"
+ fun setNotClean(notClean: String) {
+ this.notClean = notClean
+ }
+
+ companion object {
+ val cleanOuter2 = "2"
+ var notCleanOuter2 = "2"
+ }
+
+ fun test() {
+ sink(constant)
+ sink(clean)
+ sink(clean2)
+ sink(clean3)
+ sink(notClean) //warn
+ sink(property1) //warn
+ sink(property2) //warn
+ sink(cleanOuter)
+ sink(notCleanOuter) //warn
+ sink(cleanOuter2)
+ sink(notCleanOuter2) //warn
+ }
+
+ fun sink(s: @Untainted String?) {}
+}
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Limits.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Limits.kt
new file mode 100644
index 000000000000..8c017bf53cee
--- /dev/null
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Limits.kt
@@ -0,0 +1,137 @@
+import org.checkerframework.checker.tainting.qual.Untainted
+
+object Limit {
+ const val fromAnotherFile = Limit2.fromAnotherFile
+ const val fromAnotherFile2 = Limit2.fromAnotherFile2
+ val fromAnotherFile3 = Limit2.fromAnotherFile3
+ val fromAnotherFile4 = Limit2.fromAnotherFile4
+ val fromAnotherFile5 = Limit2().fromAnotherFile5
+ val fromAnotherFile6 = Limit2().fromAnotherFile6
+ fun test(clear: @Untainted String?, dirty: String) {
+ sink(dirty) //warn
+ sink(next(next(next(next(next(next(next(next(next(next(next(next(next(next(next(dirty)))))))))))))))) //warn
+ sink(next(next(next(next(next(next(next(dirty)))))))) //warn
+ val nextVariable = next(next(next(next(next(next(next(clear)))))))
+ sink(nextVariable)
+ sink(nextVariable + nextVariable + nextVariable + nextVariable + nextVariable + nextVariable)
+ sink(nextVariable + next(next(next(next(next(next(next(dirty)))))))) //warn
+ sink("${dirty} test dirty") //warn
+ sink("$clear test clear")
+ sink(fromAnotherFile)
+ sink(fromAnotherFile2) //warn
+ sink(fromAnotherFile3) //warn
+ sink(fromAnotherFile4) //warn
+ sink(fromAnotherFile5)
+ sink(fromAnotherFile6) //warn
+ val cleanLongString = "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ clear +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh"
+ sink(cleanLongString)
+ sink(cleanLongString + dirty) //warn
+ val dirtyLongString = "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ dirty +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ clear +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh" +
+ "sdafjhasdfkhaskjdfh"
+ sink( dirtyLongString) //warn
+ val a1 = clear + 1 + clear + clear + clear + clear + clear + clear + clear
+ val a2 = a1 + 1 + a1 + a1 + a1 + a1 + a1 + a1 + a1 + a1 + a1 + a1 + a1
+ val a3 = a2 + 1 + a2 + a2 + a2 + a2 + a2 + a2 + a2 + a2 + a2 + a2 + a2
+ val a4 = a3 + 1 + a3 + a3 + a3 + a3 + a3 + a3 + a3 + a3 + a3 + a3 + a3
+ val a5 = a4 + 1 + a4 + a4 + a4 + a4 + a4 + a4 + a4 + a4 + a4 + a4 + a4
+ val a6 = a5 + 1 + a5 + a5 + a5 + a5 + a5 + a5 + a5 + a5 + a5 + a5 + a5
+ val a7 = a6 + 1 + a6 + a6 + a6 + a6 + a6 + a6 + a6 + a6 + a6 + a6 + a6
+ val a8 = a7 + 1 + a7 + a7 + a7 + a7 + a7 + a7 + a7 + a7 + a7 + a7 + a7
+ val a9 = a8 + 1 + a8 + a8 + a8 + a8 + a8 + a8 + a8 + a8 + a8 + a8 + a8
+ val a10 = a9 + 1 + a9 + a9 + a9 + a9 + a9 + a9 + a9 + a9 + a9 + a9 + a9
+ sink(a10) //warn
+ sink(a2)
+ }
+
+ fun next(next: String?): String? {
+ return next
+ }
+
+ fun sink(string: @Untainted String?) {}
+}
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/LocalInference.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/LocalInference.kt
index f17a8ab824ec..1e15f0ca23c6 100644
--- a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/LocalInference.kt
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/LocalInference.kt
@@ -1,6 +1,7 @@
-package org.checkerframework.checker.tainting.qual
+import org.checkerframework.checker.tainting.qual.Untainted
+import org.checkerframework.checker.tainting.qual.Tainted
-class LocalInference {
+open class LocalInference {
fun simpleInit() {
val s1 = source()
val s = s1
@@ -16,7 +17,7 @@ class LocalInference {
fun recursive() {
var s1 = source()
val s = s1
- s1 = s
+ s1 = s
sink(s)
}
@@ -47,7 +48,7 @@ class LocalInference {
sink(s)
}
- fun foo(): String {
+ open fun foo(): String {
return ""
}
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/LocalVariables.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/LocalVariables.kt
new file mode 100644
index 000000000000..4daf0831d883
--- /dev/null
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/LocalVariables.kt
@@ -0,0 +1,31 @@
+@file:Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER")
+
+import org.checkerframework.checker.tainting.qual.Untainted
+
+class LocalCheck {
+ fun test(clean: @Untainted MutableList, cleanList2: @Untainted MutableList, t: @Untainted String?, dirty: String?) {
+ sink(t)
+ sink(clean[0])
+ val list1: List = clean
+ update(list1) //not highlighted in current realisation, might be changed
+ clean.add(dirty) //not highlighted in current realisation, might be changed
+ sink(list1[0]) //warn
+ sink(clean[0]) //warn
+ val list3: List = cleanList2
+ sink(list3[0])
+ sink(dirty) //warn
+ var clean2 = t + dirty
+ sink(clean2) // warn
+ var newT: String?
+ newT = t
+ sink(newT)
+ val runnable = Runnable {
+ sink(newT) //warn
+ }
+ val runnable2: () -> Unit = { sink(newT) } //warn
+ newT = dirty
+ }
+
+ private fun update(list: List?) {}
+ fun sink(clean: @Untainted String?) {}
+}
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/MethodPropagation.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/MethodPropagation.kt
new file mode 100644
index 000000000000..ae4b455e55d8
--- /dev/null
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/MethodPropagation.kt
@@ -0,0 +1,62 @@
+import org.checkerframework.checker.tainting.qual.Untainted
+
+open class MethodPropagation {
+ fun test1(dirty: String?, clean: @Untainted String?) {
+
+ sink(dirty) //warn
+ sink(clean)
+ }
+
+ private fun recursive(dirty: String?, clean: @Untainted String?): String {
+ if (clean === "") {
+ val a = recursive(dirty, clean)
+ sink(a) //warn
+ return recursive(dirty, clean)
+ }
+ return recursive(clean, clean)
+ }
+
+ fun test2(dirty: String?, clean: @Untainted String?) {
+ sink(next(dirty)) //warn
+ sink(next(clean))
+ sink(nextPublic(dirty)) //warn
+ sink(nextPublic(clean)) //warn (public)
+ sink(alwaysClean(dirty))
+ sink(alwaysClean(clean))
+ sink(staticNext(dirty)) //warn
+ sink(staticNext(clean))
+ sink(next(next(clean)))
+ sink(next(next(next(next(next(next(next(next(clean)))))))))
+ sink(next(next(next(next(next(next(next(next(dirty))))))))) //warn
+ sink(alwaysClean(next(next(next(next(next(next(next(clean)))))))))
+ sink(alwaysClean(next(next(next(next(next(next(next(dirty)))))))))
+ sink(next(next(next(next(next(next(next(alwaysClean(clean)))))))))
+ sink(next(next(next(next(next(next(next(alwaysClean(dirty)))))))))
+ sink(next(alwaysClean(clean)))
+ sink(next(alwaysClean(dirty)))
+ val alwaysClean = alwaysClean(next(next(next(next(next(next(next(clean))))))))
+ sink(alwaysClean)
+ val alwaysClean2 = alwaysClean(next(next(next(next(next(next(next(dirty))))))))
+ sink(alwaysClean2)
+ }
+
+ private fun next(next: String?): String? {
+ return next
+ }
+
+ private fun alwaysClean(next: String?): String {
+ return "next"
+ }
+
+ open fun nextPublic(next: String?): String? {
+ return next
+ }
+
+ companion object {
+ fun staticNext(next: String?): String? {
+ return next
+ }
+
+ fun sink(string: @Untainted String?) {}
+ }
+}
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/MethodsAsFields.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/MethodsAsFields.kt
new file mode 100644
index 000000000000..0ac5500e6002
--- /dev/null
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/MethodsAsFields.kt
@@ -0,0 +1,11 @@
+import org.checkerframework.checker.tainting.qual.Untainted
+
+class MethodAsFieldTest {
+
+ fun test(clean: @Untainted MethodAsFields, unclean: MethodAsFields) {
+ sink(clean.t)
+ sink(unclean.t) //warn
+ }
+
+ fun sink(string: @Untainted String?) {}
+}
\ No newline at end of file
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Parameters.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Parameters.kt
new file mode 100644
index 000000000000..0f69072e70ed
--- /dev/null
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Parameters.kt
@@ -0,0 +1,13 @@
+@file:Suppress("UNUSED_PARAMETER")
+
+import org.checkerframework.checker.tainting.qual.Tainted
+import org.checkerframework.checker.tainting.qual.Untainted
+
+class LocalCheck {
+ fun test(clean: @Untainted String = dirty()) {
+ }
+
+ private fun dirty(): @Tainted String {
+ return ""
+ }
+}
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Simple.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Simple.kt
index 54538de9c023..c84c46216843 100644
--- a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Simple.kt
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Simple.kt
@@ -1,4 +1,5 @@
-package org.checkerframework.checker.tainting.qual
+import org.checkerframework.checker.tainting.qual.Untainted
+import org.checkerframework.checker.tainting.qual.Tainted
class Simple {
fun simple() {
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Sink.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Sink.kt
new file mode 100644
index 000000000000..4a4f873872fc
--- /dev/null
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Sink.kt
@@ -0,0 +1,78 @@
+@file:Suppress("unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "UNUSED_VALUE")
+
+import org.checkerframework.checker.tainting.qual.Tainted
+import org.checkerframework.checker.tainting.qual.Untainted
+
+val dirty: @Tainted String = ""
+
+var clean: @Untainted String = dirty //warn
+
+var clean2: @Untainted String = ""
+
+class SinkTestKotlin {
+
+ fun breakClean2(dirty: String) {
+ clean2 = dirty // warn
+ }
+
+ companion object {
+ val dirty: @Tainted String = ""
+
+ var clean: @Untainted String = dirty //warn
+
+ var clean2: @Untainted String = ""
+
+ fun breakClean2(dirty: String) {
+ clean2 = dirty // warn
+ }
+ }
+
+ fun test(string: String?) {
+ sink(string) //warn
+ }
+
+ fun returnDirty(dirty: String?): @Untainted String? {
+ return dirty //warn
+ }
+
+ fun sink(clear: @Untainted String?) {
+ }
+
+ fun assignDirty(clear: @Untainted String?, dirty: String?) {
+ var clear1 = clear
+ var clear2: String? = clear1
+ clear1 = dirty //warn
+ clear2 = dirty
+ }
+
+ var dirty: @Untainted String? = getFromStatic() //warn
+
+ private fun getFromStatic(): @Tainted String {
+ return ""
+ }
+
+ var clear: @Untainted String? = ""
+
+ fun spoil(dirty: String?) {
+ clear = dirty //warn
+ }
+
+ fun testLocal(dirty: String?) {
+ val clean: @Untainted String? = dirty //warn
+ }
+
+ fun testParameter(clean: @Untainted String = getDirty()) { //warn
+
+ }
+
+ fun getDirty(): @Tainted String = ""
+
+ fun testLocal2(dirty: String?) {
+ var clean: @Untainted String? = ""
+ clean = dirty //warn
+ }
+
+ fun println(t: String): String {
+ return t
+ }
+}
\ No newline at end of file
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/SinkJsr.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/SinkJsr.kt
new file mode 100644
index 000000000000..c4832ce56d2b
--- /dev/null
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/SinkJsr.kt
@@ -0,0 +1,92 @@
+@file:Suppress("unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "UNUSED_VALUE")
+
+import javax.annotation.Tainted
+import javax.annotation.Untainted
+
+
+@Tainted
+val dirty: String = ""
+
+@Untainted
+var clean: String = dirty //warn
+
+@Untainted
+var clean2: String = ""
+
+class SinkTestKotlin {
+
+ fun breakClean2(dirty: String) {
+ clean2 = dirty // warn
+ }
+
+ companion object {
+ @Tainted
+ val dirty: String = ""
+
+ @Untainted
+ var clean: String = dirty //warn
+
+ @Untainted
+ var clean2: String = ""
+
+ fun breakClean2(dirty: String) {
+ clean2 = dirty // warn
+ }
+ }
+
+ fun test(string: String?) {
+ sink(string) //warn
+ }
+
+ @Untainted
+ fun returnDirty(dirty: String?): String? {
+ return dirty //warn
+ }
+
+
+ fun sink(@Untainted clear: String?) {
+ println(clear!!)
+ }
+
+ fun assignDirty(@Untainted clear: String?, dirty: String?) {
+ @Untainted var clear1: String? = clear
+ var clear2: String? = clear1
+ clear1 = dirty //warn
+ clear2 = dirty
+ }
+
+ @Untainted
+ var dirty: String? = getFromStatic() //warn
+
+ @Tainted
+ private fun getFromStatic(): String {
+ return ""
+ }
+
+ @Untainted
+ var clear: String? = ""
+
+ fun spoil(dirty: String?) {
+ clear = dirty //warn
+ }
+
+ fun testLocal(dirty: String?) {
+ @Untainted val clean: String? = dirty //warn
+ }
+
+ fun testParameter(@Untainted clean: String = getDirty()) { //warn
+
+ }
+
+ @Tainted
+ fun getDirty(): String = ""
+
+ fun testLocal2(dirty: String?) {
+ @Untainted var clean: String? = ""
+ clean = dirty //warn
+ }
+
+ fun println(t: String): String {
+ return t
+ }
+}
\ No newline at end of file
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Structure.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Structure.kt
new file mode 100644
index 000000000000..f6806fd59409
--- /dev/null
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/Structure.kt
@@ -0,0 +1,45 @@
+@file:Suppress("unused", "UNUSED_PARAMETER", "UNUSED_VARIABLE", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "UNUSED_VALUE")
+
+import org.checkerframework.checker.tainting.qual.Untainted
+
+class StructureTest {
+ fun test(clean: @Untainted String, unclean: String) {
+ sink(unclean) //warn
+ sink(clean)
+
+ sink(if (1 == 1) unclean else clean) //warn
+ sink(if (1 == 1) clean else clean)
+
+ val s = if (1 == 1) unclean else clean
+ sink(s) //warn
+
+ sink(when {
+ 1 == 1 -> clean
+ else -> unclean
+ }) //warn
+
+ sink(when {
+ 1 == 1 -> clean
+ else -> clean
+ })
+
+ val s1: String = when {
+ 1 == 1 -> clean
+ else -> unclean
+ }
+
+ sink(s1) //warn
+
+ var sumDirty: String = clean
+ var sumClean = clean
+ while (true) {
+ sumDirty += unclean
+ sumClean += clean
+ break
+ }
+ sink(sumDirty) //warn
+ sink(sumClean)
+ }
+
+ fun sink(s: @Untainted String?) {}
+}
\ No newline at end of file
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesCheckFramework.after.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesCheckFramework.after.kt
index d7b306b62b68..21733fa2d900 100644
--- a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesCheckFramework.after.kt
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesCheckFramework.after.kt
@@ -1,16 +1,9 @@
import org.checkerframework.checker.tainting.qual.Untainted
-val sOuterField: String = getSomething()
-var sOuterField2: @Untainted String = getSomething()
-
-fun getSomething(): String {
- return "1"
-}
-
internal class CommonCases {
public val sField: String? = null
private fun test(s: @Untainted String): @Untainted String {
- val s1 = s + getS(s) + sField + sOuterField + sOuterField2 + "1".extFunc() + "1".extFunc2(s) + comObject + comObject2
+ val s1 = s + getS(s) + sField + "1".extFunc() + "1".extFunc2(s) + comObject2
return s1
}
@@ -19,7 +12,6 @@ internal class CommonCases {
}
companion object{
- val comObject = getSomething2()
var comObject2: @Untainted String = getSomething2()
private fun getSomething2(): String {
@@ -29,4 +21,4 @@ internal class CommonCases {
}
private fun String.extFunc() = "test"
-private fun String.extFunc2(s: @Untainted String): @Untainted @Untainted String = s
+private fun String.extFunc2(s: String): @Untainted String = s
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesCheckFramework.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesCheckFramework.kt
index 834df6a9cc9b..93dfce310db9 100644
--- a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesCheckFramework.kt
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesCheckFramework.kt
@@ -1,16 +1,9 @@
import org.checkerframework.checker.tainting.qual.Untainted
-val sOuterField: String = getSomething()
-var sOuterField2: String = getSomething()
-
-fun getSomething(): String {
- return "1"
-}
-
internal class CommonCases {
public val sField: String? = null
private fun test(s: String): @Untainted String {
- val s1 = s + getS(s) + sField + sOuterField + sOuterField2 + "1".extFunc() + "1".extFunc2(s) + comObject + comObject2
+ val s1 = s + getS(s) + sField + "1".extFunc() + "1".extFunc2(s) + comObject2
return s1
}
@@ -19,7 +12,6 @@ internal class CommonCases {
}
companion object{
- val comObject = getSomething2()
var comObject2 = getSomething2()
private fun getSomething2(): String {
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesJsr.after.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesJsr.after.kt
index f02d42d5ddd7..a256260b44fa 100644
--- a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesJsr.after.kt
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesJsr.after.kt
@@ -1,17 +1,11 @@
import javax.annotation.Untainted
-val sOuterField: String = getSomething()
-@get:Untainted
-var sOuterField2: String = getSomething()
-
-fun getSomething(): String {
- return "1"
-}
-
internal class CommonCases {
public val sField: String? = null
- private fun test(@Untainted s: String): @Untainted String {
- val s1 = s + getS(s) + sField + sOuterField + sOuterField2 + "1".extFunc() + "1".extFunc2(s) + comObject + comObject2
+
+ @Untainted
+ private fun test(@Untainted s: String): String {
+ val s1 = s + getS(s) + sField + "1".extFunc() + "1".extFunc2(s) + comObject2
return s1
}
@@ -21,8 +15,7 @@ internal class CommonCases {
}
companion object{
- val comObject = getSomething2()
- @get:Untainted
+ @field:Untainted
var comObject2 = getSomething2()
private fun getSomething2(): String {
@@ -33,4 +26,4 @@ internal class CommonCases {
private fun String.extFunc() = "test"
@Untainted
-private fun String.extFunc2(@Untainted s: String) = s
+private fun String.extFunc2(s: String) = s
diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesJsr.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesJsr.kt
index d40e76b01747..7dd8f059c5b0 100644
--- a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesJsr.kt
+++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/sourceToSinkFlow/markAsSafeFix/CommonCasesJsr.kt
@@ -1,16 +1,11 @@
import javax.annotation.Untainted
-val sOuterField: String = getSomething()
-var sOuterField2: String = getSomething()
-
-fun getSomething(): String {
- return "1"
-}
-
internal class CommonCases {
public val sField: String? = null
- private fun test(s: String): @Untainted String {
- val s1 = s + getS(s) + sField + sOuterField + sOuterField2 + "1".extFunc() + "1".extFunc2(s) + comObject + comObject2
+
+ @Untainted
+ private fun test(s: String): String {
+ val s1 = s + getS(s) + sField + "1".extFunc() + "1".extFunc2(s) + comObject2
return s1
}
@@ -19,7 +14,6 @@ internal class CommonCases {
}
companion object{
- val comObject = getSomething2()
var comObject2 = getSomething2()
private fun getSomething2(): String {
diff --git a/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/tests/kotlin/sourceToSink/KotlinSourceToSinkFlowInspectionTest.kt b/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/tests/kotlin/sourceToSink/KotlinSourceToSinkFlowInspectionTest.kt
index e1a46ef9d15c..a575cf3f587c 100644
--- a/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/tests/kotlin/sourceToSink/KotlinSourceToSinkFlowInspectionTest.kt
+++ b/jvm/jvm-analysis-kotlin-tests/testSrc/com/intellij/codeInspection/tests/kotlin/sourceToSink/KotlinSourceToSinkFlowInspectionTest.kt
@@ -5,11 +5,11 @@ import com.intellij.codeInspection.tests.sourceToSink.SourceToSinkFlowInspection
import com.intellij.jvm.analysis.KotlinJvmAnalysisTestUtil
import com.intellij.testFramework.TestDataPath
-private const val inspectionPath = "/codeInspection/sourceToSinkFlow"
+private const val INSPECTION_PATH = "/codeInspection/sourceToSinkFlow"
-@TestDataPath("\$CONTENT_ROOT/testData$inspectionPath")
+@TestDataPath("\$CONTENT_ROOT/testData$INSPECTION_PATH")
class KotlinSourceToSinkFlowInspectionTest : SourceToSinkFlowInspectionTestBase() {
- override fun getBasePath() = KotlinJvmAnalysisTestUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH + inspectionPath
+ override fun getBasePath() = KotlinJvmAnalysisTestUtil.TEST_DATA_PROJECT_RELATIVE_BASE_PATH + INSPECTION_PATH
fun testSimple() {
prepareCheckFramework()
@@ -21,11 +21,101 @@ class KotlinSourceToSinkFlowInspectionTest : SourceToSinkFlowInspectionTestBase(
myFixture.testHighlighting("LocalInference.kt")
}
+ fun testSink() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("Sink.kt")
+ }
+
+ fun testSinkJsr() {
+ prepareJsr()
+ myFixture.testHighlighting("SinkJsr.kt")
+ }
+
+ fun testCall() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("Call.kt")
+ }
+
+ fun testLocalVariable() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("LocalVariables.kt")
+ }
+
+ fun testParameters() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("Parameters.kt")
+ }
+
+ fun testEnumAnnotations() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("EnumAnnotations.kt")
+ }
+
+ fun testFields() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("Fields.kt")
+ }
+
+ fun testStructure() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("Structure.kt")
+ }
+
+ fun testMethodPropagation() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("MethodPropagation.kt")
+ }
+
fun testKotlinPropertyPropagateFix() {
prepareCheckFramework()
myFixture.configureByFile("Property.kt")
- val propagateAction = myFixture.getAvailableIntention(JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.text"))!!
+ val propagateAction = myFixture.getAvailableIntention(
+ JvmAnalysisBundle.message("jvm.inspections.source.unsafe.to.sink.flow.propagate.safe.text"))!!
myFixture.launchAction(propagateAction)
myFixture.checkResultByFile("Property.after.kt")
}
+
+ fun testLimits() {
+ prepareCheckFramework()
+ myFixture.addClass("""
+ @SuppressWarnings({"FieldMayBeStatic", "StaticNonFinalField", "RedundantSuppression"})
+ public class Limit2 {
+
+ public final static String fromAnotherFile = "1";
+ public final static String fromAnotherFile2 = fromAnotherFile;
+ public final static String fromAnotherFile3 = fromMethod();
+ public static String fromAnotherFile4 = "1";
+ public final String fromAnotherFile5 = "1";
+ public final String fromAnotherFile6;
+
+ public Limit2() {
+ this.fromAnotherFile6 = "";
+ }
+
+ private static String fromMethod() {
+ return "null";
+ }
+ }
+ """.trimIndent())
+ myFixture.testHighlighting("Limits.kt")
+ }
+
+ fun testMethodsAsFields() {
+ prepareCheckFramework()
+ myFixture.addClass("""
+ public class MethodAsFields {
+ private static final String t = "1";
+
+ public String getT() {
+ return t;
+ }
+ }
+ """.trimIndent())
+ myFixture.testHighlighting("MethodsAsFields.kt")
+ }
+
+ fun testDifferentExpressions() {
+ prepareCheckFramework()
+ myFixture.testHighlighting("DifferentExpressions.kt")
+ }
}
\ No newline at end of file
diff --git a/jvm/jvm-analysis-tests/src/com/intellij/codeInspection/tests/sourceToSink/SourceToSinkFlowInspectionTestBase.kt b/jvm/jvm-analysis-tests/src/com/intellij/codeInspection/tests/sourceToSink/SourceToSinkFlowInspectionTestBase.kt
index d8dbca6c43db..cae652a8b803 100644
--- a/jvm/jvm-analysis-tests/src/com/intellij/codeInspection/tests/sourceToSink/SourceToSinkFlowInspectionTestBase.kt
+++ b/jvm/jvm-analysis-tests/src/com/intellij/codeInspection/tests/sourceToSink/SourceToSinkFlowInspectionTestBase.kt
@@ -7,6 +7,6 @@ abstract class SourceToSinkFlowInspectionTestBase : TaintedTestBase() {
override val inspection: SourceToSinkFlowInspection = SourceToSinkFlowInspection()
override fun getProjectDescriptor(): LightProjectDescriptor {
- return JAVA_8
+ return JAVA_19
}
}
\ No newline at end of file
diff --git a/jvm/jvm-analysis-tests/src/com/intellij/codeInspection/tests/sourceToSink/TaintedTestBase.kt b/jvm/jvm-analysis-tests/src/com/intellij/codeInspection/tests/sourceToSink/TaintedTestBase.kt
index c559dd2de8b4..5d32566ff189 100644
--- a/jvm/jvm-analysis-tests/src/com/intellij/codeInspection/tests/sourceToSink/TaintedTestBase.kt
+++ b/jvm/jvm-analysis-tests/src/com/intellij/codeInspection/tests/sourceToSink/TaintedTestBase.kt
@@ -8,7 +8,7 @@ abstract class TaintedTestBase : JvmInspectionTestBase() {
package org.checkerframework.checker.tainting.qual;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
- @Target({ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.METHOD})
+ @Target({ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
public @interface Tainted {
}
""".trimIndent())