Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion java-checks-test-sources/default/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.6.RELEASE</version>
<version>5.3.21</version>
Comment thread
lijun-chen-sonarsource marked this conversation as resolved.
<scope>provided</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import javax.transaction.Transactional.TxType;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.UUID;

import static javax.transaction.Transactional.TxType.REQUIRED;

Expand Down Expand Up @@ -342,3 +345,35 @@ public void methodB() {
}

}

class SpringIncompatibleTransactionalCheckSampleSupportTransactionTemplate {

private TransactionTemplate transactionTemplate;

@Transactional
public void retry(UUID id) {
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void retryNew(UUID id) {
}

public void retryBatch(UUID id) {
// Possible FNs: calls inside a TransactionTemplate callback are suppressed because a transaction is guaranteed
// to be active at the call site. However, if the template's propagation is customized (e.g. NEVER or NOT_SUPPORTED),
// self-invocations that require a transaction would still be problematic. Resolving the template's propagation
// statically requires data-flow / symbolic-execution techniques and is out of scope for this rule.
transactionTemplate.executeWithoutResult(status -> {
retry(id); // compliant - inside a TransactionTemplate callback: a transaction is already active
retryNew(id); // compliant - self-invocation reports are suppressed inside TransactionTemplate callbacks
});
transactionTemplate.execute(status -> {
retry(id);
retryNew(id);
return id.toString();
});

retry(id); // Noncompliant {{"retry's" @Transactional requirement is incompatible with the one for this method.}} [[secondary=354]]
retryNew(id); // Noncompliant {{"retryNew's" @Transactional requirement is incompatible with the one for this method.}} [[secondary=358]]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.sonar.java.model.ExpressionUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.SymbolMetadata;
import org.sonar.plugins.java.api.semantic.SymbolMetadata.AnnotationValue;
Expand Down Expand Up @@ -59,6 +60,12 @@ public class SpringIncompatibleTransactionalCheck extends IssuableSubscriptionVi
// Made name to represent no annotation
private static final String NOT_TRANSACTIONAL = "SONAR_NOT_TRANSACTIONAL";

private static final MethodMatchers TRANSACTION_TEMPLATE_EXECUTE = MethodMatchers.create()
.ofTypes("org.springframework.transaction.support.TransactionTemplate")
.names("execute", "executeWithoutResult")
.withAnyParameters()
.build();
Comment thread
gitar-bot[bot] marked this conversation as resolved.

private static final Map<String, Set<String>> INCOMPATIBLE_PROPAGATION_MAP = buildIncompatiblePropagationMap();

private static Map<String, Set<String>> buildIncompatiblePropagationMap() {
Expand Down Expand Up @@ -96,14 +103,21 @@ private void checkMethodInvocations(MethodTree method, @Nullable String callerPr
return;
}
methodBody.accept(new BaseTreeVisitor() {
private boolean insideTransactionTemplateCallback = false;

@Override
public void visitMethodInvocation(MethodInvocationTree methodInvocation) {
boolean previousState = insideTransactionTemplateCallback;
if (TRANSACTION_TEMPLATE_EXECUTE.matches(methodInvocation)) {
insideTransactionTemplateCallback = true;
}
super.visitMethodInvocation(methodInvocation);
insideTransactionTemplateCallback = previousState;
Symbol calleeMethodSymbol = methodInvocation.methodSymbol();
if (calleeMethodSymbol.isUnknown()) {
return;
}
Comment thread
gitar-bot[bot] marked this conversation as resolved.
if (methodsPropagationMap.containsKey(calleeMethodSymbol) && methodInvocationOnThisInstance(methodInvocation)) {
if (!insideTransactionTemplateCallback && methodsPropagationMap.containsKey(calleeMethodSymbol) && methodInvocationOnThisInstance(methodInvocation)) {
String calleePropagation = methodsPropagationMap.get(calleeMethodSymbol);
checkIncompatiblePropagation(methodInvocation, callerPropagation, calleeMethodSymbol, calleePropagation);
}
Expand Down
Loading