From f0a5776cfbd27c8814cb3d3fc6efe45ccf7b3db0 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 11 Oct 2024 14:25:42 +0200 Subject: [PATCH 1/3] Replace SecureRandom with vendored Random --- .../android/core/AnrV2EventProcessor.java | 8 +- .../android/replay/ReplayIntegration.kt | 4 +- .../replay/capture/BufferCaptureStrategy.kt | 4 +- .../io/sentry/android/replay/util/Sampling.kt | 4 +- .../capture/BufferCaptureStrategyTest.kt | 4 +- sentry/api/sentry.api | 13 + .../src/main/java/io/sentry/SentryClient.java | 6 +- .../main/java/io/sentry/TracesSampler.java | 8 +- .../java/io/sentry/metrics/MetricsHelper.java | 4 +- .../src/main/java/io/sentry/util/Random.java | 466 ++++++++++++++++++ .../test/java/io/sentry/TracesSamplerTest.kt | 4 +- 11 files changed, 502 insertions(+), 23 deletions(-) create mode 100644 sentry/src/main/java/io/sentry/util/Random.java diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2EventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2EventProcessor.java index d58d04b7f81..0399b634fb3 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2EventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AnrV2EventProcessor.java @@ -54,8 +54,8 @@ import io.sentry.protocol.SentryTransaction; import io.sentry.protocol.User; import io.sentry.util.HintUtils; +import io.sentry.util.Random; import java.io.File; -import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -83,7 +83,7 @@ public final class AnrV2EventProcessor implements BackfillingEventProcessor { private final @NotNull SentryExceptionFactory sentryExceptionFactory; - private final @Nullable SecureRandom random; + private final @Nullable Random random; public AnrV2EventProcessor( final @NotNull Context context, @@ -96,7 +96,7 @@ public AnrV2EventProcessor( final @NotNull Context context, final @NotNull SentryAndroidOptions options, final @NotNull BuildInfoProvider buildInfoProvider, - final @Nullable SecureRandom random) { + final @Nullable Random random) { this.context = ContextUtils.getApplicationContext(context); this.options = options; this.buildInfoProvider = buildInfoProvider; @@ -180,7 +180,7 @@ private boolean sampleReplay(final @NotNull SentryEvent event) { try { // we have to sample here with the old sample rate, because it may change between app launches - final @NotNull SecureRandom random = this.random != null ? this.random : new SecureRandom(); + final @NotNull Random random = this.random != null ? this.random : new Random(); final double replayErrorSampleRateDouble = Double.parseDouble(replayErrorSampleRate); if (replayErrorSampleRateDouble < random.nextDouble()) { options diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt index 82d20cf3c11..ee9223cc80c 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt @@ -35,9 +35,9 @@ import io.sentry.transport.ICurrentDateProvider import io.sentry.util.FileUtils import io.sentry.util.HintUtils import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion +import io.sentry.util.Random import java.io.Closeable import java.io.File -import java.security.SecureRandom import java.util.LinkedList import java.util.concurrent.atomic.AtomicBoolean import kotlin.LazyThreadSafetyMode.NONE @@ -78,7 +78,7 @@ public class ReplayIntegration( private var hub: IHub? = null private var recorder: Recorder? = null private var gestureRecorder: GestureRecorder? = null - private val random by lazy { SecureRandom() } + private val random by lazy { Random() } private val rootViewsSpy by lazy(NONE) { RootViewsSpy.install() } // TODO: probably not everything has to be thread-safe here diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt index 247888f47d7..2dbb2a17464 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/capture/BufferCaptureStrategy.kt @@ -18,8 +18,8 @@ import io.sentry.android.replay.util.submitSafely import io.sentry.protocol.SentryId import io.sentry.transport.ICurrentDateProvider import io.sentry.util.FileUtils +import io.sentry.util.Random import java.io.File -import java.security.SecureRandom import java.util.Date import java.util.concurrent.ScheduledExecutorService @@ -27,7 +27,7 @@ internal class BufferCaptureStrategy( private val options: SentryOptions, private val hub: IHub?, private val dateProvider: ICurrentDateProvider, - private val random: SecureRandom, + private val random: Random, executor: ScheduledExecutorService? = null, replayCacheProvider: ((replayId: SentryId, recorderConfig: ScreenshotRecorderConfig) -> ReplayCache)? = null ) : BaseCaptureStrategy(options, hub, dateProvider, executor = executor, replayCacheProvider = replayCacheProvider) { diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Sampling.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Sampling.kt index 8acb6b00a6e..5ec46ea9625 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Sampling.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Sampling.kt @@ -1,8 +1,8 @@ package io.sentry.android.replay.util -import java.security.SecureRandom +import io.sentry.util.Random -internal fun SecureRandom.sample(rate: Double?): Boolean { +internal fun Random.sample(rate: Double?): Boolean { if (rate != null) { return !(rate < this.nextDouble()) // bad luck } diff --git a/sentry-android-replay/src/test/java/io/sentry/android/replay/capture/BufferCaptureStrategyTest.kt b/sentry-android-replay/src/test/java/io/sentry/android/replay/capture/BufferCaptureStrategyTest.kt index 337ba525fc4..625306cb8e4 100644 --- a/sentry-android-replay/src/test/java/io/sentry/android/replay/capture/BufferCaptureStrategyTest.kt +++ b/sentry-android-replay/src/test/java/io/sentry/android/replay/capture/BufferCaptureStrategyTest.kt @@ -20,6 +20,7 @@ import io.sentry.android.replay.capture.BufferCaptureStrategyTest.Fixture.Compan import io.sentry.protocol.SentryId import io.sentry.transport.CurrentDateProvider import io.sentry.transport.ICurrentDateProvider +import io.sentry.util.Random import org.awaitility.kotlin.await import org.junit.Rule import org.junit.rules.TemporaryFolder @@ -35,7 +36,6 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import java.io.File -import java.security.SecureRandom import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -97,7 +97,7 @@ class BufferCaptureStrategyTest { options, hub, dateProvider, - SecureRandom(), + Random(), mock { doAnswer { invocation -> (invocation.arguments[0] as Runnable).run() diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index ac1eb2bc8a1..89684c7ae55 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -5808,6 +5808,19 @@ public final class io/sentry/util/PropagationTargetsUtils { public static fun contain (Ljava/util/List;Ljava/net/URI;)Z } +public final class io/sentry/util/Random : java/io/Serializable { + public fun ()V + public fun (J)V + public fun nextBoolean ()Z + public fun nextBytes ([B)V + public fun nextDouble ()D + public fun nextFloat ()F + public fun nextInt ()I + public fun nextInt (I)I + public fun nextLong ()J + public fun setSeed (J)V +} + public final class io/sentry/util/SampleRateUtils { public fun ()V public static fun isValidProfilesSampleRate (Ljava/lang/Double;)Z diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 6868894340a..27529d100b2 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -17,10 +17,10 @@ import io.sentry.util.CheckInUtils; import io.sentry.util.HintUtils; import io.sentry.util.Objects; +import io.sentry.util.Random; import io.sentry.util.TracingUtils; import java.io.Closeable; import java.io.IOException; -import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -40,7 +40,7 @@ public final class SentryClient implements ISentryClient, IMetricsClient { private final @NotNull SentryOptions options; private final @NotNull ITransport transport; - private final @Nullable SecureRandom random; + private final @Nullable Random random; private final @NotNull SortBreadcrumbsByDate sortBreadcrumbsByDate = new SortBreadcrumbsByDate(); private final @NotNull IMetricsAggregator metricsAggregator; @@ -67,7 +67,7 @@ public boolean isEnabled() { ? new MetricsAggregator(options, this) : NoopMetricsAggregator.getInstance(); - this.random = options.getSampleRate() == null ? null : new SecureRandom(); + this.random = options.getSampleRate() == null ? null : new Random(); } private boolean shouldApplyScopeData( diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index e0ce111037a..5e5b808333e 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -1,7 +1,7 @@ package io.sentry; import io.sentry.util.Objects; -import java.security.SecureRandom; +import io.sentry.util.Random; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -10,14 +10,14 @@ final class TracesSampler { private static final @NotNull Double DEFAULT_TRACES_SAMPLE_RATE = 1.0; private final @NotNull SentryOptions options; - private final @NotNull SecureRandom random; + private final @NotNull Random random; public TracesSampler(final @NotNull SentryOptions options) { - this(Objects.requireNonNull(options, "options are required"), new SecureRandom()); + this(Objects.requireNonNull(options, "options are required"), new Random()); } @TestOnly - TracesSampler(final @NotNull SentryOptions options, final @NotNull SecureRandom random) { + TracesSampler(final @NotNull SentryOptions options, final @NotNull Random random) { this.options = options; this.random = random; } diff --git a/sentry/src/main/java/io/sentry/metrics/MetricsHelper.java b/sentry/src/main/java/io/sentry/metrics/MetricsHelper.java index c4353c53a36..37fd4523a4d 100644 --- a/sentry/src/main/java/io/sentry/metrics/MetricsHelper.java +++ b/sentry/src/main/java/io/sentry/metrics/MetricsHelper.java @@ -1,7 +1,7 @@ package io.sentry.metrics; import io.sentry.MeasurementUnit; -import java.security.SecureRandom; +import io.sentry.util.Random; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -27,7 +27,7 @@ public final class MetricsHelper { private static final char TAGS_ESCAPE_CHAR = '\\'; private static long FLUSH_SHIFT_MS = - (long) (new SecureRandom().nextFloat() * (ROLLUP_IN_SECONDS * 1000f)); + (long) (new Random().nextFloat() * (ROLLUP_IN_SECONDS * 1000f)); public static long getTimeBucketKey(final long timestampMs) { final long seconds = timestampMs / 1000; diff --git a/sentry/src/main/java/io/sentry/util/Random.java b/sentry/src/main/java/io/sentry/util/Random.java new file mode 100644 index 00000000000..cbd81824df1 --- /dev/null +++ b/sentry/src/main/java/io/sentry/util/Random.java @@ -0,0 +1,466 @@ +/* + * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package io.sentry.util; + +import java.util.concurrent.atomic.AtomicLong; +import org.jetbrains.annotations.ApiStatus; + +/** + * A simplified version of {@link java.util.Random} that we use for sampling, which is much faster + * than {@link java.security.SecureRandom}. This is necessary so that some security tools do not + * flag our Random usage as potentially insecure. + */ +@ApiStatus.Internal +public final class Random implements java.io.Serializable { + /** use serialVersionUID from JDK 1.1 for interoperability */ + private static final long serialVersionUID = 3905348978240129619L; + + /** + * The internal state associated with this pseudorandom number generator. (The specs for the + * methods in this class describe the ongoing computation of this value.) + */ + private final AtomicLong seed; + + private static final long multiplier = 0x5DEECE66DL; + private static final long addend = 0xBL; + private static final long mask = (1L << 48) - 1; + + private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53) + + // IllegalArgumentException messages + static final String BadBound = "bound must be positive"; + + /** + * Creates a new random number generator. This constructor sets the seed of the random number + * generator to a value very likely to be distinct from any other invocation of this constructor. + */ + public Random() { + this(seedUniquifier() ^ System.nanoTime()); + } + + private static long seedUniquifier() { + // L'Ecuyer, "Tables of Linear Congruential Generators of + // Different Sizes and Good Lattice Structure", 1999 + for (; ; ) { + long current = seedUniquifier.get(); + long next = current * 1181783497276652981L; + if (seedUniquifier.compareAndSet(current, next)) return next; + } + } + + private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L); + + /** + * Creates a new random number generator using a single {@code long} seed. The seed is the initial + * value of the internal state of the pseudorandom number generator which is maintained by method + * {@link #next}. + * + *

The invocation {@code new Random(seed)} is equivalent to: + * + *

{@code
+   * Random rnd = new Random();
+   * rnd.setSeed(seed);
+   * }
+ * + * @param seed the initial seed + * @see #setSeed(long) + */ + public Random(long seed) { + if (getClass() == Random.class) this.seed = new AtomicLong(initialScramble(seed)); + else { + // subclass might have overriden setSeed + this.seed = new AtomicLong(); + setSeed(seed); + } + } + + private static long initialScramble(long seed) { + return (seed ^ multiplier) & mask; + } + + /** + * Sets the seed of this random number generator using a single {@code long} seed. The general + * contract of {@code setSeed} is that it alters the state of this random number generator object + * so as to be in exactly the same state as if it had just been created with the argument {@code + * seed} as a seed. The method {@code setSeed} is implemented by class {@code Random} by + * atomically updating the seed to + * + *
{@code (seed ^ 0x5DEECE66DL) & ((1L << 48) - 1)}
+ * + *

The implementation of {@code setSeed} by class {@code Random} happens to use only 48 bits of + * the given seed. In general, however, an overriding method may use all 64 bits of the {@code + * long} argument as a seed value. + * + * @param seed the initial seed + */ + public synchronized void setSeed(long seed) { + this.seed.set(initialScramble(seed)); + } + + /** + * Generates the next pseudorandom number. Subclasses should override this, as this is used by all + * other methods. + * + *

The general contract of {@code next} is that it returns an {@code int} value and if the + * argument {@code bits} is between {@code 1} and {@code 32} (inclusive), then that many low-order + * bits of the returned value will be (approximately) independently chosen bit values, each of + * which is (approximately) equally likely to be {@code 0} or {@code 1}. The method {@code next} + * is implemented by class {@code Random} by atomically updating the seed to + * + *

{@code (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)}
+ * + * and returning + * + *
{@code (int)(seed >>> (48 - bits))}.
+ * + * This is a linear congruential pseudorandom number generator, as defined by D. H. Lehmer and + * described by Donald E. Knuth in The Art of Computer Programming, Volume 2: + * Seminumerical Algorithms, section 3.2.1. + * + * @param bits random bits + * @return the next pseudorandom value from this random number generator's sequence + * @since 1.1 + */ + private int next(int bits) { + long oldseed, nextseed; + AtomicLong seed = this.seed; + do { + oldseed = seed.get(); + nextseed = (oldseed * multiplier + addend) & mask; + } while (!seed.compareAndSet(oldseed, nextseed)); + return (int) (nextseed >>> (48 - bits)); + } + + /** + * Generates random bytes and places them into a user-supplied byte array. The number of random + * bytes produced is equal to the length of the byte array. + * + *

The method {@code nextBytes} is implemented by class {@code Random} as if by: + * + *

{@code
+   * public void nextBytes(byte[] bytes) {
+   *   for (int i = 0; i < bytes.length; )
+   *     for (int rnd = nextInt(), n = Math.min(bytes.length - i, 4);
+   *          n-- > 0; rnd >>= 8)
+   *       bytes[i++] = (byte)rnd;
+   * }
+   * }
+ * + * @param bytes the byte array to fill with random bytes + * @throws NullPointerException if the byte array is null + * @since 1.1 + */ + public void nextBytes(byte[] bytes) { + for (int i = 0, len = bytes.length; i < len; ) + for (int rnd = nextInt(), n = Math.min(len - i, Integer.SIZE / Byte.SIZE); + n-- > 0; + rnd >>= Byte.SIZE) bytes[i++] = (byte) rnd; + } + + /** + * The form of nextLong used by LongStream Spliterators. If origin is greater than bound, acts as + * unbounded form of nextLong, else as bounded form. + * + * @param origin the least value, unless greater than bound + * @param bound the upper bound (exclusive), must not equal origin + * @return a pseudorandom value + */ + final long internalNextLong(long origin, long bound) { + long r = nextLong(); + if (origin < bound) { + long n = bound - origin, m = n - 1; + if ((n & m) == 0L) // power of two + r = (r & m) + origin; + else if (n > 0L) { // reject over-represented candidates + for (long u = r >>> 1; // ensure nonnegative + u + m - (r = u % n) < 0L; // rejection check + u = nextLong() >>> 1) // retry + ; + r += origin; + } else { // range not representable as long + while (r < origin || r >= bound) r = nextLong(); + } + } + return r; + } + + /** + * The form of nextInt used by IntStream Spliterators. For the unbounded case: uses nextInt(). For + * the bounded case with representable range: uses nextInt(int bound) For the bounded case with + * unrepresentable range: uses nextInt() + * + * @param origin the least value, unless greater than bound + * @param bound the upper bound (exclusive), must not equal origin + * @return a pseudorandom value + */ + final int internalNextInt(int origin, int bound) { + if (origin < bound) { + int n = bound - origin; + if (n > 0) { + return nextInt(n) + origin; + } else { // range not representable as int + int r; + do { + r = nextInt(); + } while (r < origin || r >= bound); + return r; + } + } else { + return nextInt(); + } + } + + /** + * The form of nextDouble used by DoubleStream Spliterators. + * + * @param origin the least value, unless greater than bound + * @param bound the upper bound (exclusive), must not equal origin + * @return a pseudorandom value + */ + final double internalNextDouble(double origin, double bound) { + double r = nextDouble(); + if (origin < bound) { + r = r * (bound - origin) + origin; + if (r >= bound) // correct for rounding + r = Double.longBitsToDouble(Double.doubleToLongBits(bound) - 1); + } + return r; + } + + /** + * Returns the next pseudorandom, uniformly distributed {@code int} value from this random number + * generator's sequence. The general contract of {@code nextInt} is that one {@code int} value is + * pseudorandomly generated and returned. All 232 possible {@code int} values are + * produced with (approximately) equal probability. + * + *

The method {@code nextInt} is implemented by class {@code Random} as if by: + * + *

{@code
+   * public int nextInt() {
+   *   return next(32);
+   * }
+   * }
+ * + * @return the next pseudorandom, uniformly distributed {@code int} value from this random number + * generator's sequence + */ + public int nextInt() { + return next(32); + } + + /** + * Returns a pseudorandom, uniformly distributed {@code int} value between 0 (inclusive) and the + * specified value (exclusive), drawn from this random number generator's sequence. The general + * contract of {@code nextInt} is that one {@code int} value in the specified range is + * pseudorandomly generated and returned. All {@code bound} possible {@code int} values are + * produced with (approximately) equal probability. The method {@code nextInt(int bound)} is + * implemented by class {@code Random} as if by: + * + *
{@code
+   * public int nextInt(int bound) {
+   *   if (bound <= 0)
+   *     throw new IllegalArgumentException("bound must be positive");
+   *
+   *   if ((bound & -bound) == bound)  // i.e., bound is a power of 2
+   *     return (int)((bound * (long)next(31)) >> 31);
+   *
+   *   int bits, val;
+   *   do {
+   *       bits = next(31);
+   *       val = bits % bound;
+   *   } while (bits - val + (bound-1) < 0);
+   *   return val;
+   * }
+   * }
+ * + *

The hedge "approximately" is used in the foregoing description only because the next method + * is only approximately an unbiased source of independently chosen bits. If it were a perfect + * source of randomly chosen bits, then the algorithm shown would choose {@code int} values from + * the stated range with perfect uniformity. + * + *

The algorithm is slightly tricky. It rejects values that would result in an uneven + * distribution (due to the fact that 2^31 is not divisible by n). The probability of a value + * being rejected depends on n. The worst case is n=2^30+1, for which the probability of a reject + * is 1/2, and the expected number of iterations before the loop terminates is 2. + * + *

The algorithm treats the case where n is a power of two specially: it returns the correct + * number of high-order bits from the underlying pseudo-random number generator. In the absence of + * special treatment, the correct number of low-order bits would be returned. Linear + * congruential pseudo-random number generators such as the one implemented by this class are + * known to have short periods in the sequence of values of their low-order bits. Thus, this + * special case greatly increases the length of the sequence of values returned by successive + * calls to this method if n is a small power of two. + * + * @param bound the upper bound (exclusive). Must be positive. + * @return the next pseudorandom, uniformly distributed {@code int} value between zero (inclusive) + * and {@code bound} (exclusive) from this random number generator's sequence + * @throws IllegalArgumentException if bound is not positive + * @since 1.2 + */ + public int nextInt(int bound) { + if (bound <= 0) throw new IllegalArgumentException(BadBound); + + int r = next(31); + int m = bound - 1; + if ((bound & m) == 0) // i.e., bound is a power of 2 + r = (int) ((bound * (long) r) >> 31); + else { + for (int u = r; u - (r = u % bound) + m < 0; u = next(31)) + ; + } + return r; + } + + /** + * Returns the next pseudorandom, uniformly distributed {@code long} value from this random number + * generator's sequence. The general contract of {@code nextLong} is that one {@code long} value + * is pseudorandomly generated and returned. + * + *

The method {@code nextLong} is implemented by class {@code Random} as if by: + * + *

{@code
+   * public long nextLong() {
+   *   return ((long)next(32) << 32) + next(32);
+   * }
+   * }
+ * + * Because class {@code Random} uses a seed with only 48 bits, this algorithm will not return all + * possible {@code long} values. + * + * @return the next pseudorandom, uniformly distributed {@code long} value from this random number + * generator's sequence + */ + @SuppressWarnings("UnnecessaryParentheses") + public long nextLong() { + // it's okay that the bottom word remains signed. + return ((long) (next(32)) << 32) + next(32); + } + + /** + * Returns the next pseudorandom, uniformly distributed {@code boolean} value from this random + * number generator's sequence. The general contract of {@code nextBoolean} is that one {@code + * boolean} value is pseudorandomly generated and returned. The values {@code true} and {@code + * false} are produced with (approximately) equal probability. + * + *

The method {@code nextBoolean} is implemented by class {@code Random} as if by: + * + *

{@code
+   * public boolean nextBoolean() {
+   *   return next(1) != 0;
+   * }
+   * }
+ * + * @return the next pseudorandom, uniformly distributed {@code boolean} value from this random + * number generator's sequence + * @since 1.2 + */ + public boolean nextBoolean() { + return next(1) != 0; + } + + /** + * Returns the next pseudorandom, uniformly distributed {@code float} value between {@code 0.0} + * and {@code 1.0} from this random number generator's sequence. + * + *

The general contract of {@code nextFloat} is that one {@code float} value, chosen + * (approximately) uniformly from the range {@code 0.0f} (inclusive) to {@code 1.0f} (exclusive), + * is pseudorandomly generated and returned. All 224 possible {@code float} values of + * the form m x 2-24, where m is a positive integer less than + * 224, are produced with (approximately) equal probability. + * + *

The method {@code nextFloat} is implemented by class {@code Random} as if by: + * + *

{@code
+   * public float nextFloat() {
+   *   return next(24) / ((float)(1 << 24));
+   * }
+   * }
+ * + *

The hedge "approximately" is used in the foregoing description only because the next method + * is only approximately an unbiased source of independently chosen bits. If it were a perfect + * source of randomly chosen bits, then the algorithm shown would choose {@code float} values from + * the stated range with perfect uniformity. + * + *

[In early versions of Java, the result was incorrectly calculated as: + * + *

{@code
+   * return next(30) / ((float)(1 << 30));
+   * }
+ * + * This might seem to be equivalent, if not better, but in fact it introduced a slight + * nonuniformity because of the bias in the rounding of floating-point numbers: it was slightly + * more likely that the low-order bit of the significand would be 0 than that it would be 1.] + * + * @return the next pseudorandom, uniformly distributed {@code float} value between {@code 0.0} + * and {@code 1.0} from this random number generator's sequence + */ + public float nextFloat() { + return next(24) / ((float) (1 << 24)); + } + + /** + * Returns the next pseudorandom, uniformly distributed {@code double} value between {@code 0.0} + * and {@code 1.0} from this random number generator's sequence. + * + *

The general contract of {@code nextDouble} is that one {@code double} value, chosen + * (approximately) uniformly from the range {@code 0.0d} (inclusive) to {@code 1.0d} (exclusive), + * is pseudorandomly generated and returned. + * + *

The method {@code nextDouble} is implemented by class {@code Random} as if by: + * + *

{@code
+   * public double nextDouble() {
+   *   return (((long)next(26) << 27) + next(27))
+   *     / (double)(1L << 53);
+   * }
+   * }
+ * + *

The hedge "approximately" is used in the foregoing description only because the {@code next} + * method is only approximately an unbiased source of independently chosen bits. If it were a + * perfect source of randomly chosen bits, then the algorithm shown would choose {@code double} + * values from the stated range with perfect uniformity. + * + *

[In early versions of Java, the result was incorrectly calculated as: + * + *

{@code
+   * return (((long)next(27) << 27) + next(27))
+   *   / (double)(1L << 54);
+   * }
+ * + * This might seem to be equivalent, if not better, but in fact it introduced a large + * nonuniformity because of the bias in the rounding of floating-point numbers: it was three times + * as likely that the low-order bit of the significand would be 0 than that it would be 1! This + * nonuniformity probably doesn't matter much in practice, but we strive for perfection.] + * + * @return the next pseudorandom, uniformly distributed {@code double} value between {@code 0.0} + * and {@code 1.0} from this random number generator's sequence + * @see Math#random + */ + @SuppressWarnings("UnnecessaryParentheses") + public double nextDouble() { + return (((long) (next(26)) << 27) + next(27)) * DOUBLE_UNIT; + } +} diff --git a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt index 11d58766d62..52e294c54dc 100644 --- a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt +++ b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt @@ -1,11 +1,11 @@ package io.sentry +import io.sentry.util.Random import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import java.security.SecureRandom import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -23,7 +23,7 @@ class TracesSamplerTest { profilesSamplerCallback: SentryOptions.ProfilesSamplerCallback? = null, logger: ILogger? = null ): TracesSampler { - val random = mock() + val random = mock() if (randomResult != null) { whenever(random.nextDouble()).thenReturn(randomResult) } From 3c67514009afb5634d48f30bdbc2edc6e89fa63f Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 11 Oct 2024 14:31:58 +0200 Subject: [PATCH 2/3] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6db963eb30..84040594a6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - Deprecate `enableTracing` option ([#3777](https://github.com/getsentry/sentry-java/pull/3777)) +- Vendor `java.util.Random` and replace `java.security.SecureRandom` usages ([#3783](https://github.com/getsentry/sentry-java/pull/3783)) ## 7.15.0 From 7fe49e4b443aff8a75ed91d014e9050ab52ac6a8 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 11 Oct 2024 16:36:31 +0200 Subject: [PATCH 3/3] Fix failing test --- .../main/java/io/sentry/cache/PersistingScopeObserver.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java b/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java index 7c186cf99d6..908e2c66e41 100644 --- a/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java +++ b/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java @@ -133,6 +133,12 @@ public void setReplayId(@NotNull SentryId replayId) { @SuppressWarnings("FutureReturnValueIgnored") private void serializeToDisk(final @NotNull Runnable task) { + if (Thread.currentThread().getName().contains("SentryExecutor")) { + // we're already on the sentry executor thread, so we can just execute it directly + task.run(); + return; + } + try { options .getExecutorService()