[java-inspections] IDEA-282916 Improve tainted analysis, support local methods

GitOrigin-RevId: 27871a7bbab9f093863d110711f7bb67c9b09fd9
This commit is contained in:
Mikhail Pyltsin
2023-05-05 11:30:39 +02:00
committed by intellij-monorepo-bot
parent 8579ca0616
commit 4edcb035e7
43 changed files with 1751 additions and 228 deletions

View File

@@ -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)
}

View File

@@ -1,8 +1,8 @@
<html><body>
Reports cases when non-safe string is passed to a method with parameter marked with <code>@Untainted</code> 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.
<p>
Safe object is:
Safe object is:
<ul>
<li>a string literal, interface instance or enum object</li>
<li>a result of a call of a method that is marked as <code>@Untainted</code></li>

View File

@@ -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

View File

@@ -109,7 +109,7 @@ internal class LoggingStringPartEvaluator {
val visitor = object : AbstractUastVisitor() {
val used: MutableSet<ULocalVariable> = 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()

View File

@@ -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<PsiElement> 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<PsiElement> 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);

View File

@@ -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()) {

View File

@@ -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))

View File

@@ -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<Pair<PsiElement, List<TaintValue>>, TaintValue> = HashMap()
private val myNonMarkedElements: MutableList<NonMarkedElement> = ArrayList()
//todo add test for new feature
private val skipClasses: Set<String> = 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<TaintValue> = 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<NonMarkedElement>
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<UExpression?>): 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<NonMarkedElement?>()
// 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>): TaintValue {
if (!equalFiles(analyzeContext, uMethod)) return TaintValue.UNKNOWN
private fun analyzeMethod(uMethod: UMethod, sourceContext: AnalyzeContext, arguments: List<TaintValue>): 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<UExpression?, *, TaintValue> {
@@ -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()

View File

@@ -98,7 +98,13 @@ public class TaintNode extends PresentableNodeDescriptor<TaintNode> {
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) {

View File

@@ -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? {

View File

@@ -0,0 +1,37 @@
import org.checkerframework.checker.tainting.qual.Untainted;
class CallsCheck {
public void testCall(String dirty, @Untainted String clean) {
sink(<warning descr="Unknown string is used as safe parameter">dirty</warning>); //warn
sink("");
sink(cleanMethod());
sink(<warning descr="Unknown string is used as safe parameter">publicMethod()</warning>); //warn
sink(publicFinalMethod());
sink(<warning descr="Unknown string is used as safe parameter">privateDirty(dirty)</warning>); //warn
sink(<warning descr="Unknown string is used as safe parameter">dirty.toLowerCase()</warning>); //warn
sink(dirty.getClass().getName());
sink(<warning descr="Unknown string is used as safe parameter">dirty.replace("1", "2")</warning>); //warn
sink(clean);
sink(<warning descr="Unknown string is used as safe parameter">clean.replace("1", dirty)</warning>); //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) {
}
}

View File

@@ -0,0 +1,25 @@
import org.checkerframework.checker.tainting.qual.Untainted;
public class DifferentExpression {
public void test() {
sink(this.toString());
Runnable r = () -> {
};
sink(<warning descr="Unknown string is used as safe parameter">r.toString()</warning>); //warn
sink(DifferentExpression.class.toString());
sink("test" + (1 - 1));
int x = 1;
sink("test" + (++x));
sink(<warning descr="Unknown string is used as safe parameter">param2("1",<error descr="Expression expected"> </error>)</warning>); //warn
}
public static void sink(@Untainted String string) {
}
public static String param2(String t, String t1) {
return t1;
}
}

View File

@@ -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(<warning descr="Unknown string is used as safe parameter">dirty</warning>); //warn
sink(state.name());
sink(interfaceSomething.toString());
}
void sink(@Untainted String clean) {
}
}

View File

@@ -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(<warning descr="Unknown string is used as safe parameter">notClean</warning>); //warn
sink(<warning descr="Unknown string is used as safe parameter">finalAppliedFromConstructor</warning>); //warn
sink(<warning descr="Unknown string is used as safe parameter">appliedFromConstructor</warning>); //warn
}
void sink(@Untainted String s) {
}
}

