Skip to content

Commit 8d53bce

Browse files
authored
Merge pull request #15476 from geoffw0/preprocblock
C++: Add PreprocBlock.qll library
2 parents 21c0422 + 72948cb commit 8d53bce

File tree

8 files changed

+224
-0
lines changed

8 files changed

+224
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: feature
3+
---
4+
* Added the `PreprocBlock.qll` library to this repository. This library offers a view of `#if`, `#elif`, `#else` and similar directives as a tree with navigable parent-child relationships.
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/**
2+
* This library offers a view of preprocessor branches (`#if`, `#ifdef`,
3+
* `#ifndef`, `#elif` and `#else`) as blocks of code between the opening and
4+
* closing directives, with navigable parent-child relationships to other
5+
* blocks. The main class is `PreprocessorBlock`.
6+
*/
7+
8+
import cpp
9+
10+
/**
11+
* Gets the line of the `ix`th `PreprocessorBranchDirective` in file `f`.
12+
*/
13+
private int getPreprocLineFromIndex(File f, int ix) {
14+
result =
15+
rank[ix](PreprocessorBranchDirective g | g.getFile() = f | g.getLocation().getStartLine())
16+
}
17+
18+
/**
19+
* Gets the `ix`th `PreprocessorBranchDirective` in file `f`.
20+
*/
21+
private PreprocessorBranchDirective getPreprocFromIndex(File f, int ix) {
22+
result.getFile() = f and
23+
result.getLocation().getStartLine() = getPreprocLineFromIndex(f, ix)
24+
}
25+
26+
/**
27+
* Get the index of a `PreprocessorBranchDirective` in its `file`.
28+
*/
29+
private int getPreprocIndex(PreprocessorBranchDirective directive) {
30+
directive = getPreprocFromIndex(directive.getFile(), result)
31+
}
32+
33+
/**
34+
* A chunk of code from one preprocessor branch (`#if`, `#ifdef`,
35+
* `#ifndef`, `#elif` or `#else`) to the directive that closes it
36+
* (`#elif`, `#else` or `#endif`). The `getParent()` method
37+
* allows these blocks to be navigated as a tree, with the root
38+
* being the entire file.
39+
*/
40+
class PreprocessorBlock extends @element {
41+
PreprocessorBlock() {
42+
mkElement(this) instanceof File or
43+
mkElement(this) instanceof PreprocessorBranch or
44+
mkElement(this) instanceof PreprocessorElse
45+
}
46+
47+
/**
48+
* Holds if this element is at the specified location.
49+
* The location spans column `startcolumn` of line `startline` to
50+
* column `endcolumn` of line `endline` in file `filepath`.
51+
* For more information, see
52+
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
53+
*/
54+
predicate hasLocationInfo(
55+
string filepath, int startline, int startcolumn, int endline, int endcolumn
56+
) {
57+
filepath = this.getFile().toString() and
58+
startline = this.getStartLine() and
59+
startcolumn = 0 and
60+
endline = this.getEndLine() and
61+
endcolumn = 0
62+
}
63+
64+
/**
65+
* Gets a textual representation of this element.
66+
*/
67+
string toString() { result = mkElement(this).toString() }
68+
69+
/**
70+
* Gets the file this `PreprocessorBlock` is located in.
71+
*/
72+
File getFile() { result = mkElement(this).getFile() }
73+
74+
/**
75+
* Gets the start line number of this `PreprocessorBlock`.
76+
*/
77+
int getStartLine() { result = mkElement(this).getLocation().getStartLine() }
78+
79+
/**
80+
* Gets the end line number of this `PreprocessorBlock`.
81+
*/
82+
int getEndLine() {
83+
result = mkElement(this).(File).getMetrics().getNumberOfLines() or
84+
result =
85+
mkElement(this).(PreprocessorBranchDirective).getNext().getLocation().getStartLine() - 1
86+
}
87+
88+
private PreprocessorBlock getParentInternal() {
89+
// find the `#ifdef` corresponding to this block and the
90+
// PreprocessorBranchDirective `prev` that came directly
91+
// before it in the source.
92+
exists(int ix, PreprocessorBranchDirective prev |
93+
ix = getPreprocIndex(mkElement(this).(PreprocessorBranchDirective).getIf()) and
94+
prev = getPreprocFromIndex(this.getFile(), ix - 1)
95+
|
96+
if prev instanceof PreprocessorEndif
97+
then
98+
// if we follow an #endif, we have the same parent
99+
// as its corresponding `#if` has.
100+
result = unresolveElement(prev.getIf()).(PreprocessorBlock).getParentInternal()
101+
else
102+
// otherwise we directly follow an #if / #ifdef / #ifndef /
103+
// #elif / #else that must be a level above and our parent
104+
// block.
105+
mkElement(result) = prev
106+
)
107+
}
108+
109+
/**
110+
* Gets the `PreprocessorBlock` that's directly surrounding this one.
111+
* Has no result if this is a file.
112+
*/
113+
PreprocessorBlock getParent() {
114+
not mkElement(this) instanceof File and
115+
(
116+
if exists(this.getParentInternal())
117+
then
118+
// found parent directive
119+
result = this.getParentInternal()
120+
else
121+
// top level directive
122+
mkElement(result) = this.getFile()
123+
)
124+
}
125+
126+
/**
127+
* Gets a `PreprocessorBlock` that's directly inside this one.
128+
*/
129+
PreprocessorBlock getAChild() { result.getParent() = this }
130+
131+
private Include getAnEnclosedInclude() {
132+
result.getFile() = this.getFile() and
133+
result.getLocation().getStartLine() > this.getStartLine() and
134+
result.getLocation().getStartLine() <= this.getEndLine()
135+
}
136+
137+
/**
138+
* Gets an include directive that is directly in this
139+
* `PreprocessorBlock`.
140+
*/
141+
Include getAnInclude() {
142+
result = this.getAnEnclosedInclude() and
143+
not result = this.getAChild().getAnEnclosedInclude()
144+
}
145+
146+
private Macro getAnEnclosedMacro() {
147+
result.getFile() = this.getFile() and
148+
result.getLocation().getStartLine() > this.getStartLine() and
149+
result.getLocation().getStartLine() <= this.getEndLine()
150+
}
151+
152+
/**
153+
* Gets a macro definition that is directly in this
154+
* `PreprocessorBlock`.
155+
*/
156+
Macro getAMacro() {
157+
result = this.getAnEnclosedMacro() and
158+
not result = this.getAChild().getAnEnclosedMacro()
159+
}
160+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// header.h
2+
3+
#ifndef HEADER_H
4+
#define HEADER_H
5+
6+
// ...
7+
8+
#endif // HEADER_H
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// preprocblock.cpp
2+
3+
#include "header.h"
4+
#define GREEN
5+
6+
#ifdef RED
7+
#elif defined GREEN
8+
#include "header.h"
9+
10+
#ifndef BLUE
11+
#include "header.h"
12+
#endif
13+
14+
#if 0
15+
#include "header.h" // not reached
16+
#else
17+
#include "header.h"
18+
#endif
19+
20+
#include "header.h"
21+
#else
22+
23+
// ...
24+
25+
#endif
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
| #elif defined GREEN | preprocblock.cpp:10:0:11:0 | #ifndef BLUE |
2+
| #elif defined GREEN | preprocblock.cpp:14:0:15:0 | #if 0 |
3+
| #elif defined GREEN | preprocblock.cpp:16:0:17:0 | #else |
4+
| (no parent) | file://:0:0:0:0 | |
5+
| (no parent) | header.h:0:0:8:0 | header.h |
6+
| (no parent) | preprocblock.cpp:0:0:25:0 | preprocblock.cpp |
7+
| header.h | header.h:3:0:7:0 | #ifndef HEADER_H |
8+
| preprocblock.cpp | preprocblock.cpp:6:0:6:0 | #ifdef RED |
9+
| preprocblock.cpp | preprocblock.cpp:7:0:20:0 | #elif defined GREEN |
10+
| preprocblock.cpp | preprocblock.cpp:21:0:24:0 | #else |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import cpp
2+
import semmle.code.cpp.headers.PreprocBlock
3+
4+
from PreprocessorBlock b, string parent
5+
where if exists(b.getParent()) then parent = b.getParent().toString() else parent = "(no parent)"
6+
select parent, b
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
| preprocblock.cpp:3:1:3:19 | #include "header.h" | preprocblock.cpp:0:0:25:0 | preprocblock.cpp |
2+
| preprocblock.cpp:8:2:8:20 | #include "header.h" | preprocblock.cpp:7:0:20:0 | #elif defined GREEN |
3+
| preprocblock.cpp:11:3:11:21 | #include "header.h" | preprocblock.cpp:10:0:11:0 | #ifndef BLUE |
4+
| preprocblock.cpp:17:3:17:21 | #include "header.h" | preprocblock.cpp:16:0:17:0 | #else |
5+
| preprocblock.cpp:20:2:20:20 | #include "header.h" | preprocblock.cpp:7:0:20:0 | #elif defined GREEN |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import cpp
2+
import semmle.code.cpp.headers.PreprocBlock
3+
4+
from PreprocessorBlock b, Include i
5+
where b.getAnInclude() = i
6+
select i, b

0 commit comments

Comments
 (0)