[uast-inspections] IDEA-357019 Inspection warning 'Number of placeholders do not match number of arguments in logging call' not reported when logger not initialized inline

GitOrigin-RevId: e601f8f8c94233af84856b7be2626f71b57325eb
This commit is contained in:
Mikhail Pyltsin
2024-08-15 12:54:33 +02:00
committed by intellij-monorepo-bot
parent 8443fffa63
commit b7d3abc522
3 changed files with 230 additions and 23 deletions

View File

@@ -3,13 +3,17 @@ package com.intellij.codeInspection.logging
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.InheritanceUtil
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.util.containers.addIfNotNull
import com.siyeh.ig.callMatcher.CallMapper
import com.siyeh.ig.callMatcher.CallMatcher
import com.siyeh.ig.format.FormatDecode
import com.siyeh.ig.psiutils.TypeUtils
import org.jetbrains.uast.*
import org.jetbrains.uast.UastBinaryOperator.Companion.ASSIGN
import org.jetbrains.uast.visitor.AbstractUastVisitor
const val MAX_BUILDER_LENGTH = 20
@@ -32,28 +36,74 @@ private val SLF4J_HOLDER = object : LoggerTypeSearcher {
internal val LOG4J_LOG_BUILDER_HOLDER = object : LoggerTypeSearcher {
override fun findType(expression: UCallExpression, context: LoggerContext): PlaceholderLoggerType? {
var initializers: List<UExpression?>?
var qualifierExpression = getImmediateLoggerQualifier(expression)
if (qualifierExpression is UReferenceExpression) {
val target: UVariable = qualifierExpression.resolveToUElement() as? UVariable ?: return null
if (!target.isFinal) {
return PlaceholderLoggerType.LOG4J_EQUAL_PLACEHOLDERS //formatted builder is really rare
}
qualifierExpression = target.uastInitializer?.skipParenthesizedExprDown()
}
if (qualifierExpression is UQualifiedReferenceExpression) {
qualifierExpression = qualifierExpression.selector
}
if (qualifierExpression is UCallExpression) {
return when (LOG4J_HOLDER.findType(qualifierExpression, context)) {
PlaceholderLoggerType.LOG4J_FORMATTED_STYLE -> PlaceholderLoggerType.LOG4J_FORMATTED_STYLE
PlaceholderLoggerType.LOG4J_OLD_STYLE -> PlaceholderLoggerType.LOG4J_EQUAL_PLACEHOLDERS
else -> null
qualifierExpression = target.uastInitializer
if (qualifierExpression == null) {
initializers = tryToFindInitializers(target, expression)
}
else {
initializers = listOf(qualifierExpression)
}
}
return PlaceholderLoggerType.LOG4J_EQUAL_PLACEHOLDERS
else {
initializers = listOf(qualifierExpression)
}
if (initializers == null) return null
return initializers.map {
it?.skipParenthesizedExprDown()
}.map {
if (it is UQualifiedReferenceExpression) {
it.selector
}
else {
it
}
}.map {
if (it is UCallExpression) {
when (LOG4J_HOLDER.findType(it, context)) {
PlaceholderLoggerType.LOG4J_FORMATTED_STYLE -> PlaceholderLoggerType.LOG4J_FORMATTED_STYLE
PlaceholderLoggerType.LOG4J_OLD_STYLE -> PlaceholderLoggerType.LOG4J_EQUAL_PLACEHOLDERS
else -> null
}
}
else {
PlaceholderLoggerType.LOG4J_EQUAL_PLACEHOLDERS
}
}.distinct().singleOrNull()
}
}
private fun tryToFindInitializers(originalVariable: UVariable, expression: UCallExpression): List<UExpression>? {
val variableText = originalVariable.name
if (variableText == null) return null
if (originalVariable.getContainingUFile()?.sourcePsi != expression.getContainingUFile()?.sourcePsi) return null
val element = originalVariable.sourcePsi ?: return null
return CachedValuesManager.getManager(element.project).getCachedValue(element, CachedValueProvider {
val variable = element.toUElementOfType<UVariable>()
val initializers = mutableListOf<UExpression>()
val uastParent = variable?.uastParent
uastParent?.accept(object : AbstractUastVisitor() {
override fun visitBinaryExpression(node: UBinaryExpression): Boolean {
if (node.operator == ASSIGN) {
val leftOperand = node.leftOperand
if (leftOperand is UReferenceExpression && leftOperand.sourcePsi?.text == variableText && leftOperand.resolveToUElement() == variable) {
initializers.add(node.rightOperand)
}
}
return super.visitBinaryExpression(node)
}
})
return@CachedValueProvider CachedValueProvider.Result.create(initializers,
PsiModificationTracker.MODIFICATION_COUNT)
})
}
internal val SLF4J_BUILDER_HOLDER = object : LoggerTypeSearcher {
override fun findType(expression: UCallExpression, context: LoggerContext): PlaceholderLoggerType {
if (context.log4jAsImplementationForSlf4j) {
@@ -66,7 +116,7 @@ internal val SLF4J_BUILDER_HOLDER = object : LoggerTypeSearcher {
private val LOG4J_HOLDER = object : LoggerTypeSearcher {
override fun findType(expression: UCallExpression, context: LoggerContext): PlaceholderLoggerType? {
val qualifierExpression = getImmediateLoggerQualifier(expression)
var initializer: UExpression? = null
var initializers: List<UExpression>? = null
if (qualifierExpression is UReferenceExpression) {
var resolvedToUElement = qualifierExpression.resolveToUElement()
if (resolvedToUElement is UMethod) {
@@ -82,20 +132,31 @@ private val LOG4J_HOLDER = object : LoggerTypeSearcher {
if (!target.isFinal) {
return null
}
initializer = target.uastInitializer
if (initializer == null) return null
val uastInitializer = target.uastInitializer
initializers = if (uastInitializer != null) listOf(uastInitializer) else null
if (initializers == null) {
initializers = tryToFindInitializers(target, expression)
}
}
else if (qualifierExpression is UCallExpression) {
initializer = qualifierExpression
initializers = listOf(qualifierExpression)
}
initializer = initializer?.skipParenthesizedExprDown()
if (initializer is UQualifiedReferenceExpression) {
initializer = initializer.selector
}
return if (initializer is UCallExpression && LoggingUtil.FORMATTED_LOG4J.uCallMatches(initializer)) {
PlaceholderLoggerType.LOG4J_FORMATTED_STYLE
}
else PlaceholderLoggerType.LOG4J_OLD_STYLE
if (initializers == null || initializers.isEmpty()) return null
return initializers.map {
it.skipParenthesizedExprDown()
}.map {
if (it is UQualifiedReferenceExpression) {
it.selector
}
else {
it
}
}.map {
if (it is UCallExpression && LoggingUtil.FORMATTED_LOG4J.uCallMatches(it)) {
PlaceholderLoggerType.LOG4J_FORMATTED_STYLE
}
else PlaceholderLoggerType.LOG4J_OLD_STYLE
}.distinct().singleOrNull()
}
}

View File

@@ -614,6 +614,91 @@ class JavaLoggingPlaceholderCountMatchesArgumentCountInspectionTest : LoggingPla
""".trimIndent())
}
fun `test lazy init`() {
myFixture.testHighlighting(JvmLanguage.JAVA, """
import org.apache.logging.log4j.LogBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
class LazyInitializer {
static class StaticInitializer {
private static final Logger log;
static {
log = LogManager.getLogger();
}
public StaticInitializer() {
log.info(<warning descr="Fewer arguments provided (0) than placeholders specified (1)">"{}"</warning>);
}
}
static class StaticInitializerBuilder {
private static final LogBuilder log;
static {
log = LogManager.getLogger().atDebug();
}
public StaticInitializerBuilder() {
log.log(<warning descr="Fewer arguments provided (0) than placeholders specified (1)">"{}"</warning>);
}
}
static class StaticInitializerBuilder2 {
private static final LogBuilder log;
static {
if (1 == 1) {
log = LogManager.getLogger().atDebug();
} else {
log = LogManager.getFormatterLogger().atDebug();
}
}
public StaticInitializerBuilder2() {
log.log("{}");
}
}
static class ConstructorInitializer {
private final Logger log;
public ConstructorInitializer() {
log = LogManager.getLogger();
}
public ConstructorInitializer(int i) {
log = LogManager.getLogger();
}
public void test() {
log.info(<warning descr="Fewer arguments provided (0) than placeholders specified (1)">"{}"</warning>);
}
}
static class ConstructorInitializer2 {
private final Logger log;
public ConstructorInitializer2() {
log = LogManager.getFormatterLogger();
}
public ConstructorInitializer2(int i) {
log = LogManager.getLogger();
}
public void test() {
log.info("{}");
}
}
}
""".trimIndent())
}
fun `test slf4j structured logging`() {
inspection.slf4jToLog4J2Type = LoggingPlaceholderCountMatchesArgumentCountInspection.Slf4jToLog4J2Type.NO
myFixture.testHighlighting(JvmLanguage.JAVA, """

View File

@@ -203,4 +203,65 @@ abstract class KotlinLoggingPlaceholderCountMatchesArgumentCountInspectionPlaceh
}
""".trimIndent())
}
fun `test lazy init`() {
myFixture.testHighlighting(JvmLanguage.KOTLIN, """
import org.apache.logging.log4j.LogBuilder
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
class LazyInitializer {
internal class StaticInitializerBuilder2 {
init {
log.log("{}")
}
companion object {
private val log: LogBuilder
init {
if (1 == 1) {
log = LogManager.getLogger().atDebug()
} else {
log = LogManager.getFormatterLogger().atDebug()
}
}
}
}
internal class ConstructorInitializer {
private val log: Logger
constructor() {
log = LogManager.getLogger()
}
constructor(<warning descr="[UNUSED_PARAMETER] Parameter 'i' is never used">i</warning>: Int) {
log = LogManager.getLogger()
}
fun test() {
log.info(<warning descr="Fewer arguments provided (0) than placeholders specified (1)">"{}"</warning>)
}
}
internal class ConstructorInitializer2 {
private val log: Logger
constructor() {
log = LogManager.getFormatterLogger()
}
constructor(<warning descr="[UNUSED_PARAMETER] Parameter 'i' is never used">i</warning>: Int) {
log = LogManager.getLogger()
}
fun test() {
log.info("{}")
}
}
}
""".trimIndent())
}
}