Skip to content

Commit 885ab16

Browse files
author
Jay Katariya
committedNov 9, 2024
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 8c83e46 commit 885ab16

File tree

8 files changed

+230
-0
lines changed

8 files changed

+230
-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,111 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.log4j.core.config.plugins.processor;
18+
19+
import static java.nio.charset.StandardCharsets.UTF_8;
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
import java.nio.file.Paths;
25+
import java.util.Arrays;
26+
import java.util.List;
27+
import java.util.Locale;
28+
import java.util.stream.Collectors;
29+
import javax.tools.Diagnostic;
30+
import javax.tools.DiagnosticCollector;
31+
import javax.tools.JavaCompiler;
32+
import javax.tools.JavaFileObject;
33+
import javax.tools.StandardJavaFileManager;
34+
import javax.tools.ToolProvider;
35+
import org.junit.Test;
36+
37+
public class PluginProcessorPublicSetterTest {
38+
39+
private static final String FAKE_PLUGIN_CLASS_PATH =
40+
"src/test/java/org/apache/logging/log4j/core/config/plugins/processor/" + FakePlugin.class.getSimpleName()
41+
+ ".java";
42+
43+
@Test
44+
public void warnWhenPluginBuilderAttributeLacksPublicSetter() throws Exception {
45+
// Instantiate the tooling
46+
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
47+
final DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
48+
final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.ROOT, UTF_8);
49+
50+
// Get the source files
51+
Path sourceFile = Paths.get(FAKE_PLUGIN_CLASS_PATH);
52+
53+
assertThat(Files.exists(sourceFile)).isTrue();
54+
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(sourceFile.toFile());
55+
56+
// Compile the sources with the plugin processor
57+
JavaCompiler.CompilationTask task = compiler.getTask(
58+
null,
59+
fileManager,
60+
diagnosticCollector,
61+
Arrays.asList("-proc:only", "-processor", PluginProcessor.class.getName()),
62+
null,
63+
compilationUnits);
64+
task.call();
65+
66+
// Check for warnings about missing public setter
67+
List<Diagnostic<? extends JavaFileObject>> warningDiagnostics = diagnosticCollector.getDiagnostics().stream()
68+
.filter(diagnostic -> diagnostic.getKind() == Diagnostic.Kind.WARNING)
69+
.collect(Collectors.toList());
70+
71+
assertThat(warningDiagnostics).anyMatch(warningMessage -> warningMessage
72+
.getMessage(Locale.ROOT)
73+
.contains("The field `attribute` does not have a public setter"));
74+
}
75+
76+
@Test
77+
public void IgnoreWarningWhenSuppressWarningsIsPresent() throws Exception {
78+
// Instantiate the tooling
79+
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
80+
final DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
81+
final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.ROOT, UTF_8);
82+
83+
// Get the source files
84+
Path sourceFile = Paths.get(FAKE_PLUGIN_CLASS_PATH);
85+
System.out.println(FAKE_PLUGIN_CLASS_PATH);
86+
assertThat(Files.exists(sourceFile)).isTrue();
87+
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(sourceFile.toFile());
88+
89+
// Compile the sources with the plugin processor
90+
JavaCompiler.CompilationTask task = compiler.getTask(
91+
null,
92+
fileManager,
93+
diagnosticCollector,
94+
Arrays.asList("-proc:only", "-processor", PluginProcessor.class.getName()),
95+
null,
96+
compilationUnits);
97+
task.call();
98+
99+
// Check for warnings about missing public setter
100+
List<Diagnostic<? extends JavaFileObject>> warningDiagnostics = diagnosticCollector.getDiagnostics().stream()
101+
.filter(diagnostic -> diagnostic.getKind() == Diagnostic.Kind.WARNING)
102+
.collect(Collectors.toList());
103+
104+
assertThat(warningDiagnostics)
105+
.allMatch(
106+
warningMessage -> !warningMessage
107+
.getMessage(Locale.ROOT)
108+
.contains(
109+
"The field `attributeWithoutPublicSetterButWithSuppressAnnotation` does not have a public setter"));
110+
}
111+
}

‎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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="https://logging.apache.org/xml/ns"
4+
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
5+
type="fixed">
6+
<issue id="2769" link="https://github.com/apache/logging-log4j2/issues/2769"/>
7+
<description format="asciidoc">Adding a compilation warning for Plugin Builder Attributes that do not have a public setter.</description>
8+
</entry>

0 commit comments

Comments
 (0)