RoundTripFuzzer.java
// Copyright 2023 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.
//
////////////////////////////////////////////////////////////////////////////////
package com.example;
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.github.wnameless.json.flattener.FlattenMode;
import com.github.wnameless.json.flattener.JsonFlattener;
import com.github.wnameless.json.flattener.JsonFlattenerFactory;
import com.github.wnameless.json.base.GsonJsonCore;
import com.github.wnameless.json.unflattener.JsonUnflattener;
import com.github.wnameless.json.unflattener.JsonUnflattenerFactory;
import java.util.function.Consumer;
/**
* Round-trip fuzzer: flatten ��� unflatten ��� flatten.
*
* Verifies that if a JSON object can be flattened and unflattened, the
* second flatten of the restored object produces a structurally equivalent
* result (same number of keys). Any key-count mismatch is surfaced as an
* AssertionError so OSS-Fuzz captures it as a reproducible finding.
*/
public class RoundTripFuzzer {
static FlattenMode[] flattenModes = {
FlattenMode.NORMAL,
FlattenMode.KEEP_ARRAYS,
FlattenMode.KEEP_PRIMITIVE_ARRAYS,
FlattenMode.MONGODB
};
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
FlattenMode mode = data.pickValue(flattenModes);
char separator = data.consumeChar();
String json = data.consumeRemainingAsString();
// Separators must be a safe, predictable non-alphanumeric char
if (Character.isLetterOrDigit(separator) || separator == '"' || separator == '\\') {
return;
}
try {
// Step 1: flatten
Consumer<JsonFlattener> flatCfg = jf -> jf.withFlattenMode(mode).withSeparator(separator);
JsonFlattenerFactory flatFactory = new JsonFlattenerFactory(flatCfg, new GsonJsonCore());
String flat1 = flatFactory.build(json).flatten();
// Step 2: unflatten back
Consumer<JsonUnflattener> unflatCfg = ju -> ju.withFlattenMode(mode).withSeparator(separator);
JsonUnflattenerFactory unflatFactory = new JsonUnflattenerFactory(unflatCfg, new GsonJsonCore());
String restored = unflatFactory.build(flat1).unflatten();
// Step 3: flatten again ��� must not throw and must be structurally equivalent
String flat2 = flatFactory.build(restored).flatten();
// Consistency assertion: key count must be identical
long keys1 = flat1.chars().filter(c -> c == ':').count();
long keys2 = flat2.chars().filter(c -> c == ':').count();
if (keys1 != keys2) {
throw new AssertionError(String.format(
"Round-trip key count mismatch (mode=%s sep='%c'): %d vs %d%ninput: %s%nflat1: %s%nrestored: %s%nflat2: %s",
mode, separator, keys1, keys2, json, flat1, restored, flat2));
}
} catch (AssertionError e) {
throw e; // surface to OSS-Fuzz as a crash
} catch (RuntimeException ignored) {
// invalid JSON or unsupported separator ��� expected, ignore
}
}
}