View File

@@ -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(<warning descr="Unknown string is used as safe parameter">dirty</warning>); //warn
sink(<warning descr="Unknown string is used as safe parameter">next(next(next(next(next(next(next(next(next(next(next(next(next(next(next(dirty)))))))))))))))</warning>); //warn complex
sink(<warning descr="Unknown string is used as safe parameter">next(next(next(next(next(next(next(dirty)))))))</warning>); //warn
sink(next(next(next(next(next(next(next(clear))))))));
sink(<weak_warning descr="Too complex to check that the string is safe in a safe context">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)))))))</weak_warning>);
sink(<weak_warning descr="Too complex to check that the string is safe in a safe context">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)))))))</weak_warning>); //warn
sink(fromAnotherFile);
sink(<warning descr="Unknown string is used as safe parameter">fromAnotherFile2</warning>); //warn
sink(<warning descr="Unknown string is used as safe parameter">fromAnotherFile3</warning>); //warn
sink(<warning descr="Unknown string is used as safe parameter">fromAnotherFile4</warning>); //warn
sink(fromAnotherFile5);
sink(<warning descr="Unknown string is used as safe parameter">fromAnotherFile6</warning>); //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(<warning descr="Unknown string is used as safe parameter">cleanLongString + dirty</warning>); //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(<warning descr="Unknown string is used as safe parameter">dirtyLongString</warning>); //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(<weak_warning descr="Too complex to check that the string is safe in a safe context">a10</weak_warning>); //warn
sink(a2);
}
public static String next(String next) {
return next;
}
public static void sink(@Untainted String string) {
}
}

View File

@@ -0,0 +1,46 @@
import org.checkerframework.checker.tainting.qual.Untainted;
import java.util.List;
class LocalCheck {
public void test(@Untainted List<String> clean, @Untainted List<String> cleanList2, @Untainted String t, String dirty) {
sink(t);
sink(clean.get(0));
List<String> list1 = clean;
List<String> list2 = clean;
update(list1); //not highlighted in current realisation, might be changed
list2.add(dirty); //not highlighted in current realisation, might be changed
sink(<warning descr="Unknown string is used as safe parameter">list1.get(0)</warning>); //warn
sink(<warning descr="Unknown string is used as safe parameter">list2.get(0)</warning>); //warn
sink(clean.get(0));
List<String> list3 = cleanList2;
sink(list3.get(0));
sink(<warning descr="Unknown string is used as safe parameter">dirty</warning>); //warn
String clean2 = t;
sink(t);
clean2 = dirty;
sink(<warning descr="Unknown string is used as safe parameter">clean2</warning>); //warn
String toDirty = t;
sink(toDirty);
new Runnable() {
@Override
public void run() {
sink(toDirty);
}
};
Runnable runnable = () -> sink(toDirty);
}
private void update(List<String> list) {
}
public void sink(@Untainted String clean) {
}
}

View File

@@ -0,0 +1,70 @@
import org.checkerframework.checker.tainting.qual.Untainted;
public class MethodPropagation {
public void test1(String dirty, @Untainted String clean) {
sink(<warning descr="Unknown string is used as safe parameter">dirty</warning>); //warn
sink(clean);
}
private String recursive(String dirty, @Untainted String clean) {
if (clean == "") {
String a = recursive(dirty, clean);
sink(<warning descr="Unknown string is used as safe parameter">a</warning>); //warn
return recursive(dirty, clean);
}
return recursive(clean, clean);
}
public void test2(String dirty, @Untainted String clean) {
sink(<warning descr="Unknown string is used as safe parameter">next(dirty)</warning>); //warn
sink(next(clean));
sink(<warning descr="Unknown string is used as safe parameter">nextPublic(dirty)</warning>); //warn
sink(<warning descr="Unknown string is used as safe parameter">nextPublic(clean)</warning>); //warn (public)
sink(alwaysClean(dirty));
sink(alwaysClean(clean));
sink(<warning descr="Unknown string is used as safe parameter">staticNext(dirty)</warning>); //warn
sink(staticNext(clean));
sink(next(next(clean)));
sink(next(next(next(next(next(next(next(next(clean)))))))));
sink(<warning descr="Unknown string is used as safe parameter">next(next(next(next(next(next(next(next(dirty))))))))</warning>); //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) {
}
}

