Skip to content

Commit 476ea7a

Browse files
author
Dave Bartolomeo
committed
Integration test
1 parent 40e0027 commit 476ea7a

5 files changed

Lines changed: 3257 additions & 0 deletions

File tree

extensions/ql-vscode/src/vscode-tests/minimal-workspace/activation.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as assert from 'assert';
22
import * as path from 'path';
33
import * as vscode from 'vscode';
44
import * as determiningSelectedQueryTest from './determining-selected-query-test';
5+
import * as sourcemapTest from './sourcemap.test';
56

67
describe('launching with a minimal workspace', async () => {
78

@@ -38,3 +39,4 @@ describe('launching with a minimal workspace', async () => {
3839
});
3940

4041
determiningSelectedQueryTest.run();
42+
sourcemapTest.run();
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { fail } from 'assert';
2+
import { commands, Selection, window, workspace } from 'vscode';
3+
import * as path from 'path';
4+
import * as assert from 'assert';
5+
import { expect } from 'chai';
6+
import { tmpDir } from '../../helpers';
7+
import * as fs from 'fs-extra';
8+
9+
export function run() {
10+
/**
11+
* Integration tests for queries
12+
*/
13+
describe('SourceMap', function() {
14+
this.timeout(200000);
15+
16+
it('should jump to QL code', async () => {
17+
try {
18+
this.timeout(60000);
19+
20+
const root = workspace.workspaceFolders![0].uri.fsPath;
21+
const srcFiles = {
22+
summary: path.join(root, 'log-summary', 'evaluator-log.summary'),
23+
summaryMap: path.join(root, 'log-summary', 'evaluator-log.summary.map')
24+
};
25+
// We need to modify the source map so that its paths point to the actual location of the
26+
// workspace root on this machine. We'll copy the summary and its source map to a temp
27+
// directory, modify the source map their, and open that summary.
28+
const tempFiles = await copyFilesToTempDirectory(srcFiles);
29+
30+
// The checked-in sourcemap has placeholders of the form `${root}`, which we need to replace
31+
// with the actual root directory.
32+
const mapText = await fs.readFile(tempFiles.summaryMap, 'utf-8');
33+
// Always use forward slashes, since they work everywhere.
34+
const slashRoot = root.replaceAll('\\', '/');
35+
const newMapText = mapText.replaceAll('${root}', slashRoot);
36+
await fs.writeFile(tempFiles.summaryMap, newMapText);
37+
38+
const summaryDocument = await workspace.openTextDocument(tempFiles.summary);
39+
assert(summaryDocument.languageId === 'ql-summary');
40+
const summaryEditor = await window.showTextDocument(summaryDocument);
41+
summaryEditor.selection = new Selection(356, 10, 356, 10);
42+
await commands.executeCommand('codeQL.gotoQL');
43+
44+
const newEditor = window.activeTextEditor;
45+
expect(newEditor).to.be.not.undefined;
46+
const newDocument = newEditor!.document;
47+
expect(path.basename(newDocument.fileName)).to.equal('Namespace.qll');
48+
const newSelection = newEditor!.selection;
49+
expect(newSelection.start.line).to.equal(60);
50+
expect(newSelection.start.character).to.equal(2);
51+
} catch (e) {
52+
console.error('Test Failed');
53+
fail(e as Error);
54+
}
55+
});
56+
57+
async function copyFilesToTempDirectory<T extends Record<string, string>>(files: T): Promise<T> {
58+
const tempDir = path.join(tmpDir.name, 'log-summary');
59+
await fs.ensureDir(tempDir);
60+
const result: Record<string, string> = {};
61+
for (const key in files) {
62+
const srcPath = files[key];
63+
const destPath = path.join(tempDir, path.basename(srcPath));
64+
await fs.copy(srcPath, destPath);
65+
result[key] = destPath;
66+
}
67+
68+
return result as T;
69+
}
70+
});
71+
}
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/**
2+
* Provides classes for modeling namespaces, `using` directives and `using` declarations.
3+
*/
4+
5+
import semmle.code.cpp.Element
6+
import semmle.code.cpp.Type
7+
import semmle.code.cpp.metrics.MetricNamespace
8+
9+
/**
10+
* A C++ namespace. For example the (single) namespace `A` in the following
11+
* code:
12+
* ```
13+
* namespace A
14+
* {
15+
* // ...
16+
* }
17+
*
18+
* // ...
19+
*
20+
* namespace A
21+
* {
22+
* // ...
23+
* }
24+
* ```
25+
* Note that namespaces are somewhat nebulous entities, as they do not in
26+
* general have a single well-defined location in the source code. The
27+
* related notion of a `NamespaceDeclarationEntry` is rather more concrete,
28+
* and should be used when a location is required. For example, the `std::`
29+
* namespace is particularly nebulous, as parts of it are defined across a
30+
* wide range of headers. As a more extreme example, the global namespace
31+
* is never explicitly declared, but might correspond to a large proportion
32+
* of the source code.
33+
*/
34+
class Namespace extends NameQualifyingElement, @namespace {
35+
/**
36+
* Gets the location of the namespace. Most namespaces do not have a
37+
* single well-defined source location, so a dummy location is returned,
38+
* unless the namespace has exactly one declaration entry.
39+
*/
40+
override Location getLocation() {
41+
if strictcount(this.getADeclarationEntry()) = 1
42+
then result = this.getADeclarationEntry().getLocation()
43+
else result instanceof UnknownDefaultLocation
44+
}
45+
46+
/** Gets the simple name of this namespace. */
47+
override string getName() { namespaces(underlyingElement(this), result) }
48+
49+
/** Holds if this element is named `name`. */
50+
predicate hasName(string name) { name = this.getName() }
51+
52+
/** Holds if this namespace is anonymous. */
53+
predicate isAnonymous() { this.hasName("(unnamed namespace)") }
54+
55+
/** Gets the name of the parent namespace, if it exists. */
56+
private string getParentName() {
57+
result = this.getParentNamespace().getName() and
58+
result != ""
59+
}
60+
61+
/** Gets the qualified name of this namespace. For example: `a::b`. */
62+
string getQualifiedName() {
63+
if exists(this.getParentName())
64+
then result = this.getParentNamespace().getQualifiedName() + "::" + this.getName()
65+
else result = this.getName()
66+
}
67+
68+
/** Gets the parent namespace, if any. */
69+
Namespace getParentNamespace() {
70+
namespacembrs(unresolveElement(result), underlyingElement(this))
71+
or
72+
not namespacembrs(_, underlyingElement(this)) and result instanceof GlobalNamespace
73+
}
74+
75+
/** Gets a child declaration of this namespace. */
76+
Declaration getADeclaration() { namespacembrs(underlyingElement(this), unresolveElement(result)) }
77+
78+
/** Gets a child namespace of this namespace. */
79+
Namespace getAChildNamespace() {
80+
namespacembrs(underlyingElement(this), unresolveElement(result))
81+
}
82+
83+
/** Holds if the namespace is inline. */
84+
predicate isInline() { namespace_inline(underlyingElement(this)) }
85+
86+
/** Holds if this namespace may be from source. */
87+
override predicate fromSource() { this.getADeclaration().fromSource() }
88+
89+
/** Gets the metric namespace. */
90+
MetricNamespace getMetrics() { result = this }
91+
92+
/** Gets a version of the `QualifiedName` that is more suitable for display purposes. */
93+
string getFriendlyName() { result = this.getQualifiedName() }
94+
95+
final override string toString() { result = this.getFriendlyName() }
96+
97+
/** Gets a declaration of (part of) this namespace. */
98+
NamespaceDeclarationEntry getADeclarationEntry() { result.getNamespace() = this }
99+
100+
/** Gets a file which declares (part of) this namespace. */
101+
File getAFile() { result = this.getADeclarationEntry().getLocation().getFile() }
102+
}
103+
104+
/**
105+
* A declaration of (part of) a C++ namespace. This corresponds to a single
106+
* `namespace N { ... }` occurrence in the source code. For example the two
107+
* mentions of `A` in the following code:
108+
* ```
109+
* namespace A
110+
* {
111+
* // ...
112+
* }
113+
*
114+
* // ...
115+
*
116+
* namespace A
117+
* {
118+
* // ...
119+
* }
120+
* ```
121+
*/
122+
class NamespaceDeclarationEntry extends Locatable, @namespace_decl {
123+
/**
124+
* Get the namespace that this declaration entry corresponds to. There
125+
* is a one-to-many relationship between `Namespace` and
126+
* `NamespaceDeclarationEntry`.
127+
*/
128+
Namespace getNamespace() {
129+
namespace_decls(underlyingElement(this), unresolveElement(result), _, _)
130+
}
131+
132+
override string toString() { result = this.getNamespace().getFriendlyName() }
133+
134+
/**
135+
* Gets the location of the token preceding the namespace declaration
136+
* entry's body.
137+
*
138+
* For named declarations, such as "namespace MyStuff { ... }", this will
139+
* give the "MyStuff" token.
140+
*
141+
* For anonymous declarations, such as "namespace { ... }", this will
142+
* give the "namespace" token.
143+
*/
144+
override Location getLocation() { namespace_decls(underlyingElement(this), _, result, _) }
145+
146+
/**
147+
* Gets the location of the namespace declaration entry's body. For
148+
* example: the "{ ... }" in "namespace N { ... }".
149+
*/
150+
Location getBodyLocation() { namespace_decls(underlyingElement(this), _, _, result) }
151+
152+
override string getAPrimaryQlClass() { result = "NamespaceDeclarationEntry" }
153+
}
154+
155+
/**
156+
* A C++ `using` directive or `using` declaration.
157+
*/
158+
class UsingEntry extends Locatable, @using {
159+
override Location getLocation() { usings(underlyingElement(this), _, result) }
160+
}
161+
162+
/**
163+
* A C++ `using` declaration. For example:
164+
* ```
165+
* using std::string;
166+
* ```
167+
*/
168+
class UsingDeclarationEntry extends UsingEntry {
169+
UsingDeclarationEntry() {
170+
not exists(Namespace n | usings(underlyingElement(this), unresolveElement(n), _))
171+
}
172+
173+
/**
174+
* Gets the declaration that is referenced by this using declaration. For
175+
* example, `std::string` in `using std::string`.
176+
*/
177+
Declaration getDeclaration() { usings(underlyingElement(this), unresolveElement(result), _) }
178+
179+
override string toString() { result = "using " + this.getDeclaration().getDescription() }
180+
}
181+
182+
/**
183+
* A C++ `using` directive. For example:
184+
* ```
185+
* using namespace std;
186+
* ```
187+
*/
188+
class UsingDirectiveEntry extends UsingEntry {
189+
UsingDirectiveEntry() {
190+
exists(Namespace n | usings(underlyingElement(this), unresolveElement(n), _))
191+
}
192+
193+
/**
194+
* Gets the namespace that is referenced by this using directive. For
195+
* example, `std` in `using namespace std`.
196+
*/
197+
Namespace getNamespace() { usings(underlyingElement(this), unresolveElement(result), _) }
198+
199+
override string toString() { result = "using namespace " + this.getNamespace().getFriendlyName() }
200+
}
201+
202+
/**
203+
* Holds if `g` is an instance of `GlobalNamespace`. This predicate
204+
* is used suppress a warning in `GlobalNamespace.getADeclaration()`
205+
* by providing a fake use of `this`.
206+
*/
207+
private predicate suppressWarningForUnused(GlobalNamespace g) { any() }
208+
209+
/**
210+
* The C/C++ global namespace.
211+
*/
212+
class GlobalNamespace extends Namespace {
213+
GlobalNamespace() { this.hasName("") }
214+
215+
override Declaration getADeclaration() {
216+
suppressWarningForUnused(this) and
217+
result.isTopLevel() and
218+
not namespacembrs(_, unresolveElement(result))
219+
}
220+
221+
/** Gets a child namespace of the global namespace. */
222+
override Namespace getAChildNamespace() {
223+
suppressWarningForUnused(this) and
224+
not namespacembrs(unresolveElement(result), _)
225+
}
226+
227+
override Namespace getParentNamespace() { none() }
228+
229+
override string getFriendlyName() { result = "(global namespace)" }
230+
}
231+
232+
/**
233+
* The C++ `std::` namespace.
234+
*/
235+
class StdNamespace extends Namespace {
236+
StdNamespace() { this.hasName("std") and this.getParentNamespace() instanceof GlobalNamespace }
237+
}

0 commit comments

Comments
 (0)