diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/IncompatibleBitMaskCheckSample.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/IncompatibleBitMaskCheckSample.java new file mode 100644 index 00000000000..ac3c73e1da1 --- /dev/null +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/IncompatibleBitMaskCheckSample.java @@ -0,0 +1,43 @@ +package checks; + +class IncompatibleBitMaskCheckSample { + + // When semantic info is unavailable, the rule uses a heuristic based on literal kinds + // to decide if the operation is int or long. + + void noSemanticWithIntLiterals(UnknownType obj) { + // No long literals present: heuristic treats as int operation + if ((obj.getValue() & 0x0F) == 0x10) {} // Noncompliant {{This comparison is always false.}} + if ((obj.getValue() | 3) == 2) {} // Noncompliant {{This comparison is always false.}} + if ((obj.getValue() & 0x0F) == 0x04) {} // Compliant + if ((obj.getValue() & 1) != 2) {} // Noncompliant {{This comparison is always true.}} + } + + void noSemanticWithLongLiterals(UnknownType obj) { + // Long literal in mask: heuristic treats as long operation + if ((obj.getValue() & 0xFFL) == 0x100L) {} // Noncompliant {{This comparison is always false.}} + if ((obj.getValue() & 0xFFL) == 0x80L) {} // Compliant + } + + void noSemanticWithNegativeLongLiteral(UnknownType obj) { + // Negative long literal (unary minus on LONG_LITERAL) exercises hasLongLiteral unary path + if ((obj.getValue() & -1L) == 0x100L) {} // Compliant - mask is -1L (all bits), any value is reachable + if ((obj.getValue() | -1L) == 0L) {} // Noncompliant {{This comparison is always false.}} + } + + void noSemanticWithPositiveLongLiteral(UnknownType obj) { + // Positive unary plus on long literal exercises hasLongLiteral unary path + if ((obj.getValue() & +1L) == 2L) {} // Noncompliant {{This comparison is always false.}} + } + + void noSemanticLongLiteralInComparisonValue(UnknownType obj) { + // Long literal only in the comparison value, not in the mask + if ((obj.getValue() & 0x0F) == 0x100L) {} // Noncompliant {{This comparison is always false.}} + } + + void noSemanticLongLiteralAsLeftOperand(UnknownType obj) { + // Long literal as left operand of bitwise operation: hasLongLiteral(leftOperand) returns true + if ((0xFFL & obj.getValue()) == 0x100L) {} // Noncompliant {{This comparison is always false.}} + if ((0xFFL & obj.getValue()) == 0x80L) {} // Compliant + } +} diff --git a/java-checks-test-sources/default/src/main/java/checks/IncompatibleBitMaskCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/IncompatibleBitMaskCheckSample.java new file mode 100644 index 00000000000..235b2ecbc3c --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/IncompatibleBitMaskCheckSample.java @@ -0,0 +1,256 @@ +package checks; + +class IncompatibleBitMaskCheckSample { + + void bitwiseAndNoncompliant(int x, long status) { + // AND mask 1 (0b01) cannot produce 2 (0b10) + if ((x & 1) == 2) {} // Noncompliant {{This comparison is always false.}} + // ^^ + + // AND mask 0x0F cannot produce 0x10 + if ((x & 0x0F) == 0x10) {} // Noncompliant {{This comparison is always false.}} + + // AND mask 3 (0b11) cannot produce 4 (0b100) + if ((x & 3) == 4) {} // Noncompliant {{This comparison is always false.}} + + // != with incompatible values is always true + if ((x & 1) != 2) {} // Noncompliant {{This comparison is always true.}} + + // Long variant: AND mask 0xFF cannot produce 0x100 + if ((status & 0xFFL) == 0x100L) {} // Noncompliant {{This comparison is always false.}} + } + + void bitwiseOrNoncompliant(int x, long data) { + // OR with 1 always sets bit 0, result can never be 0 + if ((x | 1) == 0) {} // Noncompliant {{This comparison is always false.}} + + // OR with 3 always sets bits 0 and 1, result can never be 2 (missing bit 0) + if ((x | 3) == 2) {} // Noncompliant {{This comparison is always false.}} + + // != with incompatible OR is always true + if ((x | 2) != 1) {} // Noncompliant {{This comparison is always true.}} + + // Long variant: OR with 0xFF always sets low 8 bits + if ((data | 0xFFL) == 0L) {} // Noncompliant {{This comparison is always false.}} + } + + void bitwiseAndCompliant(int x, long status) { + // Mask 2 can produce 2 + if ((x & 2) == 2) {} // Compliant + + // 0x04 is within mask 0x0F + if ((x & 0x0F) == 0x04) {} // Compliant + + // Comparing AND result to 0 is always valid + if ((x & 1) == 0) {} // Compliant + + // 3 is within mask 7 + if ((x & 7) == 3) {} // Compliant + + // 2 is reachable by AND with 3 + if ((x & 3) != 2) {} // Compliant + + // Long: 0x80 is within mask 0xFF + if ((status & 0xFFL) == 0x80L) {} // Compliant + } + + void bitwiseOrCompliant(int x, long data) { + // 1 includes all mask bits + if ((x | 1) == 1) {} // Compliant + + // 3 includes all mask bits + if ((x | 3) == 3) {} // Compliant + + // 0xFF includes all mask bits 0x0F + if ((x | 0x0F) == 0xFF) {} // Compliant + + // 3 includes mask bit 2, comparison is meaningful + if ((x | 2) != 3) {} // Compliant + + // Value includes all mask bits + if ((data | 0xFFL) == 0xFFL) {} // Compliant + } + + void maskOnLeftSide(int x) { + // Mask on left side of bitwise operation + if ((1 & x) == 2) {} // Noncompliant {{This comparison is always false.}} + + if ((1 & x) == 0) {} // Compliant + } + + void constantOnLeftSideOfComparison(int x) { + // Constant on left side of comparison + if (2 == (x & 1)) {} // Noncompliant {{This comparison is always false.}} + + if (0 == (x & 1)) {} // Compliant + } + + void hexAndBinaryLiterals(int x) { + // Hex literals + if ((x & 0xFF) == 0x100) {} // Noncompliant {{This comparison is always false.}} + + // Binary literals + if ((x & 0b1111) == 0b10000) {} // Noncompliant {{This comparison is always false.}} + + if ((x & 0b1111) == 0b1010) {} // Compliant + } + + void edgeCases(int x) { + // Mask of 0: AND with 0 always produces 0 + if ((x & 0) == 1) {} // Noncompliant {{This comparison is always false.}} + if ((x & 0) == 0) {} // Compliant + + // OR with 0: result is x, any comparison is meaningful + if ((x | 0) == 5) {} // Compliant + } + + void noConstantOperands(int x, int y, int z) { + // No constant mask - no issue + if ((x & y) == 2) {} // Compliant + + // No constant comparison value - no issue + if ((x & 1) == y) {} // Compliant + + // No constant at all + if ((x & y) == z) {} // Compliant + } + + void notBitwiseOperations(int x) { + // XOR is not covered + if ((x ^ 1) == 2) {} // Compliant + + // Regular comparison without bitwise + if (x == 2) {} // Compliant + + // Addition, not bitwise + if ((x + 1) == 2) {} // Compliant + } + + void parenthesizedExpressions(int x) { + // Extra parentheses around bitwise operation + if (((x & 1)) == 2) {} // Noncompliant {{This comparison is always false.}} + + if (((x & 2)) == 2) {} // Compliant + } + + void bitwiseOrNotEqual(int x, long data) { + // != with OR where mask bits are not all present in value: always true + if ((x | 3) != 0) {} // Noncompliant {{This comparison is always true.}} + + // Long variant: OR with != always true when mask bits missing + if ((data | 0xFFL) != 0L) {} // Noncompliant {{This comparison is always true.}} + + // Constant on left side with != and OR + if (0 != (x | 1)) {} // Noncompliant {{This comparison is always true.}} + + // Compliant: value includes mask bits + if ((x | 1) != 1) {} // Compliant + } + + void longLiteralMaskOnLeft(long x) { + // Long literal as left operand of bitwise AND + if ((0xFFL & x) == 0x100L) {} // Noncompliant {{This comparison is always false.}} + + // Long literal as left operand of bitwise OR + if ((0xFFL | x) == 0L) {} // Noncompliant {{This comparison is always false.}} + + // != with long literal mask on left + if ((0xFFL & x) != 0x100L) {} // Noncompliant {{This comparison is always true.}} + + // Compliant long literal mask on left + if ((0xFFL & x) == 0x80L) {} // Compliant + } + + void longLiteralOnRightOfBitwiseOp(long x) { + // Long literal on right side of bitwise AND + if ((x & 0xFFL) == 0x100L) {} // Noncompliant {{This comparison is always false.}} + + // Long literal on left side of bitwise AND (exercises left-operand path in hasLongLiteral) + if ((0xFFL & x) == 0x80L) {} // Compliant + } + + void intWidthHighBitMasks(int x) { + // 0xFFFFFFFF as int is -1, so (x & 0xFFFFFFFF) == -1 is valid when x == -1 + if ((x & 0xFFFFFFFF) == -1) {} // Compliant + + // 0xFFFFFFFF as int is -1, AND with -1 is identity, so comparing to 0 is valid + if ((x & 0xFFFFFFFF) == 0) {} // Compliant + + // 0x80000000 as int is Integer.MIN_VALUE (-2147483648), valid comparison + if ((x & 0x80000000) == -2147483648) {} // Compliant + + // High-bit hex mask with matching hex value + if ((x & 0xF0000000) == 0xF0000000) {} // Compliant + + // AND with 3 cannot produce -1 (only bits 0 and 1 can be set) + if ((x & 3) == -1) {} // Noncompliant {{This comparison is always false.}} + + // != with int-width high-bit mask + if ((x & 3) != -1) {} // Noncompliant {{This comparison is always true.}} + } + + void comparisonValueOnLeftWithLong(long x) { + // Long constant on left side of comparison, bitwise on right + if (0x100L == (x & 0xFFL)) {} // Noncompliant {{This comparison is always false.}} + + if (0x80L == (x & 0xFFL)) {} // Compliant + } + + void intHexMaskSignExtensionInLongContext(long longX) { + // Int literal 0xFFFFFFFF sign-extends to -1L in long context, so (longX & -1L) == longX + // This comparison is valid when longX == -1 + if ((longX & 0xFFFFFFFF) == -1L) {} // Compliant + + // 0xFFFFFFFF sign-extends to -1L, AND with -1L is identity, so any value is reachable + if ((longX & 0xFFFFFFFF) == 4294967295L) {} // Compliant + + // 0x80000000 as int is MIN_VALUE, sign-extends to 0xFFFFFFFF_80000000L + // AND with that mask can produce 0x80000000L (the low 32 bits match) + if ((longX & 0x80000000) == 0x80000000L) {} // Compliant + + // Int mask 0x0F does not sign-extend (no high bit set), stays 0x0F + // AND with 0x0F cannot produce 0x100L + if ((longX & 0x0F) == 0x100L) {} // Noncompliant {{This comparison is always false.}} + } + + void longBitwiseOpsWithLongLiterals(long x) { + // OR with 0xFFL always sets low 8 bits; result must include those bits + // 0x10000000000L does not include any of the low 8 bits + if ((x | 0xFFL) == 0x10000000000L) {} // Noncompliant {{This comparison is always false.}} + + // Long AND mask: 0xFFL cannot produce value beyond 0xFF + if ((x & 0xFFL) == 0x10000000000L) {} // Noncompliant {{This comparison is always false.}} + } + + void parenthesizedLiterals(int x) { + // Parenthesized mask literal + if ((x & (1)) == 2) {} // Noncompliant {{This comparison is always false.}} + + // Parenthesized comparison value + if ((x & 1) == (2)) {} // Noncompliant {{This comparison is always false.}} + + // Parenthesized hex mask and value + if ((x & (0xFF)) == (0x1FF)) {} // Noncompliant {{This comparison is always false.}} + + // Compliant with parenthesized literals + if ((x & (3)) == (2)) {} // Compliant + } + + void intOperationWithLongComparisonValue(int intVar) { + // int bitwise result is promoted to long for comparison with long literal + // (intVar & 0xFFFFFFFF) is int -1, promoted to long -1L, which != 0xFFFFFFFFL (4294967295) + if ((intVar & 0xFFFFFFFF) == 0xFFFFFFFFL) {} // Noncompliant {{This comparison is always false.}} + + // != variant + if ((intVar & 0xFFFFFFFF) != 0xFFFFFFFFL) {} // Noncompliant {{This comparison is always true.}} + } + + void unsignedHighBitLongMasks(long flags) { + // 0xFFFF_FFFF_FFFF_FFFEL has all bits set except LSB + // AND with that mask clears bit 0 only, so result can never be -1L (all bits set) + if ((flags & 0xFFFF_FFFF_FFFF_FFFEL) == -1L) {} // Noncompliant {{This comparison is always false.}} + + // 0x8000_0000_0000_0000L is Long.MIN_VALUE, AND with it can produce 0L + if ((flags & 0x8000_0000_0000_0000L) == 0L) {} // Compliant + } +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/IncompatibleBitMaskCheck.java b/java-checks/src/main/java/org/sonar/java/checks/IncompatibleBitMaskCheck.java new file mode 100644 index 00000000000..f87e050c6e0 --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/IncompatibleBitMaskCheck.java @@ -0,0 +1,161 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import java.util.List; +import javax.annotation.Nullable; +import org.sonar.check.Rule; +import org.sonar.java.model.ExpressionUtils; +import org.sonar.java.model.LiteralUtils; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.BinaryExpressionTree; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.LiteralTree; +import org.sonar.plugins.java.api.tree.Tree; +import org.sonar.plugins.java.api.tree.UnaryExpressionTree; +import org.sonar.plugins.java.api.tree.Tree.Kind; + +@Rule(key = "S7438") +public class IncompatibleBitMaskCheck extends IssuableSubscriptionVisitor { + + @Override + public List nodesToVisit() { + return List.of(Kind.EQUAL_TO, Kind.NOT_EQUAL_TO); + } + + @Override + public void visitNode(Tree tree) { + BinaryExpressionTree comparison = (BinaryExpressionTree) tree; + ExpressionTree left = comparison.leftOperand(); + ExpressionTree right = comparison.rightOperand(); + check(left, right, comparison); + check(right, left, comparison); + } + + private void check(ExpressionTree possibleBitwiseOp, ExpressionTree possibleConstant, BinaryExpressionTree comparison) { + ExpressionTree unwrapped = ExpressionUtils.skipParentheses(possibleBitwiseOp); + if (!unwrapped.is(Kind.AND, Kind.OR)) { + return; + } + BinaryExpressionTree bitwiseOp = (BinaryExpressionTree) unwrapped; + Long mask = extractMask(bitwiseOp); + Long value = signExtendedLongValue(possibleConstant); + if (mask == null || value == null) { + return; + } + if (isIntOperation(bitwiseOp, possibleConstant)) { + mask = (long) mask.intValue(); + // When comparing an int bitwise result against a long literal outside int range, the comparison is always incompatible + // because the int result is sign-extended to long and can never match a value outside the int range. + if (hasLongLiteral(possibleConstant) && (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE)) { + reportIncompatible(comparison); + return; + } + value = (long) value.intValue(); + } + if (isIncompatible(unwrapped.kind(), mask, value)) { + reportIncompatible(comparison); + } + } + + private void reportIncompatible(BinaryExpressionTree comparison) { + String message = comparison.is(Kind.EQUAL_TO) + ? "This comparison is always false." + : "This comparison is always true."; + reportIssue(comparison.operatorToken(), message); + } + + @Nullable + private static Long signExtendedLongValue(ExpressionTree operand) { + Long value = parseLongLiteral(operand); + if (value != null && isIntLiteral(operand)) { + value = (long) value.intValue(); + } + return value; + } + + @Nullable + private static Long parseLongLiteral(ExpressionTree expr) { + ExpressionTree unwrapped = ExpressionUtils.skipParentheses(expr); + Long value = LiteralUtils.longLiteralValue(unwrapped); + if (value != null) { + return value; + } + // Handle unsigned hex long literals above Long.MAX_VALUE (e.g. 0xFFFF_FFFF_FFFF_FFFEL) + ExpressionTree literal = unwrapped; + if (literal.is(Kind.UNARY_MINUS, Kind.UNARY_PLUS)) { + literal = ((UnaryExpressionTree) literal).expression(); + } + if (literal.is(Kind.LONG_LITERAL)) { + String text = ((LiteralTree) literal).value(); + text = text.substring(0, text.length() - 1).replace("_", ""); + if (text.startsWith("0x") || text.startsWith("0X")) { + try { + long parsed = Long.parseUnsignedLong(text.substring(2), 16); + return unwrapped.is(Kind.UNARY_MINUS) ? -parsed : parsed; + } catch (NumberFormatException e) { + return null; + } + } + } + return null; + } + + @Nullable + private static Long extractMask(BinaryExpressionTree bitwiseOp) { + Long leftValue = signExtendedLongValue(bitwiseOp.leftOperand()); + return leftValue != null ? leftValue : signExtendedLongValue(bitwiseOp.rightOperand()); + } + + private static boolean isIntLiteral(ExpressionTree tree) { + ExpressionTree expr = ExpressionUtils.skipParentheses(tree); + if (expr.is(Kind.UNARY_MINUS, Kind.UNARY_PLUS)) { + expr = ((UnaryExpressionTree) expr).expression(); + } + return expr.is(Kind.INT_LITERAL); + } + + private static boolean isIntOperation(BinaryExpressionTree bitwiseOp, ExpressionTree comparisonValue) { + String typeName = bitwiseOp.symbolType().fullyQualifiedName(); + if ("long".equals(typeName)) { + return false; + } + if ("int".equals(typeName)) { + return true; + } + // No semantic information: use heuristic based on literal kinds + return !hasLongLiteral(bitwiseOp.leftOperand()) + && !hasLongLiteral(bitwiseOp.rightOperand()) + && !hasLongLiteral(comparisonValue); + } + + private static boolean hasLongLiteral(ExpressionTree tree) { + ExpressionTree expr = ExpressionUtils.skipParentheses(tree); + if (expr.is(Kind.UNARY_MINUS, Kind.UNARY_PLUS)) { + expr = ((UnaryExpressionTree) expr).expression(); + } + return expr.is(Kind.LONG_LITERAL); + } + + private static boolean isIncompatible(Kind bitwiseKind, long mask, long value) { + if (bitwiseKind == Kind.AND) { + return (value & mask) != value; + } + // OR + return (value | mask) != value; + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/IncompatibleBitMaskCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/IncompatibleBitMaskCheckTest.java new file mode 100644 index 00000000000..d65980e6180 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/IncompatibleBitMaskCheckTest.java @@ -0,0 +1,53 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import java.util.Collections; +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; +import static org.sonar.java.checks.verifier.TestUtils.nonCompilingTestSourcesPath; + +class IncompatibleBitMaskCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/IncompatibleBitMaskCheckSample.java")) + .withCheck(new IncompatibleBitMaskCheck()) + .verifyIssues(); + } + + @Test + void test_without_semantic() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/IncompatibleBitMaskCheckSample.java")) + .withCheck(new IncompatibleBitMaskCheck()) + .withoutSemantic() + .verifyIssues(); + } + + @Test + void test_non_compiling_without_semantic() { + CheckVerifier.newVerifier() + .onFile(nonCompilingTestSourcesPath("checks/IncompatibleBitMaskCheckSample.java")) + .withCheck(new IncompatibleBitMaskCheck()) + .withClassPath(Collections.emptyList()) + .verifyIssues(); + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7438.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7438.html new file mode 100644 index 00000000000..8b183173d42 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7438.html @@ -0,0 +1,32 @@ +

Why is this an issue?

+

When performing bitwise operations in comparisons, the relationship between the bit mask and the compared value determines what results are +possible. If this relationship makes certain outcomes impossible, the comparison becomes a constant expression.

+

For bitwise AND operations (&), the result can only have bits set where the mask has bits set. For example, x & 1 +can only produce values 0 or 1, never 2. Comparing this result to an impossible value like 2 creates dead code.

+

For bitwise OR operations (|), the result always includes all bits set in the mask. If the compared value doesn't include all mask +bits, the comparison can never be equal.

+

These constant comparisons indicate logical errors in the code.

+

How to fix it

+

Review the bit mask and comparison value to ensure they are logically compatible. For AND operations, verify that the compared value only has bits +that exist in the mask. For OR operations, verify that the compared value includes all bits from the mask.

+

Code examples

+

Noncompliant code example

+
+int x = getUserPermissions();
+if ((x & 1) == 2) { // Noncompliant
+    grantAccess();
+}
+
+

Compliant solution

+
+int x = getUserPermissions();
+if ((x & 2) == 2) {
+    grantAccess();
+}
+
+

Resources

+

Documentation

+ diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7438.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7438.json new file mode 100644 index 00000000000..82f20de063b --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S7438.json @@ -0,0 +1,23 @@ +{ + "title": "Incompatible bit masks should not be used in comparisons", + "type": "BUG", + "code": { + "impacts": { + "RELIABILITY": "HIGH" + }, + "attribute": "LOGICAL" + }, + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "suspicious" + ], + "defaultSeverity": "Blocker", + "ruleSpecification": "RSPEC-7438", + "sqKey": "S7438", + "scope": "All", + "quickfix": "unknown" +} diff --git a/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S7438 b/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S7438 new file mode 100644 index 00000000000..e69de29bb2d