• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 4476

27 Feb 2025 08:31AM UTC coverage: 77.717% (+0.02%) from 77.694%
4476

push

github

adangel
[apex] New Rule: Avoid Stateful Database Results (#5425)

Merge pull request #5425 from mitchspano:stateful

17395 of 23322 branches covered (74.59%)

Branch coverage included in aggregate %.

22 of 22 new or added lines in 1 file covered. (100.0%)

99 existing lines in 9 files now uncovered.

38180 of 48187 relevant lines covered (79.23%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.46
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryCastRule.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.java.rule.codestyle;
6

7
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.ADD;
8
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.DIV;
9
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.GE;
10
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.GT;
11
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.LE;
12
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.LT;
13
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.MOD;
14
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.MUL;
15
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.SHIFT_OPS;
16
import static net.sourceforge.pmd.lang.java.ast.BinaryOp.SUB;
17
import static net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils.isInfixExprWithOperator;
18
import static net.sourceforge.pmd.util.OptionalBool.NO;
19
import static net.sourceforge.pmd.util.OptionalBool.UNKNOWN;
20
import static net.sourceforge.pmd.util.OptionalBool.YES;
21
import static net.sourceforge.pmd.util.OptionalBool.definitely;
22

23
import java.util.EnumSet;
24
import java.util.Set;
25

26
import org.checkerframework.checker.nullness.qual.NonNull;
27
import org.checkerframework.checker.nullness.qual.Nullable;
28

29
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
30
import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
31
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
32
import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
33
import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression;
34
import net.sourceforge.pmd.lang.java.ast.ASTMethodReference;
35
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
36
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
37
import net.sourceforge.pmd.lang.java.ast.InvocationNode;
38
import net.sourceforge.pmd.lang.java.ast.JavaNode;
39
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
40
import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil;
41
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
42
import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol;
43
import net.sourceforge.pmd.lang.java.types.JClassType;
44
import net.sourceforge.pmd.lang.java.types.JMethodSig;
45
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
46
import net.sourceforge.pmd.lang.java.types.OverloadSelectionResult;
47
import net.sourceforge.pmd.lang.java.types.TypeConversion;
48
import net.sourceforge.pmd.lang.java.types.TypeOps;
49
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
50
import net.sourceforge.pmd.lang.java.types.ast.ExprContext;
51
import net.sourceforge.pmd.lang.java.types.ast.ExprContext.ExprContextKind;
52
import net.sourceforge.pmd.util.OptionalBool;
53

54
/**
55
 * Detects casts where the operand is already a subtype of the context
56
 * type, or may be converted to it implicitly.
57
 */
58
public class UnnecessaryCastRule extends AbstractJavaRulechainRule {
59

60
    private static final Set<BinaryOp> BINARY_PROMOTED_OPS =
1✔
61
        EnumSet.of(LE, GE, GT, LT, ADD, SUB, MUL, DIV, MOD);
1✔
62

63
    public UnnecessaryCastRule() {
64
        super(ASTCastExpression.class);
1✔
65
    }
1✔
66

67
    @Override
68
    public Object visit(ASTCastExpression castExpr, Object data) {
69
        ASTExpression operand = castExpr.getOperand();
1✔
70

71
        // eg in
72
        // Object o = (Integer) 1;
73

74
        @Nullable ExprContext context = castExpr.getConversionContext();        // Object
1✔
75
        JTypeMirror coercionType = castExpr.getCastType().getTypeMirror();      // Integer
1✔
76
        JTypeMirror operandType = operand.getTypeMirror();                      // int
1✔
77

78
        if (TypeOps.isUnresolvedOrNull(operandType)
1✔
79
            || TypeOps.isUnresolvedOrNull(coercionType)) {
1✔
80
            return null;
1✔
81
        }
82

83
        // Note that we assume that coercionType is convertible to
84
        // contextType because the code must compile
85

86
        if (operand instanceof ASTLambdaExpression || operand instanceof ASTMethodReference) {
1✔
87
            // Then the cast provides a target type for the expression (always).
88
            // We need to check the enclosing context, as if it's invocation we give up for now
89
            if (context.isMissing() || context.hasKind(ExprContextKind.INVOCATION)) {
1!
90
                // Then the cast may be used to determine the overload.
91
                // We need to treat the casted lambda as a whole unit.
92
                // todo see below
93
                return null;
1✔
94
            }
95

96
            // Since the code is assumed to compile we'll just assume that coercionType
97
            // is a functional interface.
98
            if (coercionType.equals(context.getTargetType())) {
1✔
99
                // then we also know that the context is functional
100
                reportCast(castExpr, data);
1✔
101
            }
102
            // otherwise the cast is narrowing, and removing it would
103
            // change the runtime class of the produced lambda.
104
            // Eg `SuperItf obj = (SubItf) ()-> {};`
105
            // If we remove the cast, even if it might compile,
106
            // the object will not implement SubItf anymore.
107
        } else if (isCastUnnecessary(castExpr, context, coercionType, operandType)) {
1✔
108
            reportCast(castExpr, data);
1✔
109
        }
110
        return null;
1✔
111
    }
112

113
    private boolean isCastUnnecessary(ASTCastExpression castExpr, @NonNull ExprContext context, JTypeMirror coercionType, JTypeMirror operandType) {
114
        if (isCastDeterminingReturnOfLambda(castExpr) != NO) {
1✔
115
            return false;
1✔
116
        }
117

118
        if (operandType.equals(coercionType)) {
1✔
119
            // with the exception of the lambda thing above, casts to
120
            // the same type are always unnecessary
121
            return true;
1✔
122
        } else if (context.isMissing()) {
1✔
123
            // then we have fewer violation conditions
124

125
            return !operandType.isBottom() // casts on a null literal are necessary
1!
126
                && operandType.isSubtypeOf(coercionType);
1✔
127
        }
128

129
        return !isCastDeterminingContext(castExpr, context, coercionType, operandType)
1✔
130
            && castIsUnnecessaryToMatchContext(context, coercionType, operandType);
1✔
131
    }
132

133
    private void reportCast(ASTCastExpression castExpr, Object data) {
134
        asCtx(data).addViolation(castExpr, PrettyPrintingUtil.prettyPrintType(castExpr.getCastType()));
1✔
135
    }
1✔
136

137
    private static boolean castIsUnnecessaryToMatchContext(ExprContext context,
138
                                                           JTypeMirror coercionType,
139
                                                           JTypeMirror operandType) {
140
        if (context.hasKind(ExprContextKind.INVOCATION)) {
1✔
141
            // todo unsupported for now, the cast may be disambiguating overloads
142
            return false;
1✔
143
        }
144

145
        JTypeMirror contextType = context.getTargetType();
1✔
146
        if (contextType == null) {
1!
UNCOV
147
            return false; // should not occur in valid code
×
148
        } else if (!TypeConversion.isConvertibleUsingBoxing(operandType, coercionType)) {
1✔
149
            // narrowing cast
150
            return false;
1✔
151
        } else if (!context.acceptsType(operandType)) {
1✔
152
            // then removing the cast would produce uncompilable code
153
            return false;
1✔
154
        }
155

156
        boolean isBoxingFollowingCast = contextType.isPrimitive() != coercionType.isPrimitive();
1✔
157
        // means boxing behavior is equivalent
158
        return !isBoxingFollowingCast || operandType.unbox().isSubtypeOf(contextType.unbox());
1✔
159
    }
160

161
    /**
162
     * Returns whether the context type actually depends on the cast.
163
     * This means our analysis as written above won't work, and usually
164
     * that the cast is necessary, because there's some primitive conversions
165
     * happening, or some other corner case.
166
     */
167
    private static boolean isCastDeterminingContext(ASTCastExpression castExpr, ExprContext context, @NonNull JTypeMirror coercionType, JTypeMirror operandType) {
168

169
        if (castExpr.getParent() instanceof ASTConditionalExpression && castExpr.getIndexInParent() != 0) {
1✔
170
            // a branch of a ternary
171
            return true;
1✔
172

173
        } else if (context.hasKind(ExprContextKind.STRING) && isInfixExprWithOperator(castExpr.getParent(), ADD)) {
1!
174

175
            // inside string concatenation
176
            return !TypeTestUtil.isA(String.class, JavaAstUtils.getOtherOperandIfInInfixExpr(castExpr))
1✔
177
                && !TypeTestUtil.isA(String.class, operandType);
1!
178

179
        } else if (context.hasKind(ExprContextKind.NUMERIC) && castExpr.getParent() instanceof ASTInfixExpression) {
1✔
180
            // numeric expr
181
            ASTInfixExpression parent = (ASTInfixExpression) castExpr.getParent();
1✔
182

183
            if (isInfixExprWithOperator(parent, SHIFT_OPS)) {
1✔
184
                // if so, then the cast is determining the width of expr
185
                // the right operand is always int
186
                return castExpr == parent.getLeftOperand()
1✔
187
                        && !TypeOps.isStrictSubtype(operandType.unbox(), operandType.getTypeSystem().INT);
1✔
188
            } else if (isInfixExprWithOperator(parent, BINARY_PROMOTED_OPS)) {
1!
189
                ASTExpression otherOperand = JavaAstUtils.getOtherOperandIfInInfixExpr(castExpr);
1✔
190
                JTypeMirror otherType = otherOperand.getTypeMirror();
1✔
191

192
                // Ie, the type that is taken by the binary promotion
193
                // is the type of the cast, not the type of the operand.
194
                // Eg in
195
                //     int i; ((double) i) * i
196
                // the only reason the mult expr has type double is because of the cast
197
                JTypeMirror promotedTypeWithoutCast = TypeConversion.binaryNumericPromotion(operandType, otherType);
1✔
198
                JTypeMirror promotedTypeWithCast = TypeConversion.binaryNumericPromotion(coercionType, otherType);
1✔
199
                return !promotedTypeWithoutCast.equals(promotedTypeWithCast);
1✔
200
            }
201

202
        }
203
        return false;
1✔
204
    }
205

206
    private static OptionalBool isCastDeterminingReturnOfLambda(ASTCastExpression castExpr) {
207
        ASTLambdaExpression lambda = getLambdaParent(castExpr);
1✔
208
        if (lambda == null) {
1✔
209
            return NO;
1✔
210
        }
211

212
        // The necessary conditions are:
213
        // - The lambda return type mentions type vars that are missing from its arguments
214
        // (lambda is context dependent)
215
        // - The lambda type is inferred:
216
        //   1. its context is missing, or
217
        //   2. it is invocation and the parent method call
218
        //      a. is inferred (generic + no explicit arguments)
219
        //      b. mentions some of its type params in the argument type corresponding to the lambda
220

221
        JExecutableSymbol symbol = lambda.getFunctionalMethod().getSymbol();
1✔
222
        if (symbol.isUnresolved()) {
1!
UNCOV
223
            return UNKNOWN;
×
224
        }
225

226
        // Note we don't test the functional method directly, because it has been instantiated
227
        // We test its generic signature (the symbol).
228
        boolean contextDependent = TypeOps.isContextDependent(symbol);
1✔
229
        if (!contextDependent) {
1✔
230
            return NO;
1✔
231
        }
232

233
        ExprContext lambdaCtx = lambda.getConversionContext();
1✔
234
        if (lambdaCtx.isMissing()) {
1!
UNCOV
235
            return YES;
×
236
        } else if (lambdaCtx.hasKind(ExprContextKind.CAST)) {
1✔
237
            return NO;
1✔
238
        } else if (lambdaCtx.hasKind(ExprContextKind.INVOCATION)) {
1✔
239
            InvocationNode parentCall = (InvocationNode) lambda.getParent().getParent();
1✔
240
            if (parentCall.getExplicitTypeArguments() != null) {
1✔
241
                return NO;
1✔
242
            }
243
            OverloadSelectionResult overload = parentCall.getOverloadSelectionInfo();
1✔
244
            if (overload.isFailed()) {
1!
UNCOV
245
                return UNKNOWN;
×
246
            }
247
            JMethodSig parentMethod = overload.getMethodType();
1✔
248
            if (!parentMethod.getSymbol().isGeneric()) {
1!
UNCOV
249
                return NO;
×
250
            }
251

252
            int argIdx = lambda.getIndexInParent();
1✔
253
            JMethodSig genericSig = parentMethod.getSymbol().getGenericSignature();
1✔
254
            // this is the generic lambda ty as mentioned in the formal parameters
255
            JTypeMirror genericLambdaTy = genericSig.ithFormalParam(argIdx, overload.isVarargsCall());
1✔
256
            if (!(genericLambdaTy instanceof JClassType)) {
1!
UNCOV
257
                return NO;
×
258
            }
259
            // Note that we don't capture this type, which may make the method type malformed (eg mentioning a wildcard
260
            // as return type). We need these bare wildcards for "mentionsAny" to work properly.
261
            // The "correct" approach here to remove wildcards would be to infer the ground non-wildcard parameterization
262
            // of the lambda but this is pretty deep inside the inference code and not readily usable.
263
            JClassType lambdaTyCapture = (JClassType) genericLambdaTy;
1✔
264

265
            // This is the method signature of the lambda, given the formal parameter type of the parent call.
266
            // The formal type is not instantiated, it may contain type variables of the parent method...
267
            JMethodSig expectedLambdaMethod = genericLambdaTy.getTypeSystem().sigOf(
1✔
268
                lambda.getFunctionalMethod().getSymbol(),
1✔
269
                lambdaTyCapture.getTypeParamSubst()
1✔
270
            );
271
            // but if the return type does not contain such tvars, then the parent method type does
272
            // not depend on the lambda type :)
273
            return definitely(
1✔
274
                TypeOps.mentionsAny(
1✔
275
                    expectedLambdaMethod.getReturnType(),
1✔
276
                    parentMethod.getTypeParameters()
1✔
277
                )
278
            );
279
        }
280
        return UNKNOWN;
1✔
281
    }
282

283
    private static @Nullable ASTLambdaExpression getLambdaParent(ASTCastExpression castExpr) {
284
        if (castExpr.getParent() instanceof ASTLambdaExpression) {
1✔
285
            return (ASTLambdaExpression) castExpr.getParent();
1✔
286
        }
287
        if (castExpr.getParent() instanceof ASTReturnStatement) {
1✔
288
            JavaNode returnTarget = JavaAstUtils.getReturnTarget((ASTReturnStatement) castExpr.getParent());
1✔
289

290
            if (returnTarget instanceof ASTLambdaExpression) {
1✔
291
                return (ASTLambdaExpression) returnTarget;
1✔
292
            }
293
        }
294
        return null;
1✔
295
    }
296

297
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc