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

pmd / pmd / 225

28 Oct 2025 07:44AM UTC coverage: 78.72% (+0.04%) from 78.679%
225

push

github

adangel
[java] Fix #6146: ClassCastException in TypeTestUtil (#6156)

18307 of 24103 branches covered (75.95%)

Branch coverage included in aggregate %.

37 of 39 new or added lines in 11 files covered. (94.87%)

6 existing lines in 1 file now uncovered.

39859 of 49787 relevant lines covered (80.06%)

0.81 hits per line

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

84.3
/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/RuleChainAnalyzer.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.rule.xpath.internal;
6

7
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
8

9
import java.util.ArrayDeque;
10
import java.util.Collections;
11
import java.util.Comparator;
12
import java.util.Deque;
13
import java.util.List;
14

15
import net.sourceforge.pmd.lang.ast.Node;
16

17
import net.sf.saxon.Configuration;
18
import net.sf.saxon.expr.AxisExpression;
19
import net.sf.saxon.expr.Expression;
20
import net.sf.saxon.expr.FilterExpression;
21
import net.sf.saxon.expr.LetExpression;
22
import net.sf.saxon.expr.RootExpression;
23
import net.sf.saxon.expr.SlashExpression;
24
import net.sf.saxon.expr.VennExpression;
25
import net.sf.saxon.expr.sort.DocumentSorter;
26
import net.sf.saxon.om.AxisInfo;
27
import net.sf.saxon.pattern.CombinedNodeTest;
28
import net.sf.saxon.pattern.NameTest;
29
import net.sf.saxon.type.Type;
30

31
/**
32
 * Analyzes the xpath expression to find the root path selector for a element. If found,
33
 * the element name is available via {@link RuleChainAnalyzer#getRootElements()} and the
34
 * expression is rewritten to start at "node::self()" instead.
35
 *
36
 * <p>It uses a visitor to visit all the different expressions.
37
 *
38
 * <p>Example: The XPath expression <code>//A[condition()]/B</code> results the rootElement "A"
39
 * and the expression is rewritten to be <code>self::node[condition()]/B</code>.
40
 *
41
 * <p>DocumentSorter expression is removed. The sorting of the resulting nodes needs to be done
42
 * after all (sub)expressions have been executed.
43
 */
