Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions docs/mutation-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,21 @@ string. This is done using annotations directly on the parameters.
All annotations reside in the `com.code_intelligence.jazzer.mutation.annotation`
package.

| Annotation | Applies To | Notes |
|-------------------|----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------|
| `@Ascii` | `java.lang.String` | `String` should only contain ASCII characters |
| `@InRange` | `byte`, `Byte`, `short`, `Short`, `int`, `Int`, `long`, `Long` | Specifies `min` and `max` values of generated integrals |
| `@FloatInRange` | `float`, `Float` | Specifies `min` and `max` values of generated floats |
| `@DoubleInRange` | `double`, `Double` | Specifies `min` and `max` values of generated doubles |
| `@NotNull` | | Specifies that a reference type should not be `null` |
| `@WithLength` | `byte[]` | Specifies the length of the generated byte array |
| `@WithUtf8Length` | `java.lang.String` | Specifies the length of the generated string in UTF-8 bytes, see annotation Javadoc for further information |
| `@WithSize` | `java.util.List`, `java.util.Map` | Specifies the size of the generated collection |
| `@UrlSegment` | `java.lang.String` | `String` should only contain valid URL segment characters |
| Annotation | Applies To | Notes |
|-------------------|------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------|
| `@Ascii` | `java.lang.String` | `String` should only contain ASCII characters |
| `@InRange` | `byte`, `Byte`, `short`, `Short`, `int`, `Int`, `long`, `Long` | Specifies `min` and `max` values of generated integrals |
| `@FloatInRange` | `float`, `Float` | Specifies `min` and `max` values of generated floats |
| `@DoubleInRange` | `double`, `Double` | Specifies `min` and `max` values of generated doubles |
| `Positive` | `byte`, `Byte`, `short`, `Short`, `int`, `Int`, `long`, `Long`, `float`, `Float`, `double`, `Double` | Specifies that only positive values are generated |
| `Negative` | `byte`, `Byte`, `short`, `Short`, `int`, `Int`, `long`, `Long`, `float`, `Float`, `double`, `Double` | Specifies that only negative values are generated |
| `NonPositive` | `byte`, `Byte`, `short`, `Short`, `int`, `Int`, `long`, `Long`, `float`, `Float`, `double`, `Double` | Specifies that only non-positive values are generated |
| `NonNegative` | `byte`, `Byte`, `short`, `Short`, `int`, `Int`, `long`, `Long`, `float`, `Float`, `double`, `Double` | Specifies that only non-negative values are generated |
| `@NotNull` | | Specifies that a reference type should not be `null` |
| `@WithLength` | `byte[]` | Specifies the length of the generated byte array |
| `@WithUtf8Length` | `java.lang.String` | Specifies the length of the generated string in UTF-8 bytes, see annotation Javadoc for further information |
| `@WithSize` | `java.util.List`, `java.util.Map` | Specifies the size of the generated collection |
| `@UrlSegment` | `java.lang.String` | `String` should only contain valid URL segment characters |

The example below shows how Fuzz Test parameters can be annotated to provide
additional information to the mutation framework.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2024 Code Intelligence GmbH
*
* 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.code_intelligence.jazzer.mutation.annotation;