View File

@@ -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,<error descr="Expression expected">)</error>;
sink(<warning descr="Unknown string is used as safe parameter">a</warning>);
return recursive(clean, clean);
}
return recursive(clean, clean);
}
public static void sink(@Untainted String string) {
}
}

View File

@@ -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(<warning descr="Unknown string is used as safe parameter">string</warning>); //warn
}
@Untainted
public String returnDirty(String dirty) {
return <warning descr="Unknown string is returned from safe method">dirty</warning>; //warn
}
void sink(@Untainted String clear) {
}
void assignDirty(@Untainted String clear, String dirty) {
clear = <warning descr="Unknown string is used as safe parameter">dirty</warning>; //warn
}
@Untainted String dirty = <warning descr="Unknown string is used in a safe context">getFromStatic()</warning>; //warn
static List<String> list = new ArrayList<>();
private static String getFromStatic() {
return list.get(0);
}
@Untainted
static String clear = "";
static void spoil(String dirty) {
clear = <warning descr="Unknown string is used in a safe context">dirty</warning>; //warn
}
static void testLocal(String dirty) {
@Untainted String clean = <warning descr="Unknown string is assigned to safe variable">dirty</warning>; //warn
}
static void testLocal2(String dirty) {
@Untainted String clean = "";
clean = <warning descr="Unknown string is assigned to safe variable">dirty</warning>; //warn
}
}

View File

@@ -0,0 +1,48 @@
import javax.annotation.Untainted;
import java.util.ArrayList;
import java.util.List;
class SinkTest {
public void test(String string) {
sink(<warning descr="Unknown string is used as safe parameter">string</warning>); //warn
}
@Untainted
public String returnDirty(String dirty) {
return <warning descr="Unknown string is returned from safe method">dirty</warning>; //warn
}
void sink(@Untainted String clear) {
}
void assignDirty(@Untainted String clear, String dirty) {
clear = <warning descr="Unknown string is used as safe parameter">dirty</warning>; //warn
}
@Untainted String dirty = <warning descr="Unknown string is used in a safe context">getFromStatic()</warning>; //warn
static List<String> list = new ArrayList<>();
private static String getFromStatic() {
return list.get(0);
}
@Untainted
static String clear = "";
static void spoil(String dirty) {
clear = <warning descr="Unknown string is used in a safe context">dirty</warning>; //warn
}
static void testLocal(String dirty) {
@Untainted String clean = <warning descr="Unknown string is assigned to safe variable">dirty</warning>; //warn
}
static void testLocal2(String dirty) {
@Untainted String clean = "";
clean = <warning descr="Unknown string is assigned to safe variable">dirty</warning>; //warn
}
}

View File

@@ -0,0 +1,66 @@
import org.checkerframework.checker.tainting.qual.Untainted;
class A {
void test(@Untainted String clean, String dirty) {
sink(clean);
sink(<warning descr="Unknown string is used as safe parameter">dirty</warning>); //warn
String cleanFromIf;
if (1 == 1) {
cleanFromIf = clean;
} else {
cleanFromIf = "1";
}
sink(cleanFromIf);
String dirtyFromIf;
if (1 == 1) {
dirtyFromIf = clean;
} else {
dirtyFromIf = dirty;
}
sink(<warning descr="Unknown string is used as safe parameter">dirtyFromIf</warning>); //warn
sink(<warning descr="Unknown string is used as safe parameter">1 == 1 ? dirty : clean</warning>); //warn
String cleanFromSwitch = switch (dirty) {
default -> "1";
};
sink(cleanFromSwitch);
String dirtyFromSwitch = switch (dirty) {
default -> dirty;
};
sink(<warning descr="Unknown string is used as safe parameter">dirtyFromSwitch</warning>); //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(<warning descr="Unknown string is used as safe parameter">dirtyFromSwitchStatement</warning>); //warn
String cleanLoop = "1";
while (true) {
cleanLoop = "2";
break;
}
sink(cleanLoop);
String dirtyLoop = "1";
while (true) {
dirtyLoop = dirty;
break;
}
sink(<warning descr="Unknown string is used as safe parameter">dirtyLoop</warning>); //warn
}
void sink(@Untainted String s) {
}
}