44
public class RuleChainAnalyzer extends SaxonExprVisitor {
45

46
    private final Configuration configuration;
47
    private List<String> rootElement;
48
    private boolean rootElementReplaced;
49
    private boolean insideExpensiveExpr;
50
    private boolean foundPathInsideExpensive;
51
    private boolean foundCombinedNodeTest;
52

53
    public RuleChainAnalyzer(Configuration currentConfiguration) {
1✔
54
        this.configuration = currentConfiguration;
1✔
55
    }
1✔
56

57
    public List<String> getRootElements() {
58
        if (!foundPathInsideExpensive && rootElementReplaced) {
1✔
59
            return rootElement == null ? Collections.emptyList() : rootElement;
1!
60
        }
61
        return Collections.emptyList();
1✔
62
    }
63

64
    @Override
65
    public Expression visit(DocumentSorter e) {
66
        DocumentSorter result = (DocumentSorter) super.visit(e);
1✔
67
        // sorting of the nodes must be done after all nodes have been found
68
        return result.getBaseExpression();
1✔
69
    }
70

71
    public Expression visitSlashPreserveRootElement(SlashExpression e) {
72
        Expression start = visit(e.getStart());
1✔
73

74
        // save state
75
        List<String> elt = rootElement;
1✔
76
        boolean replaced = rootElementReplaced;
1✔
77

78
        Expression step = visit(e.getStep());
1✔
79

80
        if (!(getStart(e) instanceof RootExpression)) {
1✔
81
            // restore
82
            rootElement = elt;
1✔
83
            rootElementReplaced = replaced;
1✔
84
        }
85

86
        return new SlashExpression(start, step);
1✔
87
    }
88

89
    private Object getStart(SlashExpression e) {
90
        Expression ex = e.getStart();
1✔
91
        return ex instanceof SlashExpression ? getStart((SlashExpression) ex) : ex;
1✔
92
    }
93

94
    @Override
95
    public Expression visit(SlashExpression e) {
96
        if (!insideExpensiveExpr && rootElement == null) {
1!
97
            Expression result = visitSlashPreserveRootElement(e);
1✔
98
            if (rootElement != null && !rootElementReplaced) {
1✔
99
                if (result instanceof SlashExpression) {
1!
100
                    SlashExpression newPath = (SlashExpression) result;
1✔
101
                    Expression step = newPath.getStep();
1✔
102
                    if (step instanceof FilterExpression) {
1✔
103
                        FilterExpression filterExpression = (FilterExpression) step;
1✔
104

105
                        Deque<Expression> filters = new ArrayDeque<>();
1✔
106
                        Expression walker = filterExpression;
1✔
107
                        while (walker instanceof FilterExpression) {
1✔
108
                            filters.push(((FilterExpression) walker).getFilter());
1✔
109
                            walker = ((FilterExpression) walker).getBase();
1✔
110
                        }
111
                        result = new FilterExpression(new AxisExpression(AxisInfo.SELF, null), filters.pop());
1✔
112
                        while (!filters.isEmpty()) {
1!
UNCOV
113
                            result = new FilterExpression(result, filters.pop());
×
114
                        }
115
                        rootElementReplaced = true;
1✔
116
                    } else if (step instanceof AxisExpression) {
1!
117
                        Expression start = newPath.getStart();
1✔
118
                        if (start instanceof RootExpression) {
1!
119
                            result = new AxisExpression(AxisInfo.SELF, null);
1✔
UNCOV
120
                        } else if (start instanceof VennExpression) {
×
121
                            // abort, set rootElementReplaced so that the
122
                            // nodes above won't try to replace themselves
UNCOV
123
                            rootElement = null;
×
UNCOV
124
                            result = e;
×
125
                        } else {
126
                            result = new SlashExpression(start, new AxisExpression(AxisInfo.SELF, null));
×
127
                        }
128
                        rootElementReplaced = true;
1✔
129
                    }
130
                } else {
1✔
UNCOV
131
                    result = new AxisExpression(AxisInfo.DESCENDANT_OR_SELF, null);
×
UNCOV
132
                    rootElementReplaced = true;
×
133
                }
134
            }
135
            return result;
1✔
136
        } else {
137
            if (insideExpensiveExpr) {
1!
138
                foundPathInsideExpensive = true;
1✔
139
            }
140
            return super.visit(e);
1✔
141
        }
142
    }
143

144
    @Override
145
    public Expression visit(AxisExpression e) {
146
        if (rootElement == null && e.getNodeTest() instanceof NameTest && !foundCombinedNodeTest) {
1✔
147
            NameTest test = (NameTest) e.getNodeTest();
1✔
148
            if (test.getPrimitiveType() == Type.ELEMENT && e.getAxis() == AxisInfo.DESCENDANT) {
1!
149
                rootElement = listOf(configuration.getNamePool().getClarkName(test.getFingerprint()));
1✔
150
            } else if (test.getPrimitiveType() == Type.ELEMENT && e.getAxis() == AxisInfo.CHILD) {
1!
151
                rootElement = listOf(configuration.getNamePool().getClarkName(test.getFingerprint()));
1✔
152
            }
153
        } else if (e.getNodeTest() instanceof CombinedNodeTest) {
1✔
154
            foundCombinedNodeTest = true;
1✔
155
        }
156
        return super.visit(e);
1✔
157
    }
158

159
    @Override
160
    public Expression visit(LetExpression e) {
161
        // lazy expressions are not a thing in saxon HE
162
        // instead saxon hoists expensive subexpressions into LetExpressions
163
        // Eg //A[//B]
164
        // is transformed to let bs := //B in //A
165
        // so that the //B is done only once.
166

167
        // The cost of an expr is an abstract measure of its expensiveness,
168
        // Eg the cost of //A or //* is 40, the cost of //A//B is 820
169
        // (a path expr multiplies the cost of its lhs and rhs)
170

171
        if (e.getSequence().getCost() >= 20) {
1✔
172
            boolean prevCtx = insideExpensiveExpr;
1✔
173
            insideExpensiveExpr = true;
1✔
174
            Expression result = super.visit(e);
1✔
175
            insideExpensiveExpr = prevCtx;
1✔
176
            return result;
1✔
177
        } else {
178
            return super.visit(e);
1✔
179
        }
180
    }
181

182
    @Override
183
    public Expression visit(VennExpression e) {
184
        // stop visiting subtree. We assume all unions were at the root
185
        // and flattened, here we find one that couldn't be flattened
186
        return e;
1✔
187
    }
188

189
    public static Comparator<Node> documentOrderComparator() {
190
        return PmdDocumentSorter.INSTANCE;
1✔
191
    }
192

193
}
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