ParseFuzzer.java
// Copyright 2025 Google LLC
//
// Licensed 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.
//
///////////////////////////////////////////////////////////////////////////
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.powsybl.cgmes.model.FullModel;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.exceptions.UncheckedXmlStreamException;
import com.powsybl.computation.Partition;
import com.powsybl.dynawo.commons.dynawologs.CsvLogParser;
import com.powsybl.dynawo.commons.timeline.CsvTimeLineParser;
import com.powsybl.entsoe.util.BoundaryPointXlsParser;
import com.powsybl.entsoe.util.EntsoeFileName;
import com.powsybl.powerfactory.model.PowerFactoryException;
import com.powsybl.powerfactory.model.Project;
import com.powsybl.powerfactory.model.StudyCase;
import com.powsybl.psse.model.io.Context;
import com.powsybl.sensitivity.SensitivityAnalysisResult.SensitivityContingencyStatus;
import com.powsybl.sensitivity.SensitivityFactor;
import com.powsybl.sensitivity.SensitivityValue;
import com.powsybl.sensitivity.SensitivityVariableSet;
import com.powsybl.sensitivity.WeightedSensitivityVariable;
import com.powsybl.timeseries.InfiniteTimeSeriesIndex;
import com.powsybl.timeseries.IrregularTimeSeriesIndex;
import com.powsybl.timeseries.RegularTimeSeriesIndex;
import com.powsybl.timeseries.TimeSeries;
import com.powsybl.timeseries.ast.NodeCalc;
import com.univocity.parsers.common.TextParsingException;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeParseException;
public class ParseFuzzer {
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
if (data.remainingBytes() < 5) {
return;
}
try {
JsonParser parser;
String str;
ByteArrayInputStream bais;
InputStreamReader reader;
byte[] bytes = data.consumeBytes(Math.min(data.remainingBytes() - 4, 50000));
Integer choice = data.consumeInt(1, 19);
switch (choice) {
case 1:
bais = new ByteArrayInputStream(bytes);
reader = new InputStreamReader(bais);
FullModel.parse(reader);
break;
case 2:
str = new String(bytes, StandardCharsets.UTF_8);
Partition.parse(str);
break;
case 3:
bais = new ByteArrayInputStream(bytes);
new BoundaryPointXlsParser().parse(bais);
break;
case 4:
str = new String(bytes, StandardCharsets.UTF_8);
EntsoeFileName.parse(str);
break;
case 5:
parser = new JsonFactory().createParser(bytes);
SensitivityFactor.parseJson(parser);
break;
case 6:
parser = new JsonFactory().createParser(bytes);
SensitivityValue.parseJson(parser);
break;
case 7:
parser = new JsonFactory().createParser(bytes);
SensitivityVariableSet.parseJson(parser);
break;
case 8:
parser = new JsonFactory().createParser(bytes);
WeightedSensitivityVariable.parseJson(parser);
break;
case 9:
parser = new JsonFactory().createParser(bytes);
SensitivityContingencyStatus.parseJson(parser);
break;
case 10:
parser = new JsonFactory().createParser(bytes);
InfiniteTimeSeriesIndex.parseJson(parser);
break;
case 11:
parser = new JsonFactory().createParser(bytes);
IrregularTimeSeriesIndex.parseJson(parser);
break;
case 12:
parser = new JsonFactory().createParser(bytes);
RegularTimeSeriesIndex.parseJson(parser);
break;
case 13:
bais = new ByteArrayInputStream(bytes);
reader = new InputStreamReader(bais);
Project.parseJson(reader);
break;
case 14:
bais = new ByteArrayInputStream(bytes);
reader = new InputStreamReader(bais);
StudyCase.parseJson(reader);
break;
case 15:
parser = new JsonFactory().createParser(bytes);
NodeCalc.parseJson(parser);
break;
case 16:
str = new String(bytes, StandardCharsets.UTF_8);
TimeSeries.parseCsv(str);
break;
case 17:
File logFile = File.createTempFile("fuzz-", "-fuzz");
logFile.deleteOnExit();
try (FileOutputStream fos = new FileOutputStream(logFile)) {
fos.write(bytes);
new CsvLogParser().parse(logFile.toPath());
}
break;
case 18:
File timeFile = File.createTempFile("fuzz-", "-fuzz");
timeFile.deleteOnExit();
try (FileOutputStream fos = new FileOutputStream(timeFile)) {
fos.write(bytes);
}
new CsvTimeLineParser().parse(timeFile.toPath());
break;
case 19:
new Context().detectDelimiter(new String(bytes, StandardCharsets.UTF_8));
break;
}
} catch (PowsyblException
| IOException
| UncheckedIOException
| UncheckedXmlStreamException
| IllegalArgumentException
| IllegalStateException
| PowerFactoryException
| TextParsingException
| DateTimeParseException e) {
// Fuzzer: silently ignore
} catch (NullPointerException e) {
// Capture known NPE from malformed JSON
if (!isExpected(e)) {
throw e;
}
}
}
private static boolean isExpected(Throwable e) {
String[] expectedString = {
"java.util.Objects.requireNonNull",
"Cannot invoke \"String.hashCode()\"",
"Name is null",
"Cannot invoke \"com.fasterxml.jackson.databind.JsonNode.get(String)\""
};
for (String expected : expectedString) {
if (e.toString().contains(expected)) {
return true;
}
for (StackTraceElement ste : e.getStackTrace()) {
if (ste.toString().contains(expected)) {
return true;
}
}
}
return false;
}
}