Skip to content

Commit 41103bb

Browse files
author
Jay Katariya
committed
Fix(apache#2769): modify the annotation processor in 2.x to fail if a plugin builder attribute does not have a public setter.
1 parent 689140d commit 41103bb

File tree

7 files changed

+202
-0
lines changed

7 files changed

+202
-0
lines changed

log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakePlugin.java

+4
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde
6262
@PluginBuilderAttribute
6363
private int attribute;
6464

65+
@PluginBuilderAttribute
66+
@SuppressWarnings("log4j.public.setter")
67+
private int attributeWithoutPublicSetterButWithSuppressAnnotation;
68+
6569
@PluginElement("layout")
6670
private Layout<? extends Serializable> layout;
6771

log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class GraalVmProcessorTest {
6565
"fields",
6666
asList(
6767
asMap("name", "attribute"),
68+
asMap("name", "attributeWithoutPublicSetterButWithSuppressAnnotation"),
6869
asMap("name", "config"),
6970
asMap("name", "layout"),
7071
asMap("name", "loggerContext"),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.apache.logging.log4j.core.config.plugins.processor;
2+
3+
4+
import org.junit.Test;
5+
6+
import javax.tools.Diagnostic;
7+
import javax.tools.DiagnosticCollector;
8+
import javax.tools.JavaCompiler;
9+
import javax.tools.JavaFileObject;
10+
import javax.tools.StandardJavaFileManager;
11+
import javax.tools.ToolProvider;
12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
import java.nio.file.Paths;
15+
import java.util.Arrays;
16+
import java.util.List;
17+
import java.util.Locale;
18+
import java.util.stream.Collectors;
19+
20+
import static java.nio.charset.StandardCharsets.UTF_8;
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
23+
public class PluginProcessorPublicSetterTest {
24+
25+
private static final String FAKE_PLUGIN_CLASS_PATH =
26+
"src/test/java/org/apache/logging/log4j/core/config/plugins/processor/" + FakePlugin.class.getSimpleName() + ".java";
27+
28+
@Test
29+
public void warnWhenPluginBuilderAttributeLacksPublicSetter() throws Exception {
30+
// Instantiate the tooling
31+
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
32+
final DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
33+
final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.ROOT, UTF_8);
34+
35+
36+
// Get the source files
37+
Path sourceFile = Paths.get(FAKE_PLUGIN_CLASS_PATH);
38+
39+
assertThat(Files.exists(sourceFile)).isTrue();
40+
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(sourceFile.toFile());
41+
42+
// Compile the sources with the plugin processor
43+
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector, Arrays.asList(
44+
"-proc:only",
45+
"-processor",
46+
PluginProcessor.class.getName()),
47+
null,
48+
compilationUnits);
49+
task.call();
50+
51+
// Check for warnings about missing public setter
52+
List<Diagnostic<? extends JavaFileObject>> warningDiagnostics = diagnosticCollector.getDiagnostics().stream()
53+
.filter(diagnostic -> diagnostic.getKind() == Diagnostic.Kind.WARNING)
54+
.collect(Collectors.toList());
55+
56+
assertThat(warningDiagnostics).anyMatch(warningMessage -> warningMessage.getMessage(Locale.ROOT).contains("The field `attribute` does not have a public setter"));
57+
}
58+
59+
@Test
60+
public void IgnoreWarningWhenSuppressWarningsIsPresent() throws Exception {
61+
// Instantiate the tooling
62+
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
63+
final DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
64+
final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.ROOT, UTF_8);
65+
66+
67+
// Get the source files
68+
Path sourceFile = Paths.get(FAKE_PLUGIN_CLASS_PATH);
69+
System.out.println(FAKE_PLUGIN_CLASS_PATH);
70+
assertThat(Files.exists(sourceFile)).isTrue();
71+
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(sourceFile.toFile());
72+
73+
// Compile the sources with the plugin processor
74+
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector, Arrays.asList(
75+
"-proc:only",
76+
"-processor",
77+
PluginProcessor.class.getName()),
78+
null,
79+
compilationUnits);
80+
task.call();
81+
82+
// Check for warnings about missing public setter
83+
List<Diagnostic<? extends JavaFileObject>> warningDiagnostics = diagnosticCollector.getDiagnostics().stream()
84+
.filter(diagnostic -> diagnostic.getKind() == Diagnostic.Kind.WARNING)
85+
.collect(Collectors.toList());
86+
87+
assertThat(warningDiagnostics).allMatch(warningMessage -> !warningMessage.getMessage(Locale.ROOT).contains("The field `attributeWithoutPublicSetterButWithSuppressAnnotation` does not have a public setter"));
88+
}
89+
90+
91+
}

