From 6ea835a4713d6e2eee67392cfd1b98b212f3cabb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gl=C3=A4=C3=9Fer?= Date: Wed, 1 Oct 2025 11:54:20 +0200 Subject: [PATCH 1/2] feat: add additional number annotations --- .../jazzer/mutation/annotation/Negative.java | 50 ++++++ .../mutation/annotation/NonNegative.java | 50 ++++++ .../mutation/annotation/NonPositive.java | 50 ++++++ .../jazzer/mutation/annotation/Positive.java | 50 ++++++ .../lang/FloatingPointMutatorFactory.java | 42 ++--- .../mutator/lang/IntegralMutatorFactory.java | 42 ++--- .../lang/PrimitiveArrayMutatorFactory.java | 65 ++++--- .../jazzer/mutation/support/RangeSupport.java | 159 ++++++++++++++++++ .../support/AnnotationSupportTest.java | 8 + .../mutation/support/TypeSupportTest.java | 34 +++- 10 files changed, 471 insertions(+), 79 deletions(-) create mode 100644 src/main/java/com/code_intelligence/jazzer/mutation/annotation/Negative.java create mode 100644 src/main/java/com/code_intelligence/jazzer/mutation/annotation/NonNegative.java create mode 100644 src/main/java/com/code_intelligence/jazzer/mutation/annotation/NonPositive.java create mode 100644 src/main/java/com/code_intelligence/jazzer/mutation/annotation/Positive.java create mode 100644 src/main/java/com/code_intelligence/jazzer/mutation/support/RangeSupport.java diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/annotation/Negative.java b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/Negative.java new file mode 100644 index 000000000..0cc5fe857 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/Negative.java @@ -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; +} diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/annotation/NonNegative.java b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/NonNegative.java new file mode 100644 index 000000000..128660261 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/NonNegative.java @@ -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; +} diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/annotation/NonPositive.java b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/NonPositive.java new file mode 100644 index 000000000..65da76ae3 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/NonPositive.java @@ -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; +} diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/annotation/Positive.java b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/Positive.java new file mode 100644 index 000000000..4a0695449 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/mutation/annotation/Positive.java @@ -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; +} diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorFactory.java index 356495d17..3474f299c 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorFactory.java @@ -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; @@ -109,18 +109,12 @@ static final class FloatMutator extends SerializingMutator { 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, @@ -389,18 +383,12 @@ static final class DoubleMutator extends SerializingMutator { 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), diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorFactory.java index 5f75cf5e3..69c3221dc 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorFactory.java @@ -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; @@ -199,33 +199,17 @@ abstract static class AbstractIntegralMutator 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, diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java index c21060fab..dd321c72d 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java @@ -23,8 +23,6 @@ import static com.code_intelligence.jazzer.mutation.support.TypeSupport.forwardAnnotations; import static com.code_intelligence.jazzer.mutation.support.TypeSupport.withLength; -import com.code_intelligence.jazzer.mutation.annotation.DoubleInRange; -import com.code_intelligence.jazzer.mutation.annotation.FloatInRange; import com.code_intelligence.jazzer.mutation.annotation.InRange; import com.code_intelligence.jazzer.mutation.annotation.WithLength; import com.code_intelligence.jazzer.mutation.api.Debuggable; @@ -33,6 +31,10 @@ import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.SerializingMutator; import com.code_intelligence.jazzer.mutation.mutator.libfuzzer.LibFuzzerMutatorFactory; +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 com.code_intelligence.jazzer.mutation.support.RangeSupport.LongRange; import com.code_intelligence.jazzer.mutation.support.TypeHolder; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -138,45 +140,64 @@ public T crossOver(T value, T otherValue, PseudoRandom prng) { private void extractRange(AnnotatedType type) { Optional inRange = Optional.ofNullable(type.getAnnotation(InRange.class)); - Optional inRangeFloat = - Optional.ofNullable(type.getAnnotation(FloatInRange.class)); - Optional inRangeDouble = - Optional.ofNullable(type.getAnnotation(DoubleInRange.class)); switch (type.getType().getTypeName()) { case "int": - minRange = inRange.map(InRange::min).orElse((long) Integer.MIN_VALUE); - maxRange = inRange.map(InRange::max).orElse((long) Integer.MAX_VALUE); + { + LongRange r = + RangeSupport.resolveIntegralRange(type, Integer.MIN_VALUE, Integer.MAX_VALUE); + minRange = r.min; + maxRange = r.max; + } break; case "long": - minRange = inRange.map(InRange::min).orElse(Long.MIN_VALUE); - maxRange = inRange.map(InRange::max).orElse(Long.MAX_VALUE); + { + LongRange r = RangeSupport.resolveIntegralRange(type, Long.MIN_VALUE, Long.MAX_VALUE); + minRange = r.min; + maxRange = r.max; + } break; case "short": - minRange = inRange.map(InRange::min).orElse((long) Short.MIN_VALUE); - maxRange = inRange.map(InRange::max).orElse((long) Short.MAX_VALUE); + { + LongRange r = RangeSupport.resolveIntegralRange(type, Short.MIN_VALUE, Short.MAX_VALUE); + minRange = r.min; + maxRange = r.max; + } break; case "char": minRange = inRange.map(InRange::min).orElse((long) Character.MIN_VALUE); maxRange = inRange.map(InRange::max).orElse((long) Character.MAX_VALUE); break; case "float": - minFloatRange = inRangeFloat.map(FloatInRange::min).orElse(Float.NEGATIVE_INFINITY); - maxFloatRange = inRangeFloat.map(FloatInRange::max).orElse(Float.POSITIVE_INFINITY); - allowNaN = inRangeFloat.map(FloatInRange::allowNaN).orElse(false); + { + FloatRange r = + RangeSupport.resolveFloatRange( + type, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, false); + minFloatRange = r.min; + maxFloatRange = r.max; + allowNaN = r.allowNaN; + } break; case "double": - minDoubleRange = inRangeDouble.map(DoubleInRange::min).orElse(Double.NEGATIVE_INFINITY); - maxDoubleRange = inRangeDouble.map(DoubleInRange::max).orElse(Double.POSITIVE_INFINITY); - allowNaN = inRangeDouble.map(DoubleInRange::allowNaN).orElse(false); + { + DoubleRange r = + RangeSupport.resolveDoubleRange( + type, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, false); + minDoubleRange = r.min; + maxDoubleRange = r.max; + allowNaN = r.allowNaN; + } break; case "boolean": - minRange = inRange.map(InRange::min).orElse(0L); - maxRange = inRange.map(InRange::max).orElse(1L); + minRange = 0L; + maxRange = 1L; break; case "byte": - minRange = inRange.map(InRange::min).orElse((long) Byte.MIN_VALUE); - maxRange = inRange.map(InRange::max).orElse((long) Byte.MAX_VALUE); + { + LongRange r = RangeSupport.resolveIntegralRange(type, Byte.MIN_VALUE, Byte.MAX_VALUE); + minRange = r.min; + maxRange = r.max; + } break; default: throw new IllegalStateException("Unexpected type: " + type); diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/support/RangeSupport.java b/src/main/java/com/code_intelligence/jazzer/mutation/support/RangeSupport.java new file mode 100644 index 000000000..27a01c84a --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/mutation/support/RangeSupport.java @@ -0,0 +1,159 @@ +/* + * 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.support; + +import com.code_intelligence.jazzer.mutation.annotation.*; +import com.code_intelligence.jazzer.mutation.annotation.Negative; +import com.code_intelligence.jazzer.mutation.annotation.NonNegative; +import com.code_intelligence.jazzer.mutation.annotation.NonPositive; +import com.code_intelligence.jazzer.mutation.annotation.Positive; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedType; + +/** + * Utilities to derive range constraints from annotations. + * + *

Centralizes mapping of convenience property annotations (e.g. @Positive) to concrete range + * values. + */ +public final class RangeSupport { + private RangeSupport() {} + + public static final class LongRange { + public final long min; + public final long max; + + public LongRange(long min, long max) { + this.min = min; + this.max = max; + } + } + + public static final class FloatRange { + public final float min; + public final float max; + public final boolean allowNaN; + + public FloatRange(float min, float max, boolean allowNaN) { + this.min = min; + this.max = max; + this.allowNaN = allowNaN; + } + } + + public static final class DoubleRange { + public final double min; + public final double max; + public final boolean allowNaN; + + public DoubleRange(double min, double max, boolean allowNaN) { + this.min = min; + this.max = max; + this.allowNaN = allowNaN; + } + } + + public static LongRange resolveIntegralRange( + AnnotatedType type, long defaultMin, long defaultMax) { + long minValue = defaultMin; + long maxValue = defaultMax; + for (Annotation annotation : type.getAnnotations()) { + if (annotation instanceof InRange) { + InRange inRange = (InRange) annotation; + minValue = Math.max(inRange.min(), defaultMin); + maxValue = Math.min(inRange.max(), defaultMax); + } else if (annotation instanceof Positive) { + minValue = 1; + maxValue = defaultMax; + } else if (annotation instanceof Negative) { + minValue = defaultMin; + maxValue = -1; + } else if (annotation instanceof NonNegative) { + minValue = 0; + maxValue = defaultMax; + } else if (annotation instanceof NonPositive) { + minValue = defaultMin; + maxValue = 0; + } + } + return new LongRange(minValue, maxValue); + } + + public static FloatRange resolveFloatRange( + AnnotatedType type, float defaultMin, float defaultMax, boolean defaultAllowNaN) { + float minValue = defaultMin; + float maxValue = defaultMax; + boolean allowNaN = defaultAllowNaN; + for (Annotation annotation : type.getAnnotations()) { + if (annotation instanceof FloatInRange) { + FloatInRange floatInRange = (FloatInRange) annotation; + minValue = floatInRange.min(); + maxValue = floatInRange.max(); + allowNaN = floatInRange.allowNaN(); + } else if (annotation instanceof Positive) { + minValue = Float.MIN_VALUE; + maxValue = defaultMax; + allowNaN = false; + } else if (annotation instanceof Negative) { + minValue = defaultMin; + maxValue = -Float.MIN_VALUE; + allowNaN = false; + } else if (annotation instanceof NonNegative) { + minValue = 0.0f; + maxValue = defaultMax; + allowNaN = false; + } else if (annotation instanceof NonPositive) { + minValue = defaultMin; + maxValue = 0.0f; + allowNaN = false; + } + } + return new FloatRange(minValue, maxValue, allowNaN); + } + + public static DoubleRange resolveDoubleRange( + AnnotatedType type, double defaultMin, double defaultMax, boolean defaultAllowNaN) { + double minValue = defaultMin; + double maxValue = defaultMax; + boolean allowNaN = defaultAllowNaN; + for (Annotation annotation : type.getAnnotations()) { + if (annotation instanceof DoubleInRange) { + DoubleInRange doubleInRange = (DoubleInRange) annotation; + minValue = doubleInRange.min(); + maxValue = doubleInRange.max(); + allowNaN = doubleInRange.allowNaN(); + } else if (annotation instanceof Positive) { + minValue = Double.MIN_VALUE; + maxValue = defaultMax; + allowNaN = false; + } else if (annotation instanceof Negative) { + minValue = defaultMin; + maxValue = -Double.MIN_VALUE; + allowNaN = false; + } else if (annotation instanceof NonNegative) { + minValue = 0.0; + maxValue = defaultMax; + allowNaN = false; + } else if (annotation instanceof NonPositive) { + minValue = defaultMin; + maxValue = 0.0; + allowNaN = false; + } + } + return new DoubleRange(minValue, maxValue, allowNaN); + } +} diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/support/AnnotationSupportTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/support/AnnotationSupportTest.java index 7731642b2..3972b95ba 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/support/AnnotationSupportTest.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/support/AnnotationSupportTest.java @@ -24,7 +24,11 @@ import com.code_intelligence.jazzer.mutation.annotation.DoubleInRange; import com.code_intelligence.jazzer.mutation.annotation.FloatInRange; import com.code_intelligence.jazzer.mutation.annotation.InRange; +import com.code_intelligence.jazzer.mutation.annotation.Negative; +import com.code_intelligence.jazzer.mutation.annotation.NonNegative; +import com.code_intelligence.jazzer.mutation.annotation.NonPositive; import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.Positive; import com.code_intelligence.jazzer.mutation.annotation.UrlSegment; import com.code_intelligence.jazzer.mutation.annotation.WithLength; import com.code_intelligence.jazzer.mutation.annotation.WithSize; @@ -85,6 +89,10 @@ static Stream validateAnnotationUsageCases_throw() { arguments(new TypeHolder() {}.annotatedType()), arguments(new TypeHolder<@UrlSegment Integer>() {}.annotatedType()), arguments(new TypeHolder<@UrlSegment List>() {}.annotatedType()), + arguments(new TypeHolder<@Positive String>() {}.annotatedType()), + arguments(new TypeHolder<@Negative String>() {}.annotatedType()), + arguments(new TypeHolder<@NonNegative String>() {}.annotatedType()), + arguments(new TypeHolder<@NonPositive String>() {}.annotatedType()), // deep arguments( new TypeHolder< diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/support/TypeSupportTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/support/TypeSupportTest.java index 50944761f..92216a97a 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/support/TypeSupportTest.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/support/TypeSupportTest.java @@ -31,7 +31,11 @@ import com.code_intelligence.jazzer.mutation.annotation.DoubleInRange; import com.code_intelligence.jazzer.mutation.annotation.FloatInRange; import com.code_intelligence.jazzer.mutation.annotation.InRange; +import com.code_intelligence.jazzer.mutation.annotation.Negative; +import com.code_intelligence.jazzer.mutation.annotation.NonNegative; +import com.code_intelligence.jazzer.mutation.annotation.NonPositive; import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.Positive; import com.code_intelligence.jazzer.mutation.annotation.WithLength; import com.code_intelligence.jazzer.mutation.annotation.WithSize; import com.code_intelligence.jazzer.mutation.annotation.WithUtf8Length; @@ -444,7 +448,35 @@ static Stream forwardAnnotationCases() { arguments( new TypeHolder<@NotNull @InRange(min = 10) Integer>() {}.annotatedType(), new TypeHolder<@InRange(min = 20) Integer>() {}.annotatedType(), - new TypeHolder<@InRange(min = 20) @NotNull Integer>() {}.annotatedType())); + new TypeHolder<@InRange(min = 20) @NotNull Integer>() {}.annotatedType()), + arguments( + new TypeHolder<@Positive Float>() {}.annotatedType(), + new TypeHolder<@NotNull Float>() {}.annotatedType(), + new TypeHolder<@NotNull @Positive Float>() {}.annotatedType()), + arguments( + new TypeHolder<@Negative Float>() {}.annotatedType(), + new TypeHolder<@NotNull Float>() {}.annotatedType(), + new TypeHolder<@NotNull @Negative Float>() {}.annotatedType()), + arguments( + new TypeHolder<@NonPositive Float>() {}.annotatedType(), + new TypeHolder<@NotNull Float>() {}.annotatedType(), + new TypeHolder<@NotNull @NonPositive Float>() {}.annotatedType()), + arguments( + new TypeHolder<@NonNegative Float>() {}.annotatedType(), + new TypeHolder<@NotNull Float>() {}.annotatedType(), + new TypeHolder<@NotNull @NonNegative Float>() {}.annotatedType()), + arguments( + new TypeHolder<@Negative Double>() {}.annotatedType(), + new TypeHolder<@NotNull Double>() {}.annotatedType(), + new TypeHolder<@NotNull @Negative Double>() {}.annotatedType()), + arguments( + new TypeHolder<@NonPositive Double>() {}.annotatedType(), + new TypeHolder<@NotNull Double>() {}.annotatedType(), + new TypeHolder<@NotNull @NonPositive Double>() {}.annotatedType()), + arguments( + new TypeHolder<@NonNegative Double>() {}.annotatedType(), + new TypeHolder<@NotNull Double>() {}.annotatedType(), + new TypeHolder<@NotNull @NonNegative Double>() {}.annotatedType())); } @ParameterizedTest From b976ed99ba4a0cbd7f8451add65074630b867662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gl=C3=A4=C3=9Fer?= Date: Thu, 9 Oct 2025 14:42:17 +0200 Subject: [PATCH 2/2] chore: include new annotations in docs --- docs/mutation-framework.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/mutation-framework.md b/docs/mutation-framework.md index 76a5b8fed..65fc207c7 100644 --- a/docs/mutation-framework.md +++ b/docs/mutation-framework.md @@ -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.