import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import com.code_intelligence.jazzer.mutation.utils.AppliesTo;
import com.code_intelligence.jazzer.mutation.utils.PropertyConstraint;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target(TYPE_USE)
@Retention(RUNTIME)
@AppliesTo({
byte.class,
Byte.class,
short.class,
Short.class,
int.class,
Integer.class,
long.class,
Long.class,
float.class,
Float.class,
double.class,
Double.class
})
@PropertyConstraint
public @interface Negative {
/**
* Defines the scope of the annotation. Possible values are defined in {@link
* com.code_intelligence.jazzer.mutation.utils.PropertyConstraint}.
*/
String constraint() default PropertyConstraint.DECLARATION;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2024 Code Intelligence GmbH
*
* 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.code_intelligence.jazzer.mutation.annotation;

import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import com.code_intelligence.jazzer.mutation.utils.AppliesTo;
import com.code_intelligence.jazzer.mutation.utils.PropertyConstraint;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target(TYPE_USE)
@Retention(RUNTIME)
@AppliesTo({
byte.class,
Byte.class,
short.class,
Short.class,
int.class,
Integer.class,
long.class,
Long.class,
float.class,
Float.class,
double.class,
Double.class
})
@PropertyConstraint
public @interface NonNegative {
/**
* Defines the scope of the annotation. Possible values are defined in {@link
* com.code_intelligence.jazzer.mutation.utils.PropertyConstraint}.
*/
String constraint() default PropertyConstraint.DECLARATION;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2024 Code Intelligence GmbH
*
* 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.code_intelligence.jazzer.mutation.annotation;

import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import com.code_intelligence.jazzer.mutation.utils.AppliesTo;
import com.code_intelligence.jazzer.mutation.utils.PropertyConstraint;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target(TYPE_USE)
@Retention(RUNTIME)
@AppliesTo({
byte.class,
Byte.class,
short.class,
Short.class,
int.class,
Integer.class,
long.class,
Long.class,
float.class,
Float.class,
double.class,
Double.class
})
@PropertyConstraint
public @interface NonPositive {
/**
* Defines the scope of the annotation. Possible values are defined in {@link
* com.code_intelligence.jazzer.mutation.utils.PropertyConstraint}.
*/
String constraint() default PropertyConstraint.DECLARATION;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2024 Code Intelligence GmbH
*
* 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.code_intelligence.jazzer.mutation.annotation;

import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import com.code_intelligence.jazzer.mutation.utils.AppliesTo;
import com.code_intelligence.jazzer.mutation.utils.PropertyConstraint;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target(TYPE_USE)
@Retention(RUNTIME)
@AppliesTo({
byte.class,
Byte.class,
short.class,
Short.class,
int.class,
Integer.class,
long.class,
Long.class,
float.class,
Float.class,
double.class,
Double.class
})
@PropertyConstraint
public @interface Positive {
/**
* Defines the scope of the annotation. Possible values are defined in {@link
* com.code_intelligence.jazzer.mutation.utils.PropertyConstraint}.
*/
String constraint() default PropertyConstraint.DECLARATION;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@
import static com.code_intelligence.jazzer.mutation.support.Preconditions.require;
import static java.lang.String.format;

import com.code_intelligence.jazzer.mutation.annotation.DoubleInRange;
import com.code_intelligence.jazzer.mutation.annotation.FloatInRange;
import com.code_intelligence.jazzer.mutation.api.Debuggable;
import com.code_intelligence.jazzer.mutation.api.ExtendedMutatorFactory;
import com.code_intelligence.jazzer.mutation.api.MutatorFactory;
import com.code_intelligence.jazzer.mutation.api.PseudoRandom;
import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
import com.code_intelligence.jazzer.mutation.mutator.libfuzzer.LibFuzzerMutate;
import com.code_intelligence.jazzer.mutation.support.RangeSupport;
import com.code_intelligence.jazzer.mutation.support.RangeSupport.DoubleRange;
import com.code_intelligence.jazzer.mutation.support.RangeSupport.FloatRange;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -109,18 +109,12 @@ static final class FloatMutator extends SerializingMutator<Float> {
float defaultMinValueForType,
float defaultMaxValueForType,
boolean defaultAllowNaN) {
float minValue = defaultMinValueForType;
float maxValue = defaultMaxValueForType;
boolean allowNaN = defaultAllowNaN;
// InRange is not repeatable, so the loop body will apply at most once.
for (Annotation annotation : type.getAnnotations()) {
if (annotation instanceof FloatInRange) {
FloatInRange floatInRange = (FloatInRange) annotation;
minValue = floatInRange.min();
maxValue = floatInRange.max();
allowNaN = floatInRange.allowNaN();
}
}
FloatRange resolved =
RangeSupport.resolveFloatRange(
type, defaultMinValueForType, defaultMaxValueForType, defaultAllowNaN);
float minValue = resolved.min;
float maxValue = resolved.max;
boolean allowNaN = resolved.allowNaN;

require(
minValue <= maxValue,
Expand Down Expand Up @@ -389,18 +383,12 @@ static final class DoubleMutator extends SerializingMutator<Double> {
double defaultMinValueForType,
double defaultMaxValueForType,
boolean defaultAllowNaN) {
double minValue = defaultMinValueForType;
double maxValue = defaultMaxValueForType;
boolean allowNaN = defaultAllowNaN;
// InRange is not repeatable, so the loop body will apply at most once.
for (Annotation annotation : type.getAnnotations()) {
if (annotation instanceof DoubleInRange) {
DoubleInRange doubleInRange = (DoubleInRange) annotation;
minValue = doubleInRange.min();
maxValue = doubleInRange.max();
allowNaN = doubleInRange.allowNaN();
}
}
DoubleRange resolved =
RangeSupport.resolveDoubleRange(
type, defaultMinValueForType, defaultMaxValueForType, defaultAllowNaN);
double minValue = resolved.min;
double maxValue = resolved.max;
boolean allowNaN = resolved.allowNaN;

require(
!Double.isNaN(minValue) && !Double.isNaN(maxValue),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@
import static com.code_intelligence.jazzer.mutation.support.Preconditions.require;
import static java.lang.String.format;

import com.code_intelligence.jazzer.mutation.annotation.InRange;
import com.code_intelligence.jazzer.mutation.api.Debuggable;
import com.code_intelligence.jazzer.mutation.api.ExtendedMutatorFactory;
import com.code_intelligence.jazzer.mutation.api.MutatorFactory;
import com.code_intelligence.jazzer.mutation.api.PseudoRandom;
import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
import com.code_intelligence.jazzer.mutation.mutator.libfuzzer.LibFuzzerMutate;
import com.code_intelligence.jazzer.mutation.support.RangeSupport;
import com.code_intelligence.jazzer.mutation.support.RangeSupport.LongRange;
import com.google.errorprone.annotations.ForOverride;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.ParameterizedType;
import java.util.Optional;
Expand Down Expand Up @@ -199,33 +199,17 @@ abstract static class AbstractIntegralMutator<T extends Number> extends Serializ

AbstractIntegralMutator(
AnnotatedType type, long defaultMinValueForType, long defaultMaxValueForType) {
long minValue = defaultMinValueForType;
long maxValue = defaultMaxValueForType;
// InRange is not repeatable, so the loop body will apply exactly once.
for (Annotation annotation : type.getAnnotations()) {
if (annotation instanceof InRange) {
InRange inRange = (InRange) annotation;
// Since we use a single annotation for all integral types and its min and max fields are
// longs, we have to ignore them if they are at their default values.
//
// This results in a small quirk that is probably acceptable: If someone specifies
// @InRange(max = Long.MAX_VALUE) on a byte, we will not fail but silently use
// Byte.MAX_VALUE instead. IDEs will warn about the redundant specification of the default
// value, so this should not be a problem in practice.
if (inRange.min() != Long.MIN_VALUE) {
require(
inRange.min() >= defaultMinValueForType,
format("@InRange.min=%d is out of range: %s", inRange.min(), type.getType()));
minValue = inRange.min();
}
if (inRange.max() != Long.MAX_VALUE) {
require(
inRange.max() <= defaultMaxValueForType,
format("@InRange.max=%d is out of range: %s", inRange.max(), type.getType()));
maxValue = inRange.max();
}
}
}
LongRange resolved =
RangeSupport.resolveIntegralRange(type, defaultMinValueForType, defaultMaxValueForType);
long minValue = resolved.min;
long maxValue = resolved.max;
// Ensure range does not specify values outside the type bounds.
require(
minValue >= defaultMinValueForType,
format("@InRange.min=%d is out of range: %s", minValue, type.getType()));
require(
maxValue <= defaultMaxValueForType,
format("@InRange.max=%d is out of range: %s", maxValue, type.getType()));

require(
minValue <= maxValue,
Expand Down
Loading