log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public static class Builder<B extends Builder<B>> extends AbstractDatabaseAppend
6060
private ConnectionSource connectionSource;
6161

6262
@PluginBuilderAttribute
63+
@SuppressWarnings("log4j.public.setter")
6364
private boolean immediateFail;
6465

6566
@PluginBuilderAttribute
@@ -80,6 +81,7 @@ public static class Builder<B extends Builder<B>> extends AbstractDatabaseAppend
8081

8182
// TODO Consider moving up to AbstractDatabaseAppender.Builder.
8283
@PluginBuilderAttribute
84+
@SuppressWarnings("log4j.public.setter")
8385
private long reconnectIntervalMillis = DEFAULT_RECONNECT_INTERVAL_MILLIS;
8486

8587
@Override

log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java

+100
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
import java.io.PrintWriter;
2626
import java.io.StringWriter;
2727
import java.util.ArrayList;
28+
import java.util.Arrays;
2829
import java.util.Collection;
2930
import java.util.Collections;
3031
import java.util.List;
32+
import java.util.Locale;
3133
import java.util.Map;
3234
import java.util.Objects;
3335
import java.util.Set;
@@ -39,14 +41,19 @@
3941
import javax.lang.model.SourceVersion;
4042
import javax.lang.model.element.Element;
4143
import javax.lang.model.element.ElementVisitor;
44+
import javax.lang.model.element.ExecutableElement;
45+
import javax.lang.model.element.Modifier;
4246
import javax.lang.model.element.TypeElement;
47+
import javax.lang.model.element.VariableElement;
4348
import javax.lang.model.util.Elements;
4449
import javax.lang.model.util.SimpleElementVisitor7;
50+
import javax.lang.model.util.Types;
4551
import javax.tools.Diagnostic;
4652
import javax.tools.FileObject;
4753
import javax.tools.StandardLocation;
4854
import org.apache.logging.log4j.core.config.plugins.Plugin;
4955
import org.apache.logging.log4j.core.config.plugins.PluginAliases;
56+
import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
5057
import org.apache.logging.log4j.util.Strings;
5158

