Skip to content

Commit 7b2a2e0

Browse files
committed
Add library for more complex code identifier deviations
This new library supports deviating on the next line, or on ranges, in addition to deviating on the current line.
1 parent f3a6c3f commit 7b2a2e0

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
/**
2+
* A module for identifying comment markers in code that trigger deviations.
3+
*
4+
* Each comment marker consists of a `code-identifier` with some optional annotations. A deviation will be applied to
5+
* some range of lines in the file containing the comment based on the annotation. The supported marker annotation
6+
* formats are:
7+
* - `<code-identifier>` - the deviation applies to results on the current line.
8+
* - `DEVIATION(<code-identifier>)` - same as above.
9+
* - `DEVIATION_NEXT_LINE(<code-identifier>)` - this deviation applies to results on the next line.
10+
* - `DEVIATION_BEGIN(<code-identifier>)` - marks the beginning of a range of lines where the deviation applies.
11+
* - `DEVIATION_END(<code-identifier>)` - marks the end of a range of lines where the deviation applies.
12+
*
13+
* The valid `code-identifier`s are specified in deviation records, which also specify the query whose results are
14+
* suppressed by the deviation.
15+
*
16+
* For begin/end, we maintain a stack of begin markers. When we encounter an end marker, we pop the stack to determine
17+
* the range of that begin/end marker. If the stack is empty, the end marker is considered unmatched and invalid. If
18+
* the stack is non-empty at the end of the file, all the begin markers are considered unmatched and invalid.
19+
*
20+
* Begin/end markers are not valid across include boundaries, as the stack is not maintained across files.
21+
*/
22+
23+
import cpp
24+
import Deviations
25+
26+
/**
27+
* Holds if the given comment contains the code identifier.
28+
*/
29+
bindingset[codeIdentifier]
30+
private predicate commentMatches(Comment comment, string codeIdentifier) {
31+
exists(string text |
32+
comment instanceof CppStyleComment and
33+
// strip the beginning slashes
34+
text = comment.getContents().suffix(2).trim()
35+
or
36+
comment instanceof CStyleComment and
37+
// strip both the beginning /* and the end */ the comment
38+
exists(string text0 |
39+
text0 = comment.getContents().suffix(2) and
40+
text = text0.prefix(text0.length() - 2).trim()
41+
) and
42+
// The /* */ comment must be a single-line comment
43+
not text.matches("%\n%")
44+
|
45+
// Code identifier appears at the start of the comment (modulo whitespace)
46+
text.prefix(codeIdentifier.length()) = codeIdentifier
47+
or
48+
// Code identifier appears at the end of the comment (modulo whitespace)
49+
text.suffix(text.length() - codeIdentifier.length()) = codeIdentifier
50+
)
51+
}
52+
53+
/**
54+
* A deviation marker in the code.
55+
*/
56+
abstract class DeviationMarker extends Comment {
57+
DeviationRecord record;
58+
59+
/**
60+
* Gets the deviation record associated with this deviation marker.
61+
*/
62+
DeviationRecord getRecord() { result = record }
63+
}
64+
65+
/**
66+
* A deviation marker for a deviation that applies to the current line.
67+
*/
68+
class DeviationEndOfLineMarker extends DeviationMarker {
69+
DeviationEndOfLineMarker() {
70+
commentMatches(this, "DEVIATION(" + record.getCodeIdentifier() + ")")
71+
}
72+
}
73+
74+
/**
75+
* A deviation marker for a deviation that applies to the next line.
76+
*/
77+
class DeviationNextLineMarker extends DeviationMarker {
78+
DeviationNextLineMarker() {
79+
commentMatches(this, "DEVIATION_NEXT_LINE(" + record.getCodeIdentifier() + ")")
80+
}
81+
}
82+
83+
/**
84+
* A deviation marker for a deviation that applies to a range of lines
85+
*/
86+
abstract class DeviationRangeMarker extends DeviationMarker { }
87+
88+
/**
89+
* A deviation marker for a deviation that begins on this line.
90+
*/
91+
class DeviationBegin extends DeviationRangeMarker {
92+
DeviationBegin() { commentMatches(this, "DEVIATION_BEGIN(" + record.getCodeIdentifier() + ")") }
93+
}
94+
95+
/**
96+
* A deviation marker for a deviation that ends on this line.
97+
*/
98+
class DeviationEnd extends DeviationRangeMarker {
99+
DeviationEnd() { commentMatches(this, "DEVIATION_END(" + record.getCodeIdentifier() + ")") }
100+
}
101+
102+
private predicate hasDeviationCommentFileOrdering(
103+
DeviationRecord record, DeviationRangeMarker comment, File file, int index
104+
) {
105+
comment =
106+
rank[index](DeviationRangeMarker c |
107+
c.getRecord() = record and
108+
file = c.getFile()
109+
|
110+
c order by c.getLocation().getStartLine(), c.getLocation().getStartColumn()
111+
)
112+
}
113+
114+
private predicate mkBeginStack(DeviationRecord record, File file, BeginStack stack, int index) {
115+
// Stack is empty at the start
116+
index = 0 and
117+
stack = TEmptyBeginStack() and
118+
exists(DeviationRangeMarker marker |
119+
marker.getRecord() = record and marker.getLocation().getFile() = file
120+
)
121+
or
122+
// Next token is begin, so push it to the stack
123+
exists(DeviationBegin begin, BeginStack prev |
124+
record = begin.getRecord() and
125+
hasDeviationCommentFileOrdering(record, begin, file, index) and
126+
mkBeginStack(record, file, prev, index - 1) and
127+
stack = TConsBeginStack(begin, prev)
128+
)
129+
or
130+
// Next token is end
131+
exists(DeviationEnd end, BeginStack prevStack |
132+
record = end.getRecord() and
133+
hasDeviationCommentFileOrdering(record, end, file, index) and
134+
mkBeginStack(record, file, prevStack, index - 1)
135+
|
136+
// There is, so pop the most recent begin off the stack
137+
prevStack = TConsBeginStack(_, stack)
138+
or
139+
// Error, no begin on the stack, ignore and continue
140+
prevStack = TEmptyBeginStack() and
141+
stack = TEmptyBeginStack()
142+
)
143+
}
144+
145+
newtype TBeginStack =
146+
TConsBeginStack(DeviationBegin begin, TBeginStack prev) {
147+
exists(File file, int index |
148+
hasDeviationCommentFileOrdering(begin.getRecord(), begin, file, index) and
149+
mkBeginStack(begin.getRecord(), file, prev, index - 1)
150+
)
151+
} or
152+
TEmptyBeginStack()
153+
154+
private class BeginStack extends TBeginStack {
155+
string toString() {
156+
exists(DeviationBegin begin, BeginStack prev | this = TConsBeginStack(begin, prev) |
157+
result = "(" + begin + ", " + prev.toString() + ")"
158+
)
159+
or
160+
this = TEmptyBeginStack() and
161+
result = "()"
162+
}
163+
}
164+
165+
private predicate isDeviationRangePaired(
166+
DeviationRecord record, DeviationBegin begin, DeviationEnd end
167+
) {
168+
exists(File file, int index |
169+
record = end.getRecord() and
170+
hasDeviationCommentFileOrdering(record, end, file, index) and
171+
mkBeginStack(record, file, TConsBeginStack(begin, _), index - 1)
172+
)
173+
}
174+
175+
newtype TCodeIndentifierDeviation =
176+
TSingleLineDeviation(DeviationRecord record, Comment comment, string filepath, int suppressedLine) {
177+
(
178+
commentMatches(comment, record.getCodeIdentifier()) or
179+
comment.(DeviationEndOfLineMarker).getRecord() = record
180+
) and
181+
comment.getLocation().hasLocationInfo(filepath, suppressedLine, _, _, _)
182+
or
183+
comment.(DeviationNextLineMarker).getRecord() = record and
184+
comment.getLocation().hasLocationInfo(filepath, suppressedLine - 1, _, _, _)
185+
} or
186+
TMultiLineDeviation(
187+
DeviationRecord record, DeviationBegin beginComment, DeviationEnd endComment, string filepath,
188+
int suppressedStartLine, int suppressedEndLine
189+
) {
190+
isDeviationRangePaired(record, beginComment, endComment) and
191+
beginComment.getLocation().hasLocationInfo(filepath, suppressedStartLine, _, _, _) and
192+
endComment.getLocation().hasLocationInfo(filepath, suppressedEndLine, _, _, _)
193+
}
194+
195+
class CodeIdentifierDeviation extends TCodeIndentifierDeviation {
196+
/** The deviation record associated with the deviation comment. */
197+
DeviationRecord getDeviationRecord() {
198+
this = TSingleLineDeviation(result, _, _, _)
199+
or
200+
this = TMultiLineDeviation(result, _, _, _, _, _)
201+
}
202+
203+
/**
204+
* Holds if the given element is matched by this code identifier deviation.
205+
*/
206+
bindingset[e]
207+
pragma[inline_late]
208+
predicate isElementMatching(Element e) {
209+
exists(string filepath, int elementLocationStart |
210+
e.getLocation().hasLocationInfo(filepath, elementLocationStart, _, _, _)
211+
|
212+
exists(int suppressedLine |
213+
this = TSingleLineDeviation(_, _, filepath, suppressedLine) and
214+
suppressedLine = elementLocationStart
215+
)
216+
or
217+
exists(int suppressedStartLine, int suppressedEndLine |
218+
this = TMultiLineDeviation(_, _, _, filepath, suppressedStartLine, suppressedEndLine) and
219+
suppressedStartLine < elementLocationStart and
220+
suppressedEndLine > elementLocationStart
221+
)
222+
)
223+
}
224+
225+
string toString() {
226+
exists(string filepath |
227+
exists(int suppressedLine |
228+
this = TSingleLineDeviation(_, _, filepath, suppressedLine) and
229+
result =
230+
"Deviation record " + getDeviationRecord() + " applied to " + filepath + " Line " +
231+
suppressedLine
232+
)
233+
or
234+
exists(int suppressedStartLine, int suppressedEndLine |
235+
this = TMultiLineDeviation(_, _, _, filepath, suppressedStartLine, suppressedEndLine) and
236+
result =
237+
"Deviation record " + getDeviationRecord() + " applied to " + filepath + " Line" +
238+
suppressedStartLine + ":" + suppressedEndLine
239+
)
240+
)
241+
}
242+
}

0 commit comments

Comments
 (0)