View File

@@ -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")
}
}

View File

@@ -0,0 +1,31 @@
import org.checkerframework.checker.tainting.qual.Untainted
class CallsCheck {
fun testCall(dirty: String, clean: @Untainted String) {
sink(<warning descr="Unknown string is used as safe parameter">dirty</warning>) //warn
sink("")
sink(cleanMethod())
sink(<warning descr="Unknown string is used as safe parameter">publicMethod()</warning>) //warn
sink(publicFinalMethod())
sink(<warning descr="Unknown string is used as safe parameter">privateDirty(dirty)</warning>) //warn
sink(clean)
}
private fun privateDirty(dirty: String): String {
return dirty
}
<warning descr="[NON_FINAL_MEMBER_IN_FINAL_CLASS] 'open' has no effect in a final class">open</warning> fun publicMethod(): String {
return "1"
}
fun publicFinalMethod(): String {
return "1"
}
private fun cleanMethod(): String {
return "null"
}
fun sink(<warning descr="[UNUSED_PARAMETER] Parameter 'clean' is never used">clean</warning>: @Untainted String?) {}
}

View File

@@ -0,0 +1,21 @@
import org.checkerframework.checker.tainting.qual.Untainted
class DifferentExpression {
fun test() {
sink(this.toString())
val r = Runnable {}
sink(<warning descr="Unknown string is used as safe parameter">r.toString()</warning>) //warn
sink(DifferentExpression::class.toString())
sink("test" + (1 - 1))
var x = 1
sink("test" + ++x)
sink(param2(<error descr="[NO_VALUE_FOR_PARAMETER] No value passed for parameter 't1'">"1", )</error>) //warn
}
companion object {
fun sink(<warning descr="[UNUSED_PARAMETER] Parameter 'string' is never used">string</warning>: @Untainted String?) {}
fun param2(<warning descr="[UNUSED_PARAMETER] Parameter 't' is never used">t</warning>: String?, t1: String): String {
return t1
}
}
}

View File

@@ -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(<warning descr="Unknown string is used as safe parameter">dirty</warning>) //warn
sink(state.name)
sink(interfaceSomething.toString())
}
fun sink(clean: @Untainted String?) {}
}

View File

@@ -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(<warning descr="Unknown string is used as safe parameter">notClean</warning>) //warn
sink(<warning descr="Unknown string is used as safe parameter">property1</warning>) //warn
sink(<warning descr="Unknown string is used as safe parameter">property2</warning>) //warn
sink(cleanOuter)
sink(<warning descr="Unknown string is used as safe parameter">notCleanOuter</warning>) //warn
sink(cleanOuter2)
sink(<warning descr="Unknown string is used as safe parameter">notCleanOuter2</warning>) //warn
}
fun sink(<warning descr="[UNUSED_PARAMETER] Parameter 's' is never used">s</warning>: @Untainted String?) {}
}

View File