5259
/**
@@ -83,6 +90,12 @@ public boolean process(final Set<? extends TypeElement> annotations, final Round
8390
final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Plugin.class);
8491
collectPlugins(elements);
8592
processedElements.addAll(elements);
93+
94+
// process plugin builder Attributes
95+
final Set<? extends Element> pluginAttributeBuilderElements =
96+
roundEnv.getElementsAnnotatedWith(PluginBuilderAttribute.class);
97+
processBuilderAttributes(pluginAttributeBuilderElements);
98+
processedElements.addAll(pluginAttributeBuilderElements);
8699
}
87100
// Write the cache file
88101
if (roundEnv.processingOver() && !processedElements.isEmpty()) {
@@ -107,6 +120,93 @@ public boolean process(final Set<? extends TypeElement> annotations, final Round
107120
return false;
108121
}
109122

123+
private void processBuilderAttributes(final Iterable<? extends Element> elements) {
124+
for (final Element element : elements) {
125+
if (element instanceof VariableElement) {
126+
processBuilderAttributes((VariableElement) element);
127+
}
128+
}
129+
}
130+
131+
private void processBuilderAttributes(final VariableElement element) {
132+
final String fieldName = element.getSimpleName().toString(); // Getting the name of the field
133+
SuppressWarnings suppress = element.getAnnotation(SuppressWarnings.class);
134+
if (suppress != null && Arrays.asList(suppress.value()).contains("log4j.public.setter")) {
135+
// Suppress the warning due to annotation
136+
return;
137+
}
138+
final Element enclosingElement = element.getEnclosingElement();
139+
// `element is a field
140+
if (enclosingElement instanceof TypeElement) {
141+
final TypeElement typeElement = (TypeElement) enclosingElement;
142+
// Check the siblings of the field
143+
for (final Element enclosedElement : typeElement.getEnclosedElements()) {
144+
// `enclosedElement is a method or constructor
145+
if (enclosedElement instanceof ExecutableElement) {
146+
final ExecutableElement methodElement = (ExecutableElement) enclosedElement;
147+
final String methodName = methodElement.getSimpleName().toString();
148+
149+
if ((methodName.toLowerCase(Locale.ROOT).startsWith("set") // Typical pattern for setters
150+
|| methodName
151+
.toLowerCase(Locale.ROOT)
152+
.startsWith("with")) // Typical pattern for setters
153+
&& methodElement.getParameters().size()
154+
== 1 // It is a weird pattern to not have public setter
155+
) {
156+
157+
Types typeUtils = processingEnv.getTypeUtils();
158+
159+
boolean followsNamePattern = methodName
160+
.toLowerCase(Locale.ROOT)
161+
.equals(String.format("set%s", fieldName.toLowerCase(Locale.ROOT)))
162+
|| methodName
163+
.toLowerCase(Locale.ROOT)
164+
.equals(String.format("with%s", fieldName.toLowerCase(Locale.ROOT)));
165+
166+
if (fieldName.toLowerCase(Locale.ROOT).startsWith("is")) {
167+
followsNamePattern = methodName
168+
.toLowerCase(Locale.ROOT)
169+
.equals(String.format(
170+
"set%s",
171+
fieldName
172+
.toLowerCase(Locale.ROOT)
173+
.substring(2)))
174+
|| methodName
175+
.toLowerCase(Locale.ROOT)
176+
.equals(String.format(
177+
"with%s",
178+
fieldName
179+
.toLowerCase(Locale.ROOT)
180+
.substring(2)));
181+
}
182+
183+
// Check if method is public
184+
boolean isPublicMethod = methodElement.getModifiers().contains(Modifier.PUBLIC);
185+
186+
// Check if the return type of the method element is Assignable.
187+
// Assuming it is a builder class the type of it should be assignable to its parent
188+
boolean checkForAssignable = typeUtils.isAssignable(
189+
methodElement.getReturnType(),
190+
methodElement.getEnclosingElement().asType());
191+
192+
boolean foundPublicSetter = followsNamePattern && checkForAssignable && isPublicMethod;
193+
if (foundPublicSetter) {
194+
// Hurray we found a public setter for the field!
195+
return;
196+
}
197+
}
198+
}
199+
}
200+
// If the setter was not found generate a compiler warning.
201+
processingEnv
202+
.getMessager()
203+
.printMessage(
204+
Diagnostic.Kind.WARNING,
205+
String.format("The field `%s` does not have a public setter", fieldName),
206+
element);
207+
}
208+
}
209+
110210
private void collectPlugins(final Iterable<? extends Element> elements) {
111211
final Elements elementUtils = processingEnv.getElementUtils();
112212
final ElementVisitor<PluginEntry, Plugin> pluginVisitor = new PluginElementVisitor(elementUtils);

log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketPerformancePreferences.java

+3
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,17 @@ public static SocketPerformancePreferences newBuilder() {
4040

4141
@PluginBuilderAttribute
4242
@Required
43+
@SuppressWarnings("log4j.public.setter")
4344
private int bandwidth;
4445

4546
@PluginBuilderAttribute
4647
@Required
48+
@SuppressWarnings("log4j.public.setter")
4749
private int connectionTime;
4850

4951
@PluginBuilderAttribute
5052
@Required
53+
@SuppressWarnings("log4j.public.setter")
5154
private int latency;
5255

5356
public void apply(final Socket socket) {

log4j-jakarta-web/src/main/java/org/apache/logging/log4j/web/appender/ServletAppender.java

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public static class Builder<B extends Builder<B>> extends AbstractAppender.Build
4040
implements org.apache.logging.log4j.core.util.Builder<ServletAppender> {
4141

4242
@PluginBuilderAttribute
43+
@SuppressWarnings("log4j.public.setter")
4344
private boolean logThrowables;
4445

4546
@Override

0 commit comments

Comments
 (0)