1 package net.sourceforge.pmd.rules;
2
3 import net.sourceforge.pmd.AbstractRule;
4 import net.sourceforge.pmd.RuleContext;
5 import net.sourceforge.pmd.ast.ASTLiteral;
6
7 public class SuspiciousOctalEscape extends AbstractRule {
8
9 public Object visit(ASTLiteral node, Object data) {
10 RuleContext ctx = (RuleContext) data;
11 String image = node.getImage();
12 if (image != null && image.startsWith("\"")) // make sure it's a string literal
13 {
14 // trim quotes
15 String s = image.substring(1, image.length() - 1);
16
17 // process escape sequences
18 int offset = 0;
19 for (int slash = s.indexOf('//', offset);
20 slash != -1 && slash < s.length() - 1;
21 slash = s.indexOf('//', offset)) {
22 String escapeSequence = s.substring(slash + 1);
23 char first = escapeSequence.charAt(0);
24 if (isOctal(first)) {
25 if (escapeSequence.length() > 1) {
26 char second = escapeSequence.charAt(1);
27 if (isOctal(second)) {
28 if (escapeSequence.length() > 2) {
29 char third = escapeSequence.charAt(2);
30 if (isOctal(third)) {
31 // this is either a three digit octal escape or a two-digit
32 // octal escape followed by an octal digit. the value of
33 // the first digit in the sequence determines which is the
34 // case
35 if (first != '0' && first != '1' && first != '2' && first != '3') {
36 // VIOLATION: it's a two-digit octal escape followed by
37 // an octal digit -- legal but very confusing!
38 ctx.getReport().addRuleViolation(createRuleViolation(ctx, node));
39 } else {
40 // if there is a 4th decimal digit, it could never be part of
41 // the escape sequence, which is confusing
42 if (escapeSequence.length() > 3) {
43 char fourth = escapeSequence.charAt(3);
44 if (isDecimal(fourth)) {
45 ctx.getReport().addRuleViolation(createRuleViolation(ctx, node));
46 }
47 }
48 }
49
50 } else if (isDecimal(third)) {
51 // this is a two-digit octal escape followed by a decimal digit
52 // legal but very confusing
53 ctx.getReport().addRuleViolation(createRuleViolation(ctx, node));
54 }
55 }
56 } else if (isDecimal(second)) {
57 // this is a one-digit octal escape followed by a decimal digit
58 // legal but very confusing
59 ctx.getReport().addRuleViolation(createRuleViolation(ctx, node));
60 }
61 }
62 }
63
64 offset = slash + 1;
65 }
66 }
67
68 return super.visit(node, data);
69 }
70
71 private boolean isOctal(char c) {
72 switch (c) {
73 case '0':
74 case '1':
75 case '2':
76 case '3':
77 case '4':
78 case '5':
79 case '6':
80 case '7':
81 return true;
82 default:
83 return false;
84 }
85 }
86
87 private boolean isDecimal(char c) {
88 switch (c) {
89 case '0':
90 case '1':
91 case '2':
92 case '3':
93 case '4':
94 case '5':
95 case '6':
96 case '7':
97 case '8':
98 case '9':
99 return true;
100 default:
101 return false;
102 }
103 }
104 }