@@ -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(<warning descr="Unknown string is used as safe parameter">dirty</warning>) //warn
sink(<warning descr="Unknown string is used as safe parameter">next(next(next(next(next(next(next(next(next(next(next(next(next(next(next(dirty)))))))))))))))</warning>) //warn
sink(<warning descr="Unknown string is used as safe parameter">next(next(next(next(next(next(next(dirty)))))))</warning>) //warn
val nextVariable = next(next(next(next(next(next(next(clear)))))))
sink(nextVariable)
sink(nextVariable + nextVariable + nextVariable + nextVariable + nextVariable + nextVariable)
sink(<warning descr="Unknown string is used as safe parameter">nextVariable + next(next(next(next(next(next(next(dirty)))))))</warning>) //warn
sink(<warning descr="Unknown string is used as safe parameter">"${dirty} test dirty"</warning>) //warn
sink("$clear test clear")
sink(fromAnotherFile)
sink(<warning descr="Unknown string is used as safe parameter">fromAnotherFile2</warning>) //warn
sink(<warning descr="Unknown string is used as safe parameter">fromAnotherFile3</warning>) //warn
sink(<warning descr="Unknown string is used as safe parameter">fromAnotherFile4</warning>) //warn
sink(fromAnotherFile5)
sink(<warning descr="Unknown string is used as safe parameter">fromAnotherFile6</warning>) //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(<warning descr="Unknown string is used as safe parameter">cleanLongString + dirty</warning>) //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( <warning descr="Unknown string is used as safe parameter">dirtyLongString</warning>) //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(<weak_warning descr="Too complex to check that the string is safe in a safe context">a10</weak_warning>) //warn
sink(a2)
}
fun next(next: String?): String? {
return next
}
fun sink(<warning descr="[UNUSED_PARAMETER] Parameter 'string' is never used">string</warning>: @Untainted String?) {}
}

View File

@@ -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
<warning descr="[UNUSED_VALUE] The value 's' assigned to 'var s1: String defined in org.checkerframework.checker.tainting.qual.LocalInference.recursive' is never used">s1 =</warning> s
<warning descr="[UNUSED_VALUE] The value 's' assigned to 'var s1: String defined in LocalInference.recursive' is never used">s1 =</warning> s
sink(<warning descr="Unsafe string is used as safe parameter">s</warning>)
}
@@ -47,7 +48,7 @@ class LocalInference {
sink(<warning descr="Unknown string is used as safe parameter">s</warning>)
}
fun foo(): String {
open fun foo(): String {
return ""
}

View File

@@ -0,0 +1,31 @@
@file:Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER")
import org.checkerframework.checker.tainting.qual.Untainted
class LocalCheck {
fun test(clean: @Untainted MutableList<String?>, cleanList2: @Untainted MutableList<String>, t: @Untainted String?, dirty: String?) {
sink(t)
sink(clean[0])
val list1: List<String?> = clean
update(list1) //not highlighted in current realisation, might be changed
clean.add(dirty) //not highlighted in current realisation, might be changed
sink(<warning descr="Unknown string is used as safe parameter">list1[0]</warning>) //warn
sink(clean[0]) //warn
val list3: List<String> = cleanList2
sink(list3[0])
sink(<warning descr="Unknown string is used as safe parameter">dirty</warning>) //warn
var clean2 = t + dirty
sink(<warning descr="Unknown string is used as safe parameter">clean2</warning>) // warn
var newT: String?
newT = t
sink(newT)
val runnable = Runnable {
sink(<warning descr="Unknown string is used as safe parameter">newT</warning>) //warn
}
val runnable2: () -> Unit = { sink(<warning descr="Unknown string is used as safe parameter">newT</warning>) } //warn
newT = dirty
}
private fun update(list: List<String?>?) {}
fun sink(clean: @Untainted String?) {}
}

View File

@@ -0,0 +1,62 @@
import org.checkerframework.checker.tainting.qual.Untainted
open class MethodPropagation {
fun test1(dirty: String?, clean: @Untainted String?) {
sink(<warning descr="Unknown string is used as safe parameter">dirty</warning>) //warn
sink(clean)
}
private fun recursive(dirty: String?, clean: @Untainted String?): String {
if (clean === "") {
val a = recursive(dirty, clean)
sink(<warning descr="Unknown string is used as safe parameter">a</warning>) //warn
return recursive(dirty, clean)
}
return recursive(clean, clean)
}
fun test2(dirty: String?, clean: @Untainted String?) {
sink(<warning descr="Unknown string is used as safe parameter">next(dirty)</warning>) //warn
sink(next(clean))
sink(<warning descr="Unknown string is used as safe parameter">nextPublic(dirty)</warning>) //warn
sink(<warning descr="Unknown string is used as safe parameter">nextPublic(clean)</warning>) //warn (public)
sink(alwaysClean(dirty))
sink(alwaysClean(clean))
sink(<warning descr="Unknown string is used as safe parameter">staticNext(dirty)</warning>) //warn
sink(staticNext(clean))
sink(next(next(clean)))
sink(next(next(next(next(next(next(next(next(clean)))))))))
sink(<warning descr="Unknown string is used as safe parameter">next(next(next(next(next(next(next(next(dirty))))))))</warning>) //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(<warning descr="[UNUSED_PARAMETER] Parameter 'next' is never used">next</warning>: String?): String {
return "next"
}
open fun nextPublic(next: String?): String? {
return next
}
companion object {
fun staticNext(next: String?): String? {
return next
}
fun sink(<warning descr="[UNUSED_PARAMETER] Parameter 'string' is never used">string</warning>: @Untainted String?) {}
}
}

View File

@@ -0,0 +1,11 @@
import org.checkerframework.checker.tainting.qual.Untainted
class MethodAsFieldTest {
fun test(clean: @Untainted MethodAsFields, unclean: MethodAsFields) {
sink(clean.t)
sink(<warning descr="Unknown string is used as safe parameter">unclean.t</warning>) //warn
}
fun sink(<warning descr="[UNUSED_PARAMETER] Parameter 'string' is never used">string</warning>: @Untainted String?) {}
}

View File

@@ -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 = <warning descr="Unsafe string is used as safe parameter">dirty()</warning>) {
}
private fun dirty(): @Tainted String {
return ""
}
}

View File

@@ -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() {

View File

@@ -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 = <warning descr="Unsafe string is used in a safe context">dirty</warning> //warn
var clean2: @Untainted String = ""
class SinkTestKotlin {
fun breakClean2(dirty: String) {
clean2 = <warning descr="Unknown string is returned from safe method">dirty</warning> // warn
}
companion object {
val dirty: @Tainted String = ""
var clean: @Untainted String = <warning descr="Unsafe string is used in a safe context">dirty</warning> //warn
var clean2: @Untainted String = ""
fun breakClean2(dirty: String) {
clean2 = <warning descr="Unknown string is returned from safe method">dirty</warning> // warn
}
}
fun test(string: String?) {
sink(<warning descr="Unknown string is used as safe parameter">string</warning>) //warn
}
fun returnDirty(dirty: String?): @Untainted String? {
return <warning descr="Unknown string is returned from safe method">dirty</warning> //warn
}
fun sink(clear: @Untainted String?) {
}
fun assignDirty(clear: @Untainted String?, dirty: String?) {
var clear1 = clear
var clear2: String? = clear1
clear1 = <warning descr="Unknown string is assigned to safe variable">dirty</warning> //warn
clear2 = dirty
}
var dirty: @Untainted String? = <warning descr="Unsafe string is used in a safe context">getFromStatic()</warning> //warn
private fun getFromStatic(): @Tainted String {
return ""
}
var clear: @Untainted String? = ""
fun spoil(dirty: String?) {
clear = <warning descr="Unknown string is returned from safe method">dirty</warning> //warn
}
fun testLocal(dirty: String?) {
val clean: @Untainted String? = <warning descr="Unknown string is assigned to safe variable">dirty</warning> //warn
}
fun testParameter(clean: @Untainted String = <warning descr="Unsafe string is used as safe parameter">getDirty()</warning>) { //warn
}
fun getDirty(): @Tainted String = ""
fun testLocal2(dirty: String?) {
var clean: @Untainted String? = ""
clean = <warning descr="Unknown string is assigned to safe variable">dirty</warning> //warn
}
fun println(t: String): String {
return t
}
}

View File

@@ -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 = <warning descr="Unsafe string is used in a safe context">dirty</warning> //warn
@Untainted
var clean2: String = ""
class SinkTestKotlin {
fun breakClean2(dirty: String) {
clean2 = <warning descr="Unknown string is returned from safe method">dirty</warning> // warn
}
companion object {
@Tainted
val dirty: String = ""
@Untainted
var clean: String = <warning descr="Unsafe string is used in a safe context">dirty</warning> //warn
@Untainted
var clean2: String = ""
fun breakClean2(dirty: String) {
clean2 = <warning descr="Unknown string is returned from safe method">dirty</warning> // warn
}
}
fun test(string: String?) {
sink(<warning descr="Unknown string is used as safe parameter">string</warning>) //warn
}
@Untainted
fun returnDirty(dirty: String?): String? {
return <warning descr="Unknown string is returned from safe method">dirty</warning> //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 = <warning descr="Unknown string is assigned to safe variable">dirty</warning> //warn
clear2 = dirty
}
@Untainted
var dirty: String? = <warning descr="Unsafe string is used in a safe context">getFromStatic()</warning> //warn
@Tainted
private fun getFromStatic(): String {
return ""
}
@Untainted
var clear: String? = ""
fun spoil(dirty: String?) {
clear = <warning descr="Unknown string is returned from safe method">dirty</warning> //warn
}
fun testLocal(dirty: String?) {
@Untainted val clean: String? = <warning descr="Unknown string is assigned to safe variable">dirty</warning> //warn
}
fun testParameter(@Untainted clean: String = <warning descr="Unsafe string is used as safe parameter">getDirty()</warning>) { //warn
}
@Tainted
fun getDirty(): String = ""
fun testLocal2(dirty: String?) {
@Untainted var clean: String? = ""
clean = <warning descr="Unknown string is assigned to safe variable">dirty</warning> //warn
}
fun println(t: String): String {
return t
}
}

View File

@@ -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(<warning descr="Unknown string is used as safe parameter">unclean</warning>) //warn
sink(clean)
sink(<warning descr="Unknown string is used as safe parameter">if (1 == 1) unclean else clean</warning>) //warn
sink(if (1 == 1) clean else clean)
val s = if (1 == 1) unclean else clean
sink(<warning descr="Unknown string is used as safe parameter">s</warning>) //warn
sink(<warning descr="Unknown string is used as safe parameter">when {
1 == 1 -> clean
else -> unclean
}</warning>) //warn
sink(when {
1 == 1 -> clean
else -> clean
})
val s1: String = when {
1 == 1 -> clean
else -> unclean
}
sink(<warning descr="Unknown string is used as safe parameter">s1</warning>) //warn
var sumDirty: String = clean
var sumClean = clean
while (true) {
sumDirty += unclean
sumClean += clean
break
}
sink(<warning descr="Unknown string is used as safe parameter">sumDirty</warning>) //warn
sink(sumClean)
}
fun sink(s: @Untainted String?) {}
}

View File

@@ -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 <caret>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

View File

@@ -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 <caret>s1
}
@@ -19,7 +12,6 @@ internal class CommonCases {
}
companion object{
val comObject = getSomething2()
var comObject2 = getSomething2()
private fun getSomething2(): String {

View File

@@ -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 <caret>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

View File

@@ -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 <caret>s1
}
@@ -19,7 +14,6 @@ internal class CommonCases {
}
companion object{
val comObject = getSomething2()
var comObject2 = getSomething2()
private fun getSomething2(): String {

View File

@@ -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")
}
}

View File

@@ -7,6 +7,6 @@ abstract class SourceToSinkFlowInspectionTestBase : TaintedTestBase() {
override val inspection: SourceToSinkFlowInspection = SourceToSinkFlowInspection()
override fun getProjectDescriptor(): LightProjectDescriptor {
return JAVA_8
return JAVA_19
}
}

View File

@@ -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())