Pay attention to potential integer cross-border issues | JD Logistics technical team

In the normal development process, integer out-of-bounds is a problem that is easily overlooked. Paying attention to potential integer out-of-bounds problems can make the code we write more robust and avoid bugs caused by integer out-of-bounds problems.

Comparators

Here is the comparator implementation found in Code Review :



At first glance, there is no problem with the comparator implementation, but if tag1 = Integer.MIN_VALUE = -2147483648, tag2 is a number greater than 0 such as 1, then tag1 - tag2 = 2147483647, but according to java.util.Comparator#compare By definition, when tag1 is less than tag2, a negative number should be returned. The above writing method will cause confusion in the sorting results and cause related bugs when encountering such sample data.

Let's take a look at the implementation of the comparator in Spring. In Spring, the @Order annotation is provided to specify the order of beans. The default value is Ordered.LOWEST_PRECEDENCE = Integer.MAX_VALUE, which means it will be ranked last when sorting. The relevant source code is as follows:



The corresponding comparator is implemented as follows:

It can be seen that the Integer.compare method is used to compare two integers. View the source code of the Integer#compare method:

/**
 * Compares two {@code int} values numerically.
 * The value returned is identical to what would be returned by:
 * <pre>
 *    Integer.valueOf(x).compareTo(Integer.valueOf(y))
 * </pre>
 *
 * @param  x the first {@code int} to compare
 * @param  y the second {@code int} to compare
 * @return the value {@code 0} if {@code x == y};
 *         a value less than {@code 0} if {@code x < y}; and
 *         a value greater than {@code 0} if {@code x > y}
 * @since 1.7
 */
public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

It can be seen that java.lang.Integer#compare does not use the x - y method for comparison, but uses the less than and equal operators to directly compare, avoiding potential integer out-of-bounds problems. Then the correct implementation of the first code should be return Integer.compare(tag1, tag2). If you look at the source code of common numerical classes in the JDK, you can see that they all provide static compare methods, such as: java.lang.Long#compare, java.lang.Double#compare, which will not be described here.

Cutting ratio

The above code is the implementation of the initial cutting ratio in a certain piece of business logic. The remainder 100 mode is often used in business scenarios such as proportional cutting and proportional downgrading. The above code uses the hash value of userPin to take remainder 100 to determine whether it is less than the cut ratio to decide whether to execute new business logic. If we look at the source code implementation of java.lang.String#hashCode:

/**
 * Returns a hash code for this string. The hash code for a
 * {@code String} object is computed as
 * <blockquote><pre>
 * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
 * </pre></blockquote>
 * using {@code int} arithmetic, where {@code s[i]} is the
 * <i>i</i>th character of the string, {@code n} is the length of
 * the string, and {@code ^} indicates exponentiation.
 * (The hash value of the empty string is zero.)
 *
 * @return  a hash code value for this object.
 */
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

It can be seen that java.lang.String#hashCode essentially performs s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1 on the string ] Polynomial evaluation, the potential risk here is that the calculated hash value may go out of bounds , causing the return value of userPin.hashCode() to be a negative number, such as: "jd_xxxxxxxxxxxx".hashCode() = -1406647067, and in the Java language, use It is possible to get a negative number by taking the modulus of a negative number from a positive number. The risk of the above code is that it potentially amplifies the expected volume ratio . If the above code is used to go online, then when we set a 1% volume ratio, it will cause far more than 1% of users to execute new business logic ( Through sampling logs, we found that the proportion of negative hashCode values ​​in user pin sets is not low), leading to unexpected measurement results.

Based on the above background, a fix that can easily be thought of is to use Math.abs in the outer layer of userPin.hashCode to ensure that the number before the remainder is a positive number:

The above repair solution seems to no longer have the problem, but it is not guaranteed to be completely correct. Let’s check the source code implementation of Math.abs:

/**
 * Returns the absolute value of an {@code int} value.
 * If the argument is not negative, the argument is returned.
 * If the argument is negative, the negation of the argument is returned.
 *
 * <p>Note that if the argument is equal to the value of
 * {@link Integer#MIN_VALUE}, the most negative representable
 * {@code int} value, the result is that same value, which is
 * negative.
 *
 * @param   a   the argument whose absolute value is to be determined
 * @return  the absolute value of the argument.
 */
public static int abs(int a) {
    return (a < 0) ? -a : a;
}

It can be seen that it is specifically mentioned in the comments that if the input parameter is Integer.MIN_VALUE, that is, the smallest value in the int field, the return value is still Integer.MIN_VALUE, because the range of the int field is [-2147483648, 2147483647]. If you follow the explanation in the JLS, -x equals (~x)+1. So it can be known:

x = Integer.MIN_VALUE:
10000000_00000000_00000000_00000000

~x:
01111111_11111111_11111111_11111111

(~x) + 1:
10000000_00000000_00000000_00000000

If you search Math.abs on Magic Lantern, you can find three articles related to this function, all of which are related to Math.abs(Integer.MIN_VALUE) still being Integer.MIN_VALUE. When we discovered this problem during the Code Review stage, we fundamentally avoided the problem and would not put the buggy code online. The final modified implementation of the cutting ratio is as follows:



Summarize

  • java.lang.String#hashCode may return a negative number because the integer is out of bounds during calculation.
  • % in Java language is remainder rather than modulus , such as: (-21) % 4 = (-21) - (-21) / 4 *4 = -1
  • Math.abs(int a) When the input parameter is Integer.MIN_VALUE, the return value is still a negative number Integer.MIN_VALUE.

reference

15.15.4. Unary Minus Operator -

What's the difference between “mod” and “remainder”? - Stack Overflow

Best way to make Java's modulus behave like it should with negative numbers? - Stack Overflow

OrderComparator.java · spring-projects/spring-framework

Author: JD Logistics Liu Jianshe Zhang Jiulong Tian Shuang

Source: JD Cloud Developer Community Ziyuanqishuo Tech Please indicate the source when reprinting

Microsoft launches new "Windows App" .NET 8 officially GA, the latest LTS version Xiaomi officially announced that Xiaomi Vela is fully open source, and the underlying kernel is NuttX Alibaba Cloud 11.12 The cause of the failure is exposed: Access Key Service (Access Key) exception Vite 5 officially released GitHub report : TypeScript replaces Java and becomes the third most popular language Offering a reward of hundreds of thousands of dollars to rewrite Prettier in Rust Asking the open source author "Is the project still alive?" Very rude and disrespectful Bytedance: Using AI to automatically tune Linux kernel parameter operators Magic operation: disconnect the network in the background, deactivate the broadband account, and force the user to change the optical modem
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/10148337