You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by vy...@apache.org on 2024/09/17 19:13:15 UTC
(logging-log4j2) branch 2.x updated: Google OSS-Fuzz integration (#2949)
This is an automated email from the ASF dual-hosted git repository.
vy pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/2.x by this push:
new caa0f1dee5 Google OSS-Fuzz integration (#2949)
caa0f1dee5 is described below
commit caa0f1dee5fb337fb09f56b5b670ee899eb95766
Author: Volkan Yazıcı <vo...@yazi.ci>
AuthorDate: Tue Sep 17 21:13:10 2024 +0200
Google OSS-Fuzz integration (#2949)
Co-authored-by: Piotr P. Karwasz <pi...@karwasz.org>
---
.mvn/wrapper/maven-wrapper.properties | 4 +-
FUZZING.adoc | 106 +++++
log4j-core-fuzz-test/pom.xml | 51 +++
.../log4j/core/fuzz/PatternLayoutFuzzer.java | 43 ++
log4j-core-its/pom.xml | 10 +-
log4j-fuzz-test/.log4j-plugin-processing-activator | 1 +
log4j-fuzz-test/pom.xml | 77 ++++
.../logging/log4j/fuzz/EncodingAppender.java | 62 +++
.../org/apache/logging/log4j/fuzz/FuzzingUtil.java | 194 +++++++++
.../logging/log4j/fuzz/JsonEncodingAppender.java | 66 +++
.../org/apache/logging/log4j/fuzz/JsonUtil.java | 47 +++
.../apache/logging/log4j/fuzz/package-info.java | 20 +
.../.log4j-plugin-processing-activator | 1 +
log4j-layout-template-json-fuzz-test/pom.xml | 67 +++
.../json/fuzz/JsonTemplateLayoutCodecFuzzer.java | 82 ++++
.../json/fuzz/JsonTemplateLayoutFuzzer.java | 42 ++
log4j-layout-template-json-test/pom.xml | 3 +-
.../layout/template/json/util/JsonWriterTest.java | 96 -----
.../layout/template/json/util/JsonWriter.java | 7 +-
log4j-osgi-test/pom.xml | 8 +-
log4j-parent/pom.xml | 76 +++-
log4j-perf-test/README.adoc | 30 ++
log4j-perf-test/pom.xml | 58 +--
log4j-slf4j2-impl-fuzz-test/pom.xml | 56 +++
.../logging/slf4j/fuzz/Slf4jLoggerFacade.java | 65 +++
...jToLog4jBridgeWithJsonTemplateLayoutFuzzer.java | 38 ++
.../Slf4jToLog4jBridgeWithPatternLayoutFuzzer.java | 38 ++
...4jToLog4jBridgeWithJsonTemplateLayoutFuzzer.xml | 36 ++
.../Slf4jToLog4jBridgeWithPatternLayoutFuzzer.xml | 36 ++
mvnw | 447 +++++++++------------
mvnw.cmd | 303 ++++++--------
oss-fuzz-build.sh | 217 ++++++++++
pom.xml | 10 +-
src/changelog/.2.x.x/2949_add_fuzzing.xml | 8 +
.../.2.x.x/fix_JsonWriter_surrogate_handling.xml | 7 +
.../antora/modules/ROOT/pages/development.adoc | 7 +-
36 files changed, 1813 insertions(+), 606 deletions(-)
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index cc0f6f262b..c76af40b2f 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -15,8 +15,6 @@
# specific language governing permissions and limitations
# under the License.
distributionSha256Sum=8351955a9acf2f83c136c4eee0f6db894ab6265fdbe0a94b32a380307dbaa3e1
-distributionType=script
+distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip
-wrapperSha256Sum=3d8f20ce6103913be8b52aef6d994e0c54705fb527324ceb9b835b338739c7a8
-wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
wrapperVersion=3.3.2
diff --git a/FUZZING.adoc b/FUZZING.adoc
new file mode 100644
index 0000000000..838a89419a
--- /dev/null
+++ b/FUZZING.adoc
@@ -0,0 +1,106 @@
+////
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+////
+
+Log4j contains fuzz tests implemented using https://github.com/CodeIntelligenceTesting/jazzer[Jazzer]footnote:[
+We are aware that https://github.com/google/oss-fuzz/discussions/12195[Jazzer is discontinued].
+Yet it is still the only mature fuzzing framework in Java and https://google.github.io/oss-fuzz/getting-started/new-project-guide/jvm-lang/#jazzer[the recommended library by OSS-Fuzz].].
+These tests are located in `-fuzz-test` prefixed modules; `log4j-core-fuzz-test`, `log4j-layout-template-json-fuzz-test`, etc.
+
+[#oss-fuzz]
+== Google OSS-Fuzz
+
+https://github.com/google/oss-fuzz[OSS-Fuzz] is a Google service that continuously runs fuzz tests of critical F/OSS projects on a beefy cluster and reports its findings (bugs, vulnerabilities, etc.) privately to project maintainers.
+Log4j provides OSS-Fuzz integration with following helpers:
+
+- https://github.com/google/oss-fuzz/tree/master/projects/log4j2/Dockerfile[Dockerfile] to create a container image for running tests
+- link:oss-fuzz-build.sh[`oss-fuzz-build.sh`] to generate fuzz test runner scripts along with all necessary dependencies
+
+[#faq]
+== F.A.Q.
+
+Below we will try to answer some frequently asked questions.
+
+[#running]
+=== How can I run fuzz tests locally?
+
+. Clone the OSS-Fuzz repository:
++
+[source,bash]
+----
+git clone --depth 1 https://github.com/google/oss-fuzz google-oss-fuzz && cd $_
+----
+
+. Build the container image:
++
+[source,bash]
+----
+python infra/helper.py build_image log4j2
+----
+
+. Run the container image to build the Log4j project and generate runner scripts along with dependencies:
++
+[source,bash]
+----
+python infra/helper.py build_fuzzers \
+ --sanitizer address --engine libfuzzer --architecture x86_64 \
+ log4j2
+----
+
+. List generated runner scripts:
++
+[source,bash]
+----
+ls -al build/out/log4j2
+----
+
+. Check one of the generated runner scripts:
++
+[source,bash]
+----
+python infra/helper.py check_build \
+ --sanitizer address --engine libfuzzer --architecture x86_64 \
+ log4j2 log4j-core-fuzz-test-PatternLayoutFuzzer
+----
+
+. Execute one of the generated runner scripts:
++
+[source,bash]
+----
+python infra/helper.py run_fuzzer \
+ --sanitizer address --engine libfuzzer --architecture x86_64 \
+ log4j2 log4j-core-fuzz-test-PatternLayoutFuzzer
+----
+
+[#view]
+=== How can I view fuzzing failures detected by OSS-Fuzz?
+
+The system running fuzzers registered to OSS-Fuzz is called *ClusterFuzz*, which provides https://oss-fuzz.com/[a web interface] for maintainers to monitor the fuzzing results.
+Tests outputs and <<#reproduce,reproduction>> inputs for failed tests are stored in https://console.cloud.google.com/storage/browser/log4j2-logs.clusterfuzz-external.appspot.com[a Google Cloud Storage bucket].
+Access to both the web interface and the bucket is restricted, and only allowed to https://github.com/google/oss-fuzz/blob/master/projects/log4j2/project.yaml[those configured for the project].
+
+[#reproduce]
+=== How can I reproduce fuzzing failures detected by OSS-Fuzz?
+
+Download the associated `.testcase` file from https://console.cloud.google.com/storage/browser/log4j2-logs.clusterfuzz-external.appspot.com[the Google Cloud Storage bucket], and run the following command:
+
+[source,bash]
+----
+python infra/helper.py reproduce \
+ log4j2 <FUZZ-TARGET-NAME> <TESTCASE-FILE-PATH>
+----
+
+Refer to https://google.github.io/oss-fuzz/advanced-topics/reproducing/[the related OSS-Fuzz documentation] for details.
diff --git a/log4j-core-fuzz-test/pom.xml b/log4j-core-fuzz-test/pom.xml
new file mode 100644
index 0000000000..85bb659a78
--- /dev/null
+++ b/log4j-core-fuzz-test/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to you under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>${revision}</version>
+ <relativePath>../log4j-parent</relativePath>
+ </parent>
+
+ <artifactId>log4j-core-fuzz-test</artifactId>
+
+ <name>Apache Log4j Core fuzz tests</name>
+
+ <properties>
+ <bnd.baseline.skip>true</bnd.baseline.skip>
+ <log4j.docgen.skip>true</log4j.docgen.skip>
+ <maven.deploy.skip>true</maven.deploy.skip>
+ <maven.install.skip>true</maven.install.skip>
+ <maven.test.skip>true</maven.test.skip>
+ <sign.skip>true</sign.skip>
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-fuzz-test</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/log4j-core-fuzz-test/src/main/java/org/apache/logging/log4j/core/fuzz/PatternLayoutFuzzer.java b/log4j-core-fuzz-test/src/main/java/org/apache/logging/log4j/core/fuzz/PatternLayoutFuzzer.java
new file mode 100644
index 0000000000..a241991126
--- /dev/null
+++ b/log4j-core-fuzz-test/src/main/java/org/apache/logging/log4j/core/fuzz/PatternLayoutFuzzer.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.fuzz;
+
+import static org.apache.logging.log4j.fuzz.FuzzingUtil.createLoggerContext;
+import static org.apache.logging.log4j.fuzz.FuzzingUtil.fuzzLogger;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.fuzz.EncodingAppender;
+import org.apache.logging.log4j.fuzz.FuzzingUtil.Log4jLoggerFacade;
+import org.apache.logging.log4j.fuzz.FuzzingUtil.LoggerFacade;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+public final class PatternLayoutFuzzer {
+
+ public static void fuzzerTestOneInput(final FuzzedDataProvider dataProvider) {
+ final String loggerContextName = PatternLayoutFuzzer.class.getSimpleName() + "LoggerContext";
+ try (final LoggerContext loggerContext =
+ createLoggerContext(loggerContextName, EncodingAppender.PLUGIN_NAME, configBuilder -> configBuilder
+ .newLayout("PatternLayout")
+ // Enforce using a single message-based converter, i.e., `MessagePatternConverter`
+ .addAttribute("pattern", "%m"))) {
+ final ExtendedLogger logger = loggerContext.getLogger(PatternLayoutFuzzer.class);
+ final LoggerFacade loggerFacade = new Log4jLoggerFacade(logger);
+ fuzzLogger(loggerFacade, dataProvider);
+ }
+ }
+}
diff --git a/log4j-core-its/pom.xml b/log4j-core-its/pom.xml
index fe1efb53a3..49f770b883 100644
--- a/log4j-core-its/pom.xml
+++ b/log4j-core-its/pom.xml
@@ -16,19 +16,24 @@
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
<modelVersion>4.0.0</modelVersion>
+
<parent>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j</artifactId>
<version>${revision}</version>
<relativePath>../log4j-parent</relativePath>
</parent>
+
<artifactId>log4j-core-its</artifactId>
- <packaging>jar</packaging>
+
<name>Apache Log4j Core Integration Tests</name>
- <description>Integration Tests for the Apache Log4j Implementation</description>
+
<properties>
+
<bnd.baseline.skip>true</bnd.baseline.skip>
+ <log4j.docgen.skip>true</log4j.docgen.skip>
<maven.deploy.skip>true</maven.deploy.skip>
<maven.install.skip>true</maven.install.skip>
<sign.skip>true</sign.skip>
@@ -36,6 +41,7 @@
<!-- Dependency versions -->
<slf4j2.version>2.0.16</slf4j2.version>
+
</properties>
<dependencyManagement>
diff --git a/log4j-fuzz-test/.log4j-plugin-processing-activator b/log4j-fuzz-test/.log4j-plugin-processing-activator
new file mode 100644
index 0000000000..ba133f3696
--- /dev/null
+++ b/log4j-fuzz-test/.log4j-plugin-processing-activator
@@ -0,0 +1 @@
+This file is here to activate the `plugin-processing` Maven profile.
diff --git a/log4j-fuzz-test/pom.xml b/log4j-fuzz-test/pom.xml
new file mode 100644
index 0000000000..ba7cca9ce0
--- /dev/null
+++ b/log4j-fuzz-test/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to you under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>${revision}</version>
+ <relativePath>../log4j-parent</relativePath>
+ </parent>
+
+ <artifactId>log4j-fuzz-test</artifactId>
+
+ <name>Apache Log4j fuzz tests</name>
+
+ <properties>
+
+ <log4j.docgen.skip>true</log4j.docgen.skip>
+ <bnd.baseline.skip>true</bnd.baseline.skip>
+ <maven.deploy.skip>true</maven.deploy.skip>
+ <maven.test.skip>true</maven.test.skip>
+ <sign.skip>true</sign.skip>
+
+ <!-- dependency versions -->
+ <json.version>20240303</json.version>
+
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jspecify</groupId>
+ <artifactId>jspecify</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.code-intelligence</groupId>
+ <artifactId>jazzer</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ <version>${json.version}</version>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/EncodingAppender.java b/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/EncodingAppender.java
new file mode 100644
index 0000000000..9a86d96bb2
--- /dev/null
+++ b/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/EncodingAppender.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.fuzz;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.Serializable;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * Appender encoding incoming log events using the provided layout.
+ * It is intended for appender-agnostic fuzzing.
+ */
+@Plugin(name = EncodingAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
+public final class EncodingAppender extends AbstractAppender {
+
+ public static final String PLUGIN_NAME = "EncodingAppender";
+
+ private EncodingAppender(final String name, final Layout<? extends Serializable> layout) {
+ super(name, null, layout, true, null);
+ // Guard `PLUGIN_NAME` against copy-paste mistakes
+ assertThat(PLUGIN_NAME).isEqualTo(getClass().getSimpleName());
+ }
+
+ @PluginFactory
+ public static EncodingAppender createAppender(
+ final @PluginAttribute("name") String name, final @PluginElement("layout") Layout<?> layout) {
+ return new EncodingAppender(name, layout);
+ }
+
+ @Override
+ public void append(final LogEvent event) {
+ try {
+ getLayout().toByteArray(event);
+ } catch (final Exception ignored) {
+ // We are inspecting unexpected access.
+ // Hence, event encoding failures are not of interest.
+ }
+ }
+}
diff --git a/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/FuzzingUtil.java b/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/FuzzingUtil.java
new file mode 100644
index 0000000000..a529e0e7ac
--- /dev/null
+++ b/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/FuzzingUtil.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.fuzz;
+
+import static java.util.Objects.requireNonNull;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
+import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
+import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
+import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
+import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
+import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+import org.apache.logging.log4j.util.Strings;
+import org.jspecify.annotations.Nullable;
+
+public final class FuzzingUtil {
+
+ private static final int MAX_STRING_LENGTH = 30;
+
+ public static LoggerContext createLoggerContext(
+ final String loggerContextName,
+ final String appenderPluginName,
+ final Function<ConfigurationBuilder<?>, LayoutComponentBuilder> layoutSupplier) {
+
+ // Create the configuration builder
+ final ConfigurationBuilder<BuiltConfiguration> configBuilder =
+ ConfigurationBuilderFactory.newConfigurationBuilder();
+
+ // Create the appender configuration
+ final String appenderName = "FUZZING_APPENDER";
+ final LayoutComponentBuilder layoutComponentBuilder = layoutSupplier.apply(configBuilder);
+ final AppenderComponentBuilder appenderComponentBuilder =
+ configBuilder.newAppender(appenderName, appenderPluginName).add(layoutComponentBuilder);
+
+ // Create the root logger configuration
+ final RootLoggerComponentBuilder loggerComponentBuilder =
+ configBuilder.newRootLogger(Level.ALL).add(configBuilder.newAppenderRef(appenderName));
+
+ // Create the conclusive configuration (uninitialized!)
+ final Configuration config = configBuilder
+ .setStatusLevel(Level.OFF)
+ .add(appenderComponentBuilder)
+ .add(loggerComponentBuilder)
+ .build(false);
+
+ // Create the logger context
+ final LoggerContext loggerContext = new LoggerContext(loggerContextName);
+ loggerContext.start(config);
+ return loggerContext;
+ }
+
+ /**
+ * Contract modelling an API-agnostic logger.
+ * This allows {@link #fuzzLogger(LoggerFacade, FuzzedDataProvider)} to accept all logger implementations; of Log4j API, of SLF4J, etc.
+ */
+ public interface LoggerFacade {
+
+ void log(String message);
+
+ void log(String message, Throwable throwable);
+
+ void log(String message, Object[] parameters);
+ }
+
+ public static final class Log4jLoggerFacade implements LoggerFacade {
+
+ private static final String FQCN = Log4jLoggerFacade.class.getName();
+
+ private final ExtendedLogger logger;
+
+ public Log4jLoggerFacade(final ExtendedLogger logger) {
+ this.logger = requireNonNull(logger, "logger");
+ }
+
+ @Override
+ public void log(final String message) {
+ logger.logIfEnabled(FQCN, Level.ERROR, null, message);
+ }
+
+ @Override
+ public void log(final String message, final Throwable throwable) {
+ logger.logIfEnabled(FQCN, Level.ERROR, null, message, throwable);
+ }
+
+ @Override
+ public void log(final String message, final Object[] parameters) {
+ logger.logIfEnabled(FQCN, Level.ERROR, null, message, parameters);
+ }
+ }
+
+ public static void fuzzLogger(final LoggerFacade logger, final FuzzedDataProvider dataProvider) {
+ requireNonNull(logger, "logger");
+ requireNonNull(dataProvider, "dataProvider");
+ final boolean parameterized = dataProvider.consumeBoolean();
+ if (parameterized) {
+ fuzzLoggerWithParams(logger, dataProvider);
+ } else {
+ fuzzLoggerWithoutParams(logger, dataProvider);
+ }
+ }
+
+ private static void fuzzLoggerWithoutParams(final LoggerFacade logger, final FuzzedDataProvider dataProvider) {
+ final String message = dataProvider.consumeString(MAX_STRING_LENGTH);
+ final Throwable throwable = createThrowableLogParam(dataProvider);
+ if (throwable != null) {
+ logger.log(message, throwable);
+ } else {
+ logger.log(message);
+ }
+ }
+
+ @Nullable
+ private static Throwable createThrowableLogParam(final FuzzedDataProvider dataProvider) {
+ final boolean enabled = dataProvider.consumeBoolean();
+ if (!enabled) {
+ return null;
+ }
+ final String errorMessage = dataProvider.consumeString(MAX_STRING_LENGTH);
+ return new Exception(errorMessage);
+ }
+
+ private static void fuzzLoggerWithParams(final LoggerFacade logger, final FuzzedDataProvider dataProvider) {
+ final int paramCount = dataProvider.consumeInt(1, 3);
+ final String message = Strings.repeat("{}", paramCount);
+ final Throwable throwable = createThrowableLogParam(dataProvider);
+ final Object[] params = IntStream.range(0, paramCount + (throwable != null ? 1 : 0))
+ .mapToObj(ignored -> createLogParam(dataProvider, true))
+ .toArray(Object[]::new);
+ if (throwable != null) {
+ params[paramCount] = throwable;
+ logger.log(message, params);
+ } else {
+ logger.log(message, params);
+ }
+ }
+
+ private static Object createLogParam(final FuzzedDataProvider dataProvider, final boolean nestingAllowed) {
+ final boolean nested = nestingAllowed && dataProvider.consumeBoolean();
+ final int paramTypeCode = dataProvider.consumeInt(0, nested ? 9 : 8);
+ switch (paramTypeCode) {
+ case 0:
+ return dataProvider.consumeByte();
+ case 1:
+ return dataProvider.consumeBoolean();
+ case 2:
+ return dataProvider.consumeChar();
+ case 3:
+ return dataProvider.consumeShort();
+ case 4:
+ return dataProvider.consumeInt();
+ case 5:
+ return dataProvider.consumeLong();
+ case 6:
+ return dataProvider.consumeFloat();
+ case 7:
+ return dataProvider.consumeDouble();
+ case 8:
+ return dataProvider.consumeString(MAX_STRING_LENGTH);
+ default:
+ return createNestedLogParam(dataProvider);
+ }
+ }
+
+ private static Object createNestedLogParam(final FuzzedDataProvider dataProvider) {
+ final int nestLength = dataProvider.consumeInt(0, 3);
+ final Stream<Object> nestItems =
+ IntStream.range(0, nestLength).mapToObj(ignored -> createLogParam(dataProvider, false));
+ final boolean arrayTyped = dataProvider.consumeBoolean();
+ return arrayTyped ? nestItems.toArray(Object[]::new) : nestItems.collect(Collectors.toList());
+ }
+}
diff --git a/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/JsonEncodingAppender.java b/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/JsonEncodingAppender.java
new file mode 100644
index 0000000000..5962f62f0c
--- /dev/null
+++ b/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/JsonEncodingAppender.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.fuzz;
+
+import static org.apache.logging.log4j.fuzz.JsonUtil.assertValidJson;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.Serializable;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * Appender encoding incoming log events using the provided layout and validating its output's JSON conformance.
+ * It is intended for appender-agnostic fuzzing of JSON-producing layouts.
+ */
+@Plugin(name = JsonEncodingAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
+public final class JsonEncodingAppender extends AbstractAppender {
+
+ public static final String PLUGIN_NAME = "JsonEncodingAppender";
+
+ private JsonEncodingAppender(final String name, final Layout<? extends Serializable> layout) {
+ super(name, null, layout, true, null);
+ // Guard `PLUGIN_NAME` against copy-paste mistakes
+ assertThat(PLUGIN_NAME).isEqualTo(getClass().getSimpleName());
+ }
+
+ @PluginFactory
+ public static JsonEncodingAppender createAppender(
+ final @PluginAttribute("name") String name, final @PluginElement("layout") Layout<?> layout) {
+ return new JsonEncodingAppender(name, layout);
+ }
+
+ @Override
+ public void append(final LogEvent event) {
+ final byte[] jsonBytes;
+ try {
+ jsonBytes = getLayout().toByteArray(event);
+ } catch (final Exception ignored) {
+ // We are inspecting unexpected access.
+ // Hence, event encoding failures are not of interest.
+ return;
+ }
+ assertValidJson(jsonBytes);
+ }
+}
diff --git a/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/JsonUtil.java b/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/JsonUtil.java
new file mode 100644
index 0000000000..daee120f05
--- /dev/null
+++ b/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/JsonUtil.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.fuzz;
+
+import com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical;
+import java.nio.charset.StandardCharsets;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+
+public final class JsonUtil {
+
+ public static void assertValidJson(final String json) {
+ final byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8);
+ assertValidJson(jsonBytes);
+ }
+
+ public static void assertValidJson(final byte[] jsonBytes) {
+ // We deliberately use an external library instead of using `JsonReader` from Log4j.
+ // This is to fuzz Log4j components in isolation.
+ // We are not using Jackson or Gson, since they have token limitations due to performance reasons.
+ // That is, for instance, Jackson supports numbers up to 1000 digits, which the fuzzer quickly bumps into.
+ try {
+ // `JSONObject` requires the input to be a JSON object, hence we are ensuring it to be so:
+ final String json = "{\"x\":" + new String(jsonBytes, StandardCharsets.UTF_8) + "}";
+ final JSONTokener jsonTokener = new JSONTokener(json);
+ new JSONObject(jsonTokener);
+ } catch (final Exception error) {
+ throw new FuzzerSecurityIssueCritical("malformed JSON output", error);
+ }
+ }
+
+ private JsonUtil() {}
+}
diff --git a/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/package-info.java b/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/package-info.java
new file mode 100644
index 0000000000..e18c54c359
--- /dev/null
+++ b/log4j-fuzz-test/src/main/java/org/apache/logging/log4j/fuzz/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@NullMarked
+package org.apache.logging.log4j.fuzz;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/log4j-layout-template-json-fuzz-test/.log4j-plugin-processing-activator b/log4j-layout-template-json-fuzz-test/.log4j-plugin-processing-activator
new file mode 100644
index 0000000000..ba133f3696
--- /dev/null
+++ b/log4j-layout-template-json-fuzz-test/.log4j-plugin-processing-activator
@@ -0,0 +1 @@
+This file is here to activate the `plugin-processing` Maven profile.
diff --git a/log4j-layout-template-json-fuzz-test/pom.xml b/log4j-layout-template-json-fuzz-test/pom.xml
new file mode 100644
index 0000000000..df7720b3db
--- /dev/null
+++ b/log4j-layout-template-json-fuzz-test/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to you under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>${revision}</version>
+ <relativePath>../log4j-parent</relativePath>
+ </parent>
+
+ <artifactId>log4j-layout-template-json-fuzz-test</artifactId>
+
+ <name>Apache Log4j JSON Template Layout fuzz tests</name>
+
+ <properties>
+
+ <bnd.baseline.skip>true</bnd.baseline.skip>
+ <log4j.docgen.skip>true</log4j.docgen.skip>
+ <maven.deploy.skip>true</maven.deploy.skip>
+ <maven.install.skip>true</maven.install.skip>
+ <maven.test.skip>true</maven.test.skip>
+ <sign.skip>true</sign.skip>
+
+ <!-- dependency versions -->
+ <json.version>20240303</json.version>
+
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-layout-template-json</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-fuzz-test</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ <version>${json.version}</version>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/log4j-layout-template-json-fuzz-test/src/main/java/org/apache/logging/log4j/layout/template/json/fuzz/JsonTemplateLayoutCodecFuzzer.java b/log4j-layout-template-json-fuzz-test/src/main/java/org/apache/logging/log4j/layout/template/json/fuzz/JsonTemplateLayoutCodecFuzzer.java
new file mode 100644
index 0000000000..1fe836481e
--- /dev/null
+++ b/log4j-layout-template-json-fuzz-test/src/main/java/org/apache/logging/log4j/layout/template/json/fuzz/JsonTemplateLayoutCodecFuzzer.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.layout.template.json.fuzz;
+
+import static org.apache.logging.log4j.fuzz.JsonUtil.assertValidJson;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults;
+import org.apache.logging.log4j.layout.template.json.util.JsonReader;
+import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
+
+// FUZZER-SETUP-SCRIPT cp ${outputDir}/json.dict ${scriptPath}.dict
+// FUZZER-SETUP-SCRIPT cp ${outputDir}/json_seed_corpus.zip ${scriptPath}_seed_corpus.zip
+
+public final class JsonTemplateLayoutCodecFuzzer {
+
+ // Choosing a reasonable JSON document length sufficing following needs:
+ //
+ // 1. Long enough to give fuzzer room to try breaking stuff
+ //
+ // 2. Short enough to avoid stack overflows, which are interpreted as fuzzer failures, though they are not.
+ // Consider a JSON document containing more than 1024 nested objects.
+ // This triggers a stack overflow, which is expected, hence nothing to signal.
+ private static final int MAX_STRING_LENGTH = 1024;
+
+ public static void fuzzerTestOneInput(final FuzzedDataProvider dataProvider) {
+ final boolean encodeFirst = dataProvider.consumeBoolean();
+ if (encodeFirst) {
+ encodeThenDecode(dataProvider);
+ } else {
+ decodeThenEncode(dataProvider);
+ }
+ }
+
+ private static void encodeThenDecode(final FuzzedDataProvider dataProvider) {
+ final String expectedString = dataProvider.consumeString(MAX_STRING_LENGTH);
+ final String json = encodeJson(expectedString);
+ final Object actualString = JsonReader.read(json);
+ assertThat(actualString).isEqualTo(expectedString);
+ }
+
+ private static void decodeThenEncode(final FuzzedDataProvider dataProvider) {
+ final Object object = decodeJson(dataProvider);
+ final String json = encodeJson(object);
+ assertValidJson(json);
+ }
+
+ private static Object decodeJson(final FuzzedDataProvider dataProvider) {
+ final String expectedJson = dataProvider.consumeString(MAX_STRING_LENGTH);
+ try {
+ return JsonReader.read(expectedJson);
+ } catch (final Exception ignored) {
+ // We are inspecting unexpected access.
+ // Hence, event decoding failures are not of interest.
+ return null;
+ }
+ }
+
+ private static String encodeJson(final Object object) {
+ final JsonWriter jsonWriter = JsonWriter.newBuilder()
+ .setMaxStringLength(MAX_STRING_LENGTH)
+ .setTruncatedStringSuffix(JsonTemplateLayoutDefaults.getTruncatedStringSuffix())
+ .build();
+ jsonWriter.writeValue(object);
+ return jsonWriter.getStringBuilder().toString();
+ }
+}
diff --git a/log4j-layout-template-json-fuzz-test/src/main/java/org/apache/logging/log4j/layout/template/json/fuzz/JsonTemplateLayoutFuzzer.java b/log4j-layout-template-json-fuzz-test/src/main/java/org/apache/logging/log4j/layout/template/json/fuzz/JsonTemplateLayoutFuzzer.java
new file mode 100644
index 0000000000..f44866ca5b
--- /dev/null
+++ b/log4j-layout-template-json-fuzz-test/src/main/java/org/apache/logging/log4j/layout/template/json/fuzz/JsonTemplateLayoutFuzzer.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.layout.template.json.fuzz;
+
+import static org.apache.logging.log4j.fuzz.FuzzingUtil.createLoggerContext;
+import static org.apache.logging.log4j.fuzz.FuzzingUtil.fuzzLogger;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.fuzz.FuzzingUtil.Log4jLoggerFacade;
+import org.apache.logging.log4j.fuzz.FuzzingUtil.LoggerFacade;
+import org.apache.logging.log4j.fuzz.JsonEncodingAppender;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+public final class JsonTemplateLayoutFuzzer {
+
+ public static void fuzzerTestOneInput(final FuzzedDataProvider dataProvider) {
+ final String loggerContextName = JsonTemplateLayoutFuzzer.class.getSimpleName() + "LoggerContext";
+ try (final LoggerContext loggerContext = createLoggerContext(
+ loggerContextName,
+ JsonEncodingAppender.PLUGIN_NAME,
+ configBuilder -> configBuilder.newLayout("JsonTemplateLayout"))) {
+ final ExtendedLogger logger = loggerContext.getLogger(JsonTemplateLayoutFuzzer.class);
+ final LoggerFacade loggerFacade = new Log4jLoggerFacade(logger);
+ fuzzLogger(loggerFacade, dataProvider);
+ }
+ }
+}
diff --git a/log4j-layout-template-json-test/pom.xml b/log4j-layout-template-json-test/pom.xml
index 51bc8a9660..bba5ab598e 100644
--- a/log4j-layout-template-json-test/pom.xml
+++ b/log4j-layout-template-json-test/pom.xml
@@ -31,9 +31,8 @@
<name>Apache Log4j JSON Template Layout tests</name>
<properties>
-
- <log4j.docgen.skip>true</log4j.docgen.skip>
<bnd.baseline.skip>true</bnd.baseline.skip>
+ <log4j.docgen.skip>true</log4j.docgen.skip>
<maven.deploy.skip>true</maven.deploy.skip>
<sign.skip>true</sign.skip>
diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/JsonWriterTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/JsonWriterTest.java
index bdada9f49a..11607acb2d 100644
--- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/JsonWriterTest.java
+++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/JsonWriterTest.java
@@ -49,19 +49,6 @@ class JsonWriterTest {
.setTruncatedStringSuffix("~")
.build();
- private static final int SURROGATE_CODE_POINT = 65536;
-
- private static final char[] SURROGATE_PAIR = new char[2];
-
- static {
- // noinspection ResultOfMethodCallIgnored
- Character.toChars(SURROGATE_CODE_POINT, SURROGATE_PAIR, 0);
- }
-
- private static final char HI_SURROGATE = SURROGATE_PAIR[0];
-
- private static final char LO_SURROGATE = SURROGATE_PAIR[1];
-
private static synchronized <V> V withLockedWriterReturning(final Function<JsonWriter, V> consumer) {
synchronized (WRITER) {
return consumer.apply(WRITER);
@@ -99,16 +86,6 @@ class JsonWriterTest {
Assertions.assertThat(writer.getStringBuilder().length()).isEqualTo(0);
}
- @Test
- void test_surrogate_code_point() {
- Assertions.assertThat(HI_SURROGATE).matches(Character::isHighSurrogate, "is high surrogate");
- Assertions.assertThat(LO_SURROGATE).matches(Character::isLowSurrogate, "is low surrogate");
- Assertions.assertThat(Character.isSurrogatePair(HI_SURROGATE, LO_SURROGATE))
- .as("is surrogate pair")
- .isTrue();
- Assertions.assertThat(SURROGATE_CODE_POINT).matches(Character::isDefined, "is defined");
- }
-
@Test
void test_use_null_Runnable() {
Assertions.assertThatThrownBy(() -> withLockedWriter(writer -> writer.use(null)))
@@ -436,25 +413,6 @@ class JsonWriterTest {
});
}
- @Test
- void test_writeString_emitter_excessive_string_ending_with_high_surrogate() {
- withLockedWriter(writer -> {
- final int maxStringLength = writer.getMaxStringLength();
- @SuppressWarnings("StringBufferReplaceableByString")
- final String excessiveString = new StringBuilder()
- .append(Strings.repeat("x", maxStringLength - 1))
- .append(HI_SURROGATE)
- .append(LO_SURROGATE)
- .toString();
- final String expectedJson =
- "\"" + Strings.repeat("x", maxStringLength - 1) + writer.getTruncatedStringSuffix() + '"';
- final BiConsumer<StringBuilder, String> emitter = StringBuilder::append;
- final String actualJson = writer.use(() -> writer.writeString(emitter, excessiveString));
- Assertions.assertThat(actualJson).isEqualTo(expectedJson);
- assertFormattableBufferReset(writer);
- });
- }
-
@Test
void test_writeString_null_formattable() {
expectNull(writer -> writer.writeString((StringBuilderFormattable) null));
@@ -482,25 +440,6 @@ class JsonWriterTest {
});
}
- @Test
- void test_writeString_formattable_excessive_string_ending_with_high_surrogate() {
- withLockedWriter(writer -> {
- final int maxStringLength = writer.getMaxStringLength();
- @SuppressWarnings("StringBufferReplaceableByString")
- final String excessiveString = new StringBuilder()
- .append(Strings.repeat("x", maxStringLength - 1))
- .append(HI_SURROGATE)
- .append(LO_SURROGATE)
- .toString();
- final String expectedJson =
- "\"" + Strings.repeat("x", maxStringLength - 1) + writer.getTruncatedStringSuffix() + '"';
- final String actualJson =
- writer.use(() -> writer.writeString(stringBuilder -> stringBuilder.append(excessiveString)));
- Assertions.assertThat(actualJson).isEqualTo(expectedJson);
- assertFormattableBufferReset(writer);
- });
- }
-
private static void assertFormattableBufferReset(final JsonWriter writer) {
final StringBuilder formattableBuffer = getFormattableBuffer(writer);
Assertions.assertThat(formattableBuffer.capacity()).isEqualTo(writer.getMaxStringLength());
@@ -552,23 +491,6 @@ class JsonWriterTest {
});
}
- @Test
- void test_writeString_excessive_seq_ending_with_high_surrogate() {
- withLockedWriter(writer -> {
- final int maxStringLength = writer.getMaxStringLength();
- @SuppressWarnings("StringBufferReplaceableByString")
- final CharSequence seq = new StringBuilder()
- .append(Strings.repeat("x", maxStringLength - 1))
- .append(HI_SURROGATE)
- .append(LO_SURROGATE)
- .toString();
- final String expectedJson =
- "\"" + Strings.repeat("x", maxStringLength - 1) + writer.getTruncatedStringSuffix() + '"';
- final String actualJson = writer.use(() -> writer.writeString(seq));
- Assertions.assertThat(actualJson).isEqualTo(expectedJson);
- });
- }
-
@Test
void test_writeString_seq() throws IOException {
final char[] surrogates = new char[2];
@@ -617,24 +539,6 @@ class JsonWriterTest {
});
}
- @Test
- void test_writerString_excessive_buffer_ending_with_high_surrogate() {
- withLockedWriter(writer -> {
- final int maxStringLength = writer.getMaxStringLength();
- @SuppressWarnings("StringBufferReplaceableByString")
- final char[] buffer = new StringBuilder()
- .append(Strings.repeat("x", maxStringLength - 1))
- .append(HI_SURROGATE)
- .append(LO_SURROGATE)
- .toString()
- .toCharArray();
- final String expectedJson =
- "\"" + Strings.repeat("x", maxStringLength - 1) + writer.getTruncatedStringSuffix() + '"';
- final String actualJson = writer.use(() -> writer.writeString(buffer));
- Assertions.assertThat(actualJson).isEqualTo(expectedJson);
- });
- }
-
@Test
void test_writeString_buffer() throws IOException {
final char[] buffer = new char[2];
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/JsonWriter.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/JsonWriter.java
index 4316d731fd..749eb1519e 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/JsonWriter.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/JsonWriter.java
@@ -579,9 +579,7 @@ public final class JsonWriter implements AutoCloseable, Cloneable {
* Quote text contents using JSON standard quoting.
*/
private void quoteString(final CharSequence seq, final int offset, final int length) {
- final int surrogateCorrection =
- length > 0 && Character.isHighSurrogate(seq.charAt(offset + length - 1)) ? -1 : 0;
- final int limit = offset + length + surrogateCorrection;
+ final int limit = offset + length;
int i = offset;
outer:
while (i < limit) {
@@ -643,8 +641,7 @@ public final class JsonWriter implements AutoCloseable, Cloneable {
* Quote text contents using JSON standard quoting.
*/
private void quoteString(final char[] buffer, final int offset, final int length) {
- final int surrogateCorrection = length > 0 && Character.isHighSurrogate(buffer[offset + length - 1]) ? -1 : 0;
- final int limit = offset + length + surrogateCorrection;
+ final int limit = offset + length;
int i = offset;
outer:
while (i < limit) {
diff --git a/log4j-osgi-test/pom.xml b/log4j-osgi-test/pom.xml
index 60930eb317..a4f3d91179 100644
--- a/log4j-osgi-test/pom.xml
+++ b/log4j-osgi-test/pom.xml
@@ -32,15 +32,17 @@
<description>The Apache Log4j OSGi tests</description>
<properties>
- <log4j.docgen.skip>true</log4j.docgen.skip>
- <module.name>org.apache.logging.log4j.osgi</module.name>
+
<bnd.baseline.skip>true</bnd.baseline.skip>
+ <log4j.docgen.skip>true</log4j.docgen.skip>
<maven.deploy.skip>true</maven.deploy.skip>
<maven.install.skip>true</maven.install.skip>
<sign.skip>true</sign.skip>
-
<spotbugs.skip>true</spotbugs.skip>
+
+ <!-- dependency versions -->
<spifly.version>1.3.7</spifly.version>
+
</properties>
<dependencies>
diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml
index 304231465a..78c5d4fc15 100644
--- a/log4j-parent/pom.xml
+++ b/log4j-parent/pom.xml
@@ -103,6 +103,7 @@
<javax-servlet-jsp.version>2.3.3</javax-servlet-jsp.version>
<jansi.version>2.4.1</jansi.version>
<java-allocation-instrumenter.version>3.3.4</java-allocation-instrumenter.version>
+ <jazzer.version>0.22.1</jazzer.version>
<jconsole.version>1.7.0</jconsole.version>
<jctools.version>4.0.5</jctools.version>
<jeromq.version>0.6.0</jeromq.version>
@@ -116,6 +117,7 @@
<kafka.version>3.8.0</kafka.version>
<lightcouch.version>0.2.0</lightcouch.version>
<log4j.version>1.2.17</log4j.version>
+ <log4j-transform.version>0.1.0</log4j-transform.version>
<log4j2-cachefile-transformer.version>2.15.0</log4j2-cachefile-transformer.version>
<log4j2-ecs-layout.version>1.6.0</log4j2-ecs-layout.version>
<logback.version>1.3.14</logback.version>
@@ -158,11 +160,11 @@
<dependencyManagement>
<dependencies>
- <!-- BEGIN: Modules that are not published (and hence missing in the BOM) -->
+ <!-- BEGIN: Modules that are consumed only internally -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-osgi-test</artifactId>
+ <artifactId>log4j-fuzz-test</artifactId>
<version>${project.version}</version>
</dependency>
@@ -172,7 +174,7 @@
<version>${project.version}</version>
</dependency>
- <!-- END: Modules that are not published (and hence missing in the BOM) -->
+ <!-- END: Modules that are consumed only internally -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
@@ -244,6 +246,13 @@
<type>zip</type>
</dependency>
+ <!-- Dummy dependency to trick `dependabot` into updating the `log4j-transform.version` property: -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId>
+ <version>${log4j-transform.version}</version>
+ </dependency>
+
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
@@ -268,6 +277,12 @@
<version>${awaitility.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.code-intelligence</groupId>
+ <artifactId>jazzer</artifactId>
+ <version>${jazzer.version}</version>
+ </dependency>
+
<dependency>
<groupId>org.apache-extras.beanshell</groupId>
<artifactId>bsh</artifactId>
@@ -894,6 +909,61 @@
<version>${maven-taglib-plugin.version}</version>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId>
+ <version>${log4j-transform.version}</version>
+ </dependency>
+ </dependencies>
+ <executions>
+ <execution>
+ <id>generate-uber-jar</id>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <createDependencyReducedPom>false</createDependencyReducedPom>
+ <!-- Minimization causes `@Plugin`-annotated classes to be omitted, hence disabling it: -->
+ <minimizeJar>false</minimizeJar>
+ <finalName>${project.artifactId}-${project.version}-uber</finalName>
+ <transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer" />
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer" />
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <manifestEntries>
+ <Multi-Release>true</Multi-Release>
+ </manifestEntries>
+ </transformer>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
+ <transformer implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer" />
+ </transformers>
+ <filters>
+ <filter>
+ <!--
+ Shading signed JARs will fail without this.
+ http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar
+ -->
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+ <exclude>META-INF/MANIFEST.MF</exclude>
+ <exclude>module-info.class</exclude>
+ <exclude>META-INF/versions/9/module-info.class</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
</plugins>
</pluginManagement>
diff --git a/log4j-perf-test/README.adoc b/log4j-perf-test/README.adoc
new file mode 100644
index 0000000000..ab7951082c
--- /dev/null
+++ b/log4j-perf-test/README.adoc
@@ -0,0 +1,30 @@
+////
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+////
+
+This module contains JMH-based performance tests for Log4j.
+You can use the generated fat JAR to run the tests as follows:
+
+[source,bash]
+----
+java -jar target/log4j-perf-test-*-uber.jar <JMH-ARGS>
+----
+
+To list the available JMH arguments run:
+[source,bash]
+----
+java -jar target/log4j-perf-test-*-uber.jar -h
+----
diff --git a/log4j-perf-test/pom.xml b/log4j-perf-test/pom.xml
index 76a38c5d99..8b47178c9d 100644
--- a/log4j-perf-test/pom.xml
+++ b/log4j-perf-test/pom.xml
@@ -16,7 +16,9 @@
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
<modelVersion>4.0.0</modelVersion>
+
<parent>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j</artifactId>
@@ -30,22 +32,21 @@
<description>The Apache Log4j development-time performance tests</description>
<properties>
- <uberjar.name>benchmarks</uberjar.name>
- <log4j.docgen.skip>true</log4j.docgen.skip>
+
<bnd.baseline.skip>true</bnd.baseline.skip>
+ <log4j.docgen.skip>true</log4j.docgen.skip>
<maven.test.skip>true</maven.test.skip>
<maven.deploy.skip>true</maven.deploy.skip>
<maven.install.skip>true</maven.install.skip>
<sign.skip>true</sign.skip>
+ <spotbugs.skip>true</spotbugs.skip>
- <module.name>org.apache.logging.log4j.perf</module.name>
<maven.compiler.release>9</maven.compiler.release>
<surefire.jdkToolchain>[9, )</surefire.jdkToolchain>
- <spotbugs.skip>true</spotbugs.skip>
<!-- Dependency versions -->
- <log4j-transform.version>0.1.0</log4j-transform.version>
<slf4j2.version>2.0.16</slf4j2.version>
+
</properties>
<dependencyManagement>
@@ -191,56 +192,13 @@
</configuration>
</plugin>
+ <!-- Activate the inherited `generate-uber-jar` configuration -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
- <dependencies>
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId>
- <version>${log4j-transform.version}</version>
- </dependency>
- </dependencies>
- <executions>
- <execution>
- <goals>
- <goal>shade</goal>
- </goals>
- <phase>package</phase>
- <configuration>
- <createDependencyReducedPom>false</createDependencyReducedPom>
- <minimizeJar>false</minimizeJar>
- <finalName>${uberjar.name}</finalName>
- <transformers>
- <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer" />
- <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
- <mainClass>org.openjdk.jmh.Main</mainClass>
- <manifestEntries>
- <Multi-Release>true</Multi-Release>
- </manifestEntries>
- </transformer>
- <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
- <transformer implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer" />
- </transformers>
- <filters>
- <filter>
- <!--
- Shading signed JARs will fail without this.
- http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar
- -->
- <artifact>*:*</artifact>
- <excludes>
- <exclude>META-INF/*.SF</exclude>
- <exclude>META-INF/*.DSA</exclude>
- <exclude>META-INF/*.RSA</exclude>
- </excludes>
- </filter>
- </filters>
- </configuration>
- </execution>
- </executions>
</plugin>
</plugins>
</build>
+
</project>
diff --git a/log4j-slf4j2-impl-fuzz-test/pom.xml b/log4j-slf4j2-impl-fuzz-test/pom.xml
new file mode 100644
index 0000000000..6dc3fd1020
--- /dev/null
+++ b/log4j-slf4j2-impl-fuzz-test/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to you under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>${revision}</version>
+ <relativePath>../log4j-parent</relativePath>
+ </parent>
+
+ <artifactId>log4j-slf4j2-impl-fuzz-test</artifactId>
+
+ <name>SLF4J 2 Provider for Log4j API fuzz tests</name>
+
+ <properties>
+ <bnd.baseline.skip>true</bnd.baseline.skip>
+ <log4j.docgen.skip>true</log4j.docgen.skip>
+ <maven.deploy.skip>true</maven.deploy.skip>
+ <maven.install.skip>true</maven.install.skip>
+ <maven.test.skip>true</maven.test.skip>
+ <sign.skip>true</sign.skip>
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j2-impl</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-fuzz-test</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/log4j-slf4j2-impl-fuzz-test/src/main/java/org/apache/logging/slf4j/fuzz/Slf4jLoggerFacade.java b/log4j-slf4j2-impl-fuzz-test/src/main/java/org/apache/logging/slf4j/fuzz/Slf4jLoggerFacade.java
new file mode 100644
index 0000000000..5622f0343c
--- /dev/null
+++ b/log4j-slf4j2-impl-fuzz-test/src/main/java/org/apache/logging/slf4j/fuzz/Slf4jLoggerFacade.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.slf4j.fuzz;
+
+import static java.util.Objects.requireNonNull;
+
+import org.apache.logging.log4j.fuzz.FuzzingUtil.LoggerFacade;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.spi.CallerBoundaryAware;
+import org.slf4j.spi.LoggingEventBuilder;
+
+public final class Slf4jLoggerFacade implements LoggerFacade {
+
+ private static final String FQCN = Slf4jLoggerFacade.class.getName();
+
+ private final Logger logger;
+
+ private Slf4jLoggerFacade(final Logger logger) {
+ this.logger = logger;
+ }
+
+ static Slf4jLoggerFacade ofClass(final Class<?> clazz) {
+ requireNonNull(clazz, "clazz");
+ final Logger logger = LoggerFactory.getLogger(clazz);
+ return new Slf4jLoggerFacade(logger);
+ }
+
+ private LoggingEventBuilder atError() {
+ final LoggingEventBuilder builder = logger.atError();
+ if (builder instanceof CallerBoundaryAware) {
+ ((CallerBoundaryAware) builder).setCallerBoundary(FQCN);
+ }
+ return builder;
+ }
+
+ @Override
+ public void log(final String message) {
+ atError().log(message);
+ }
+
+ @Override
+ public void log(final String message, final Throwable throwable) {
+ atError().setCause(throwable).log(message);
+ }
+
+ @Override
+ public void log(final String message, final Object[] parameters) {
+ atError().log(message, parameters);
+ }
+}
diff --git a/log4j-slf4j2-impl-fuzz-test/src/main/java/org/apache/logging/slf4j/fuzz/Slf4jToLog4jBridgeWithJsonTemplateLayoutFuzzer.java b/log4j-slf4j2-impl-fuzz-test/src/main/java/org/apache/logging/slf4j/fuzz/Slf4jToLog4jBridgeWithJsonTemplateLayoutFuzzer.java
new file mode 100644
index 0000000000..0ee07f0572
--- /dev/null
+++ b/log4j-slf4j2-impl-fuzz-test/src/main/java/org/apache/logging/slf4j/fuzz/Slf4jToLog4jBridgeWithJsonTemplateLayoutFuzzer.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.slf4j.fuzz;
+
+import static org.apache.logging.log4j.fuzz.FuzzingUtil.fuzzLogger;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.fuzz.FuzzingUtil.LoggerFacade;
+
+public final class Slf4jToLog4jBridgeWithJsonTemplateLayoutFuzzer {
+
+ static {
+ final String configurationFile = Slf4jToLog4jBridgeWithJsonTemplateLayoutFuzzer.class.getSimpleName() + ".xml";
+ System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, configurationFile);
+ }
+
+ private static final LoggerFacade LOGGER =
+ Slf4jLoggerFacade.ofClass(Slf4jToLog4jBridgeWithJsonTemplateLayoutFuzzer.class);
+
+ public static void fuzzerTestOneInput(final FuzzedDataProvider dataProvider) {
+ fuzzLogger(LOGGER, dataProvider);
+ }
+}
diff --git a/log4j-slf4j2-impl-fuzz-test/src/main/java/org/apache/logging/slf4j/fuzz/Slf4jToLog4jBridgeWithPatternLayoutFuzzer.java b/log4j-slf4j2-impl-fuzz-test/src/main/java/org/apache/logging/slf4j/fuzz/Slf4jToLog4jBridgeWithPatternLayoutFuzzer.java
new file mode 100644
index 0000000000..059d0ac9a8
--- /dev/null
+++ b/log4j-slf4j2-impl-fuzz-test/src/main/java/org/apache/logging/slf4j/fuzz/Slf4jToLog4jBridgeWithPatternLayoutFuzzer.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.slf4j.fuzz;
+
+import static org.apache.logging.log4j.fuzz.FuzzingUtil.fuzzLogger;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.fuzz.FuzzingUtil.LoggerFacade;
+
+public final class Slf4jToLog4jBridgeWithPatternLayoutFuzzer {
+
+ static {
+ final String configurationFile = Slf4jToLog4jBridgeWithPatternLayoutFuzzer.class.getSimpleName() + ".xml";
+ System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, configurationFile);
+ }
+
+ private static final LoggerFacade LOGGER =
+ Slf4jLoggerFacade.ofClass(Slf4jToLog4jBridgeWithPatternLayoutFuzzer.class);
+
+ public static void fuzzerTestOneInput(final FuzzedDataProvider dataProvider) {
+ fuzzLogger(LOGGER, dataProvider);
+ }
+}
diff --git a/log4j-slf4j2-impl-fuzz-test/src/main/resources/Slf4jToLog4jBridgeWithJsonTemplateLayoutFuzzer.xml b/log4j-slf4j2-impl-fuzz-test/src/main/resources/Slf4jToLog4jBridgeWithJsonTemplateLayoutFuzzer.xml
new file mode 100644
index 0000000000..a60edb4b16
--- /dev/null
+++ b/log4j-slf4j2-impl-fuzz-test/src/main/resources/Slf4jToLog4jBridgeWithJsonTemplateLayoutFuzzer.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to you under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<Configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="https://logging.apache.org/xml/ns"
+ xsi:schemaLocation="
+ https://logging.apache.org/xml/ns
+ https://logging.apache.org/xml/ns/log4j-config-2.xsd">
+
+ <Appenders>
+ <JsonEncodingAppender name="FUZZING_APPENDER">
+ <JsonTemplateLayout/>
+ </JsonEncodingAppender>
+ </Appenders>
+
+ <Loggers>
+ <Root level="ALL">
+ <AppenderRef ref="FUZZING_APPENDER"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
diff --git a/log4j-slf4j2-impl-fuzz-test/src/main/resources/Slf4jToLog4jBridgeWithPatternLayoutFuzzer.xml b/log4j-slf4j2-impl-fuzz-test/src/main/resources/Slf4jToLog4jBridgeWithPatternLayoutFuzzer.xml
new file mode 100644
index 0000000000..5f3ea8d49f
--- /dev/null
+++ b/log4j-slf4j2-impl-fuzz-test/src/main/resources/Slf4jToLog4jBridgeWithPatternLayoutFuzzer.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to you under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<Configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="https://logging.apache.org/xml/ns"
+ xsi:schemaLocation="
+ https://logging.apache.org/xml/ns
+ https://logging.apache.org/xml/ns/log4j-config-2.xsd">
+
+ <Appenders>
+ <EncodingAppender name="FUZZING_APPENDER">
+ <PatternLayout pattern="%m%n"/>
+ </EncodingAppender>
+ </Appenders>
+
+ <Loggers>
+ <Root level="ALL">
+ <AppenderRef ref="FUZZING_APPENDER"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
diff --git a/mvnw b/mvnw
index 5e9618cac2..19529ddf8c 100755
--- a/mvnw
+++ b/mvnw
@@ -21,312 +21,239 @@
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.2
#
-# Required ENV vars:
-# ------------------
-# JAVA_HOME - location of a JDK home dir
-#
# Optional ENV vars
# -----------------
-# MAVEN_OPTS - parameters passed to the Java VM when running Maven
-# e.g. to debug Maven itself, use
-# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
-if [ -z "$MAVEN_SKIP_RC" ]; then
-
- if [ -f /usr/local/etc/mavenrc ]; then
- . /usr/local/etc/mavenrc
- fi
-
- if [ -f /etc/mavenrc ]; then
- . /etc/mavenrc
- fi
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
- if [ -f "$HOME/.mavenrc" ]; then
- . "$HOME/.mavenrc"
- fi
-
-fi
-
-# OS specific support. $var _must_ be set to either true or false.
-cygwin=false
-darwin=false
-mingw=false
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
case "$(uname)" in
-CYGWIN*) cygwin=true ;;
-MINGW*) mingw=true ;;
-Darwin*)
- darwin=true
- # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
- # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
- if [ -z "$JAVA_HOME" ]; then
- if [ -x "/usr/libexec/java_home" ]; then
- JAVA_HOME="$(/usr/libexec/java_home)"
- export JAVA_HOME
- else
- JAVA_HOME="/Library/Java/Home"
- export JAVA_HOME
- fi
- fi
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
;;
esac
-if [ -z "$JAVA_HOME" ]; then
- if [ -r /etc/gentoo-release ]; then
- JAVA_HOME=$(java-config --jre-home)
- fi
-fi
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched
-if $cygwin; then
- [ -n "$JAVA_HOME" ] \
- && JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
- [ -n "$CLASSPATH" ] \
- && CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
-fi
-
-# For Mingw, ensure paths are in UNIX format before anything is touched
-if $mingw; then
- [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \
- && JAVA_HOME="$(
- cd "$JAVA_HOME" || (
- echo "cannot cd into $JAVA_HOME." >&2
- exit 1
- )
- pwd
- )"
-fi
-
-if [ -z "$JAVA_HOME" ]; then
- javaExecutable="$(which javac)"
- if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then
- # readlink(1) is not available as standard on Solaris 10.
- readLink=$(which readlink)
- if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
- if $darwin; then
- javaHome="$(dirname "$javaExecutable")"
- javaExecutable="$(cd "$javaHome" && pwd -P)/javac"
- else
- javaExecutable="$(readlink -f "$javaExecutable")"
- fi
- javaHome="$(dirname "$javaExecutable")"
- javaHome=$(expr "$javaHome" : '\(.*\)/bin')
- JAVA_HOME="$javaHome"
- export JAVA_HOME
- fi
- fi
-fi
-
-if [ -z "$JAVACMD" ]; then
- if [ -n "$JAVA_HOME" ]; then
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
fi
else
JAVACMD="$(
- \unset -f command 2>/dev/null
- \command -v java
- )"
- fi
-fi
-
-if [ ! -x "$JAVACMD" ]; then
- echo "Error: JAVA_HOME is not defined correctly." >&2
- echo " We cannot execute $JAVACMD" >&2
- exit 1
-fi
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
-if [ -z "$JAVA_HOME" ]; then
- echo "Warning: JAVA_HOME environment variable is not set." >&2
-fi
-
-# traverses directory structure from process work directory to filesystem root
-# first directory with .mvn subdirectory is considered project base directory
-find_maven_basedir() {
- if [ -z "$1" ]; then
- echo "Path not specified to find_maven_basedir" >&2
- return 1
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
fi
+}
- basedir="$1"
- wdir="$1"
- while [ "$wdir" != '/' ]; do
- if [ -d "$wdir"/.mvn ]; then
- basedir=$wdir
- break
- fi
- # workaround for JBEAP-8937 (on Solaris 10/Sparc)
- if [ -d "${wdir}" ]; then
- wdir=$(
- cd "$wdir/.." || exit 1
- pwd
- )
- fi
- # end of workaround
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
done
- printf '%s' "$(
- cd "$basedir" || exit 1
- pwd
- )"
+ printf %x\\n $h
}
-# concatenates all lines of a file
-concat_lines() {
- if [ -f "$1" ]; then
- # Remove \r in case we run on Windows within Git Bash
- # and check out the repository with auto CRLF management
- # enabled. Otherwise, we may read lines that are delimited with
- # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
- # splitting rules.
- tr -s '\r\n' ' ' <"$1"
- fi
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
}
-log() {
- if [ "$MVNW_VERBOSE" = true ]; then
- printf '%s\n' "$1"
- fi
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
}
-BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
-if [ -z "$BASE_DIR" ]; then
- exit 1
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
fi
-MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
-export MAVEN_PROJECTBASEDIR
-log "$MAVEN_PROJECTBASEDIR"
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
-##########################################################################################
-# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-# This allows using the maven wrapper in projects that prohibit checking in binary data.
-##########################################################################################
-wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
-if [ -r "$wrapperJarPath" ]; then
- log "Found $wrapperJarPath"
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
else
- log "Couldn't find $wrapperJarPath, downloading it ..."
+ die "cannot create temp dir"
+fi
- if [ -n "$MVNW_REPOURL" ]; then
- wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
- else
- wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
- fi
- while IFS="=" read -r key value; do
- # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
- safeValue=$(echo "$value" | tr -d '\r')
- case "$key" in wrapperUrl)
- wrapperUrl="$safeValue"
- break
- ;;
- esac
- done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
- log "Downloading from: $wrapperUrl"
+mkdir -p -- "${MAVEN_HOME%/*}"
- if $cygwin; then
- wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
- fi
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
- if command -v wget >/dev/null; then
- log "Found wget ... using wget"
- [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
- else
- wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
- fi
- elif command -v curl >/dev/null; then
- log "Found curl ... using curl"
- [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
- else
- curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
- fi
- else
- log "Falling back to using Java to download"
- javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
- javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
- # For Cygwin, switch paths to Windows format before running javac
- if $cygwin; then
- javaSource=$(cygpath --path --windows "$javaSource")
- javaClass=$(cygpath --path --windows "$javaClass")
- fi
- if [ -e "$javaSource" ]; then
- if [ ! -e "$javaClass" ]; then
- log " - Compiling MavenWrapperDownloader.java ..."
- ("$JAVA_HOME/bin/javac" "$javaSource")
- fi
- if [ -e "$javaClass" ]; then
- log " - Running MavenWrapperDownloader.java ..."
- ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
- fi
- fi
- fi
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
fi
-##########################################################################################
-# End of extension
-##########################################################################################
-# If specified, validate the SHA-256 sum of the Maven wrapper jar file
-wrapperSha256Sum=""
-while IFS="=" read -r key value; do
- case "$key" in wrapperSha256Sum)
- wrapperSha256Sum=$value
- break
- ;;
- esac
-done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
-if [ -n "$wrapperSha256Sum" ]; then
- wrapperSha256Result=false
- if command -v sha256sum >/dev/null; then
- if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then
- wrapperSha256Result=true
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
- if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then
- wrapperSha256Result=true
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
- echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
- if [ $wrapperSha256Result = false ]; then
- echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
- echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
- echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
-MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin; then
- [ -n "$JAVA_HOME" ] \
- && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
- [ -n "$CLASSPATH" ] \
- && CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
- [ -n "$MAVEN_PROJECTBASEDIR" ] \
- && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
-# Provide a "standardized" way to retrieve the CLI args that will
-# work with both Windows and non-Windows executions.
-MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
-export MAVEN_CMD_LINE_ARGS
-
-WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-# shellcheck disable=SC2086 # safe args
-exec "$JAVACMD" \
- $MAVEN_OPTS \
- $MAVEN_DEBUG_OPTS \
- -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
- ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
+clean || :
+exec_maven "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
index 4136715f08..249bdf3822 100644
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -1,3 +1,4 @@
+<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@@ -20,187 +21,129 @@
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
-@REM Required ENV vars:
-@REM JAVA_HOME - location of a JDK home dir
-@REM
@REM Optional ENV vars
-@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
-@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
-@REM e.g. to debug Maven itself, use
-@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
-@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
-@echo off
-@REM set title of command window
-title %0
-@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
-@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
-
-@REM set %HOME% to equivalent of $HOME
-if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
-
-@REM Execute a user defined script before this one
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
-@REM check for pre script, once with legacy .bat ending and once with .cmd ending
-if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
-if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
-:skipRcPre
-
-@setlocal
-
-set ERROR_CODE=0
-
-@REM To isolate internal variables from possible post scripts, we use another setlocal
-@setlocal
-
-@REM ==== START VALIDATION ====
-if not "%JAVA_HOME%" == "" goto OkJHome
-
-echo. >&2
-echo Error: JAVA_HOME not found in your environment. >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo. >&2
-goto error
-
-:OkJHome
-if exist "%JAVA_HOME%\bin\java.exe" goto init
-
-echo. >&2
-echo Error: JAVA_HOME is set to an invalid directory. >&2
-echo JAVA_HOME = "%JAVA_HOME%" >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo. >&2
-goto error
-
-@REM ==== END VALIDATION ====
-
-:init
-
-@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
-@REM Fallback to current working directory if not found.
-
-set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
-IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
-
-set EXEC_DIR=%CD%
-set WDIR=%EXEC_DIR%
-:findBaseDir
-IF EXIST "%WDIR%"\.mvn goto baseDirFound
-cd ..
-IF "%WDIR%"=="%CD%" goto baseDirNotFound
-set WDIR=%CD%
-goto findBaseDir
-
-:baseDirFound
-set MAVEN_PROJECTBASEDIR=%WDIR%
-cd "%EXEC_DIR%"
-goto endDetectBaseDir
-
-:baseDirNotFound
-set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
-cd "%EXEC_DIR%"
-
-:endDetectBaseDir
-
-IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
-
-@setlocal EnableExtensions EnableDelayedExpansion
-for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
-@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
-
-:endReadAdditionalConfig
-
-SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
-set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
-
-FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
- IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
-)
-
-@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
-if exist %WRAPPER_JAR% (
- if "%MVNW_VERBOSE%" == "true" (
- echo Found %WRAPPER_JAR%
- )
-) else (
- if not "%MVNW_REPOURL%" == "" (
- SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
- )
- if "%MVNW_VERBOSE%" == "true" (
- echo Couldn't find %WRAPPER_JAR%, downloading it ...
- echo Downloading from: %WRAPPER_URL%
- )
-
- powershell -Command "&{"^
- "$webclient = new-object System.Net.WebClient;"^
- "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
- "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
- "}"^
- "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
- "}"
- if "%MVNW_VERBOSE%" == "true" (
- echo Finished downloading %WRAPPER_JAR%
- )
-)
-@REM End of extension
-
-@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
-SET WRAPPER_SHA_256_SUM=""
-FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
- IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
-IF NOT %WRAPPER_SHA_256_SUM%=="" (
- powershell -Command "&{"^
- "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^
- "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
- "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
- " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
- " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
- " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
- " exit 1;"^
- "}"^
- "}"
- if ERRORLEVEL 1 goto error
-)
-
-@REM Provide a "standardized" way to retrieve the CLI args that will
-@REM work with both Windows and non-Windows executions.
-set MAVEN_CMD_LINE_ARGS=%*
-
-%MAVEN_JAVA_EXE% ^
- %JVM_CONFIG_MAVEN_PROPS% ^
- %MAVEN_OPTS% ^
- %MAVEN_DEBUG_OPTS% ^
- -classpath %WRAPPER_JAR% ^
- "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
- %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
-if ERRORLEVEL 1 goto error
-goto end
-
-:error
-set ERROR_CODE=1
-
-:end
-@endlocal & set ERROR_CODE=%ERROR_CODE%
-
-if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
-@REM check for post script, once with legacy .bat ending and once with .cmd ending
-if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
-if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
-:skipRcPost
-
-@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
-if "%MAVEN_BATCH_PAUSE%"=="on" pause
-
-if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
-
-cmd /C exit /B %ERROR_CODE%
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/oss-fuzz-build.sh b/oss-fuzz-build.sh
new file mode 100755
index 0000000000..98f0650695
--- /dev/null
+++ b/oss-fuzz-build.sh
@@ -0,0 +1,217 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Enable strict mode
+set -euo pipefail
+IFS=$'\n\t'
+
+# Read command line arguments
+if [[ "$#" -ne 1 || -z "${JVM_LD_LIBRARY_PATH:-}" ]]; then
+ cat >&2 <<EOF
+Generates fuzzer runner scripts to be employed by Google OSS-Fuzz.
+See "FUZZING.adoc" for details.
+
+Usage: $0 <outputDir>
+
+ outputDir
+
+ The output directory to dump runner scripts and their dependencies.
+
+Environment variables:
+
+ BUILD (optional)
+
+ If "false", Maven installation will be skipped.
+
+ FORCE_DOWNLOAD (optional)
+
+ If "true", certain externally downloaded files (corpus, dictionary, etc.)
+ will be downloaded anyway and override existing ones.
+
+ JVM_LD_LIBRARY_PATH (required)
+
+ Directory containing the Java shared libraries, e.g.,
+ "/usr/lib/jvm/zulu17/lib/server".
+EOF
+ exit 1
+fi
+outputDir=$(readlink -f "$1")
+
+# Ensure output directory exists
+mkdir -p "$outputDir"
+
+# Switch to the script directory
+cd -- "$(dirname -- "${BASH_SOURCE[0]}")"
+
+# Record the Git commit ID
+commitId=$(git rev-parse HEAD)
+
+# To contain all Maven dependencies under `$outputDir`, explicitly provide the Maven local repository.
+# `.`-prefixed files are not copied by OSS-Fuzz, don't use them!
+export MAVEN_OPTS="-Dmaven.repo.local=$outputDir/m2"
+
+# Make Maven executions scripting friendly
+export MAVEN_ARGS="--batch-mode --no-transfer-progress --errors"
+
+# Determine the project version
+projectVersion=$(./mvnw \
+ --quiet -DforceStdout=true \
+ -Dexpression=project.version \
+ help:evaluate \
+ | tail -n 1)
+
+# Build the project
+[ "${BUILD:-}" != "false" ] && ./mvnw \
+ -Dmaven.test.skip=true \
+ -Dxml.skip \
+ -Dcyclonedx.skip \
+ -Dbnd.baseline.skip \
+ -Drat.skip \
+ -Dspotless.skip \
+ -Dspotbugs.skip \
+ -Denforcer.skip \
+ install
+
+# Download the JSON dictionary
+jsonDictPath="$outputDir/json.dict"
+[[ ! -f "$jsonDictPath" || "${FORCE_DOWNLOAD:-}" = "true" ]] && \
+ wget --quiet https://raw.githubusercontent.com/google/fuzzing/master/dictionaries/json.dict -O "$jsonDictPath"
+
+# Download the JSON seed corpus
+jsonSeedCorpusPath="$outputDir/json_seed_corpus.zip"
+if [[ ! -f "$jsonSeedCorpusPath" || "${FORCE_DOWNLOAD:-}" = "true" ]]; then
+ git clone --quiet --depth 1 https://github.com/dvyukov/go-fuzz-corpus
+ zip -q -j "$jsonSeedCorpusPath" go-fuzz-corpus/json/corpus/*
+ rm -rf go-fuzz-corpus
+fi
+
+# Build and fuzz environments are different: https://google.github.io/oss-fuzz/further-reading/fuzzer-environment/
+# Package the Java runtime along with the fuzzers.
+javaHomeFileName="jvm"
+cp -rL "$JAVA_HOME" "$outputDir/$javaHomeFileName"
+
+# Iterate over fuzzers
+for module in *-fuzz-test; do
+
+ # Copy the built module artifact to the output folder
+ cp "$PWD/$module/target/$module-$projectVersion.jar" "$outputDir/$module-$projectVersion.jar"
+
+ # Determine the Java class path
+ ./mvnw dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt -pl $module
+ classPath="$(sed "s|$outputDir/||g" /tmp/cp.txt):$module-$projectVersion.jar"
+ rm /tmp/cp.txt
+
+ for fuzzer in $(find "$module" -name "*Fuzzer.java"); do
+
+ # Create the runner script
+ fqcn=$(echo "$fuzzer" | sed -r 's|^'$module'/src/main/java/(.+)\.java|\1|' | tr / .)
+ className="${fqcn##*.}"
+ scriptPath="$outputDir/$module-$className"
+ cat >"$scriptPath" <<EOF
+#!/bin/bash
+#
+# This file is auto-generated by https://github.com/apache/logging-log4j2/tree/2.x/oss-fuzz-build.sh
+#
+
+# OSS-Fuzz detects fuzzers by checking the presence of the magical "LLVMFuzzerTestOneInput" word, hence this line.
+
+# Enable strict mode
+set -euo pipefail
+IFS=\$'\n\t'
+
+# Report the build
+echo "INFO: Produced using the commit ID: $commitId"
+
+# Switch to the script directory
+cd -- "\$(dirname -- "\${BASH_SOURCE[0]}")"
+
+# Determine JVM args
+if [[ "\$@" =~ (^| )-runs=[0-9]+(\$| ) ]]; then
+ jvmArgs="-Xmx1900m:-Xss900k"
+else
+ jvmArgs="-Xmx2048m:-Xss1024k"
+fi
+
+# Verify the classpath
+classPath="$classPath"
+echo "\$classPath" | sed 's/:/\n/g' | while read classPathFilePath; do
+ if [[ ! -f "\$classPathFilePath" ]]; then
+ echo "ERROR: Could not find class path file: \"\$classPathFilePath\""
+ echo "ERROR: Complete class path: \"\$classPath\""
+ echo "DEBUG: \"ls\" output"
+ ls -al | sed "s/^/DEBUG: /g"
+ classPathFileName=\$(basename "\$classPathFilePath")
+ echo "DEBUG: \"find\" output"
+ find / -name "\$classPathFileName" 2>/dev/null | sed "s/^/DEBUG: /g"
+ exit 1
+ fi
+done
+
+# Employ the Java runtime
+export JAVA_HOME="\$PWD/$javaHomeFileName"
+export JVM_LD_LIBRARY_PATH="\$JAVA_HOME/lib/server"
+export PATH="\$JAVA_HOME/bin:\$PATH"
+java -version
+
+# Dump some debugging aid
+dump_debug() {
+ echo "DEBUG: Dumping system memory information..."
+ free -m
+ echo "DEBUG: Dumping environment variables..."
+ export
+ for filePath in . "\${CRASH_STACKTRACES_DIR:-}" "\${CRASH_STACKTRACES_DIR:-}" "\${DATA_BUNDLES_DIR:-}" "\${FUZZ_INPUTS:-}"; do
+ if [ -e "\$filePath" ]; then
+ echo "DEBUG: Listing \"\$filePath\"..."
+ ls -al "\$filePath"
+ fi
+ done
+}
+dump_debug
+
+# Create a temporary file for capturing the command output
+cmdOutputFile=\$(mktemp "$module-$className-XXX.out")
+trap 'rm -f -- "'\$cmdOutputFile'"' EXIT
+
+# Run the fuzzer
+LD_LIBRARY_PATH="\$JVM_LD_LIBRARY_PATH":. \\
+jazzer_driver \\
+ --agent_path=jazzer_agent_deploy.jar \\
+ --cp="\$classPath" \\
+ --target_class="$fqcn" \\
+ --jvm_args="\$jvmArgs" \\
+ \$@ \\
+ 2>&1 | tee "\$cmdOutputFile" || {
+ retCode=\$?;
+ if grep -q "A fatal error has been detected by the Java Runtime Environment" "\$cmdOutputFile"; then
+ echo "WARN: Detected JRE crash; it doesn't qualify as a fuzzing failure."
+ echo "WARN: Cleaning up crash reports and exiting with success..."
+ dump_debug
+ exit 0
+ else
+ exit \$retCode
+ fi
+ }
+EOF
+ chmod +x "$scriptPath"
+
+ # Execute fuzzer setup scripts
+ { grep "^// FUZZER-SETUP-SCRIPT " "$fuzzer" || :; } | while read _ _ setupScript; do
+ eval $setupScript
+ done
+
+ done
+
+done
diff --git a/pom.xml b/pom.xml
index e778a2e640..5b6219e14d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -230,7 +230,7 @@
<!-- the parent POM must come first: -->
<module>log4j-parent</module>
- <!-- Next unpublished modules in alphabetical order: -->
+ <!-- Next unpublished, but internally and *implicitly* (e.g., as a plugin dependency) consumed modules in alphabetical order: -->
<module>log4j-api-java9</module>
<module>log4j-core-java9</module>
@@ -242,10 +242,12 @@
<module>log4j-appserver</module>
<module>log4j-cassandra</module>
<module>log4j-core</module>
+ <module>log4j-core-fuzz-test</module>
<module>log4j-core-its</module>
<module>log4j-core-test</module>
<module>log4j-couchdb</module>
<module>log4j-docker</module>
+ <module>log4j-fuzz-test</module>
<module>log4j-iostreams</module>
<module>log4j-jakarta-smtp</module>
<module>log4j-jakarta-web</module>
@@ -255,6 +257,7 @@
<module>log4j-jdbc-dbcp2</module>
<module>log4j-jul</module>
<module>log4j-layout-template-json</module>
+ <module>log4j-layout-template-json-fuzz-test</module>
<module>log4j-layout-template-json-test</module>
<module>log4j-mongodb</module>
<module>log4j-mongodb4</module>
@@ -262,6 +265,7 @@
<module>log4j-perf-test</module>
<module>log4j-slf4j-impl</module>
<module>log4j-slf4j2-impl</module>
+ <module>log4j-slf4j2-impl-fuzz-test</module>
<module>log4j-spring-boot</module>
<module>log4j-spring-cloud-config-client</module>
<module>log4j-taglib</module>
@@ -372,6 +376,10 @@
<dependencyManagement>
<dependencies>
+ <!-- List of managed dependencies (in alphabetical order) that are intended for public consumption.
+ Modules that are not used anywhere (e.g., `log4j-core-its`, `log4j-osgi-test`) should not be placed here.
+ Modules that are used only internally (e.g., `log4j-layout-template-json-test`) should be added to `dependencyManagement > dependencies` in `log4j-parent/pom.xml`. -->
+
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
diff --git a/src/changelog/.2.x.x/2949_add_fuzzing.xml b/src/changelog/.2.x.x/2949_add_fuzzing.xml
new file mode 100644
index 0000000000..5ca555b861
--- /dev/null
+++ b/src/changelog/.2.x.x/2949_add_fuzzing.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="https://logging.apache.org/xml/ns"
+ xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
+ type="added">
+ <issue id="2949" link="https://github.com/apache/logging-log4j2/pull/2949"/>
+ <description format="asciidoc">Add Google OSS-Fuzz integration</description>
+</entry>
diff --git a/src/changelog/.2.x.x/fix_JsonWriter_surrogate_handling.xml b/src/changelog/.2.x.x/fix_JsonWriter_surrogate_handling.xml
new file mode 100644
index 0000000000..d3610aaec4
--- /dev/null
+++ b/src/changelog/.2.x.x/fix_JsonWriter_surrogate_handling.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="https://logging.apache.org/xml/ns"
+ xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
+ type="fixed">
+ <description format="asciidoc">Avoid surrogate character handling in `JsonWriter`</description>
+</entry>
diff --git a/src/site/antora/modules/ROOT/pages/development.adoc b/src/site/antora/modules/ROOT/pages/development.adoc
index f9efa8959d..edc861ede6 100644
--- a/src/site/antora/modules/ROOT/pages/development.adoc
+++ b/src/site/antora/modules/ROOT/pages/development.adoc
@@ -61,10 +61,15 @@ See {logging-services-url}/logging-parent/usage.html#website[the Logging Parent
Branch triggering the CI logic to start {logging-services-url}/logging-parent/release-instructions-project.html[the release process]
[#building]
-== How can I build Log4j from sources?
+== How can I build the project?
See {project-github-url}/BUILDING.adoc[the build instructions].
+[#fuzzing]
+== How can I run fuzz tests?
+
+See {project-github-url}/FUZZING.adoc[the fuzzing instructions].
+
[#contributor]
== I am **not** a committer. How shall I submit a patch?