Skip to content

Commit 47ac8f3

Browse files
committed
iterate on the implementation, and use shared Locations implementation in JavaScript
1 parent 56ad4d8 commit 47ac8f3

File tree

9 files changed

+151
-309
lines changed

9 files changed

+151
-309
lines changed

javascript/ql/lib/qlpack.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ dbscheme: semmlecode.javascript.dbscheme
55
extractor: javascript
66
library: true
77
upgrades: upgrades
8+
dependencies:
9+
codeql/utils: 0.0.1

javascript/ql/lib/semmle/javascript/Files.qll

Lines changed: 17 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -2,179 +2,35 @@
22

33
import javascript
44
private import NodeModuleResolutionImpl
5+
private import codeql.utils.FileSystem as FS
56

6-
/** A file or folder. */
7-
abstract class Container extends @container {
8-
/**
9-
* Gets the absolute, canonical path of this container, using forward slashes
10-
* as path separator.
11-
*
12-
* The path starts with a _root prefix_ followed by zero or more _path
13-
* segments_ separated by forward slashes.
14-
*
15-
* The root prefix is of one of the following forms:
16-
*
17-
* 1. A single forward slash `/` (Unix-style)
18-
* 2. An upper-case drive letter followed by a colon and a forward slash,
19-
* such as `C:/` (Windows-style)
20-
* 3. Two forward slashes, a computer name, and then another forward slash,
21-
* such as `//FileServer/` (UNC-style)
22-
*
23-
* Path segments are never empty (that is, absolute paths never contain two
24-
* contiguous slashes, except as part of a UNC-style root prefix). Also, path
25-
* segments never contain forward slashes, and no path segment is of the
26-
* form `.` (one dot) or `..` (two dots).
27-
*
28-
* Note that an absolute path never ends with a forward slash, except if it is
29-
* a bare root prefix, that is, the path has no path segments. A container
30-
* whose absolute path has no segments is always a `Folder`, not a `File`.
31-
*/
32-
abstract string getAbsolutePath();
7+
module FSImpl implements FS::FilesSig {
8+
// TODO: Move to internal/ folder?
9+
abstract class AtContainer extends @container {
10+
abstract string getAbsolutePath();
3311

34-
/**
35-
* Gets a URL representing the location of this container.
36-
*
37-
* For more information see [Providing URLs](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls).
38-
*/
39-
abstract string getURL();
12+
string toString() { result = this.getAbsolutePath() }
4013

41-
/**
42-
* Gets the relative path of this file or folder from the root folder of the
43-
* analyzed source location. The relative path of the root folder itself is
44-
* the empty string.
45-
*
46-
* This has no result if the container is outside the source root, that is,
47-
* if the root folder is not a reflexive, transitive parent of this container.
48-
*/
49-
string getRelativePath() {
50-
exists(string absPath, string pref |
51-
absPath = this.getAbsolutePath() and sourceLocationPrefix(pref)
52-
|
53-
absPath = pref and result = ""
54-
or
55-
absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
56-
not result.matches("/%")
57-
)
14+
AtContainer getParentContainer() { containerparent(result, this) }
5815
}
5916

60-
/**
61-
* Gets the base name of this container including extension, that is, the last
62-
* segment of its absolute path, or the empty string if it has no segments.
63-
*
64-
* Here are some examples of absolute paths and the corresponding base names
65-
* (surrounded with quotes to avoid ambiguity):
66-
*
67-
* <table border="1">
68-
* <tr><th>Absolute path</th><th>Base name</th></tr>
69-
* <tr><td>"/tmp/tst.js"</td><td>"tst.js"</td></tr>
70-
* <tr><td>"C:/Program Files (x86)"</td><td>"Program Files (x86)"</td></tr>
71-
* <tr><td>"/"</td><td>""</td></tr>
72-
* <tr><td>"C:/"</td><td>""</td></tr>
73-
* <tr><td>"D:/"</td><td>""</td></tr>
74-
* <tr><td>"//FileServer/"</td><td>""</td></tr>
75-
* </table>
76-
*/
77-
string getBaseName() {
78-
result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(\\.([^.]*))?)", 1)
17+
class AtFile extends @file, AtContainer {
18+
override string getAbsolutePath() { files(this, result) }
7919
}
8020

81-
/**
82-
* Gets the extension of this container, that is, the suffix of its base name
83-
* after the last dot character, if any.
84-
*
85-
* In particular,
86-
*
87-
* - if the name does not include a dot, there is no extension, so this
88-
* predicate has no result;
89-
* - if the name ends in a dot, the extension is the empty string;
90-
* - if the name contains multiple dots, the extension follows the last dot.
91-
*
92-
* Here are some examples of absolute paths and the corresponding extensions
93-
* (surrounded with quotes to avoid ambiguity):
94-
*
95-
* <table border="1">
96-
* <tr><th>Absolute path</th><th>Extension</th></tr>
97-
* <tr><td>"/tmp/tst.js"</td><td>"js"</td></tr>
98-
* <tr><td>"/tmp/.classpath"</td><td>"classpath"</td></tr>
99-
* <tr><td>"/bin/bash"</td><td>not defined</td></tr>
100-
* <tr><td>"/tmp/tst2."</td><td>""</td></tr>
101-
* <tr><td>"/tmp/x.tar.gz"</td><td>"gz"</td></tr>
102-
* </table>
103-
*/
104-
string getExtension() {
105-
result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(\\.([^.]*))?)", 4)
21+
class AtFolder extends @folder, AtContainer {
22+
override string getAbsolutePath() { folders(this, result) }
10623
}
10724

108-
/**
109-
* Gets the stem of this container, that is, the prefix of its base name up to
110-
* (but not including) the last dot character if there is one, or the entire
111-
* base name if there is not.
112-
*
113-
* Here are some examples of absolute paths and the corresponding stems
114-
* (surrounded with quotes to avoid ambiguity):
115-
*
116-
* <table border="1">
117-
* <tr><th>Absolute path</th><th>Stem</th></tr>
118-
* <tr><td>"/tmp/tst.js"</td><td>"tst"</td></tr>
119-
* <tr><td>"/tmp/.classpath"</td><td>""</td></tr>
120-
* <tr><td>"/bin/bash"</td><td>"bash"</td></tr>
121-
* <tr><td>"/tmp/tst2."</td><td>"tst2"</td></tr>
122-
* <tr><td>"/tmp/x.tar.gz"</td><td>"x.tar"</td></tr>
123-
* </table>
124-
*/
125-
string getStem() {
126-
result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(\\.([^.]*))?)", 2)
127-
}
128-
129-
/** Gets the parent container of this file or folder, if any. */
130-
Container getParentContainer() { containerparent(result, this) }
131-
132-
/** Gets a file or sub-folder in this container. */
133-
Container getAChildContainer() { this = result.getParentContainer() }
134-
135-
/** Gets a file in this container. */
136-
File getAFile() { result = this.getAChildContainer() }
137-
138-
/** Gets the file in this container that has the given `baseName`, if any. */
139-
File getFile(string baseName) {
140-
result = this.getAFile() and
141-
result.getBaseName() = baseName
142-
}
143-
144-
/** Gets a sub-folder in this container. */
145-
Folder getAFolder() { result = this.getAChildContainer() }
146-
147-
/** Gets the sub-folder in this container that has the given `baseName`, if any. */
148-
Folder getFolder(string baseName) {
149-
result = this.getAFolder() and
150-
result.getBaseName() = baseName
151-
}
152-
153-
/**
154-
* Gets a textual representation of the path of this container.
155-
*
156-
* This is the absolute path of the container.
157-
*/
158-
string toString() { result = this.getAbsolutePath() }
25+
string getSourceLocationPrefix() { sourceLocationPrefix(result) }
15926
}
16027

161-
/** A folder. */
162-
class Folder extends Container, @folder {
163-
override string getAbsolutePath() { folders(this, result) }
164-
165-
/** Gets the file or subfolder in this folder that has the given `name`, if any. */
166-
Container getChildContainer(string name) {
167-
result = this.getAChildContainer() and
168-
result.getBaseName() = name
169-
}
28+
private module FSInst = FS::Make<FSImpl>;
17029

171-
/** Gets the file in this folder that has the given `stem` and `extension`, if any. */
172-
File getFile(string stem, string extension) {
173-
result = this.getAChildContainer() and
174-
result.getStem() = stem and
175-
result.getExtension() = extension
176-
}
30+
class Container = FSInst::Container;
17731

32+
/** A folder. */
33+
class Folder extends FSInst::Folder {
17834
/** Like `getFile` except `d.ts` is treated as a single extension. */
17935
private File getFileLongExtension(string stem, string extension) {
18036
not (stem.matches("%.d") and extension = "ts") and
@@ -203,25 +59,17 @@ class Folder extends Container, @folder {
20359
this.getFileLongExtension(stem, ext) order by p
20460
)
20561
}
206-
207-
/** Gets a subfolder contained in this folder. */
208-
Folder getASubFolder() { result = this.getAChildContainer() }
209-
210-
/** Gets the URL of this folder. */
211-
override string getURL() { result = "folder://" + this.getAbsolutePath() }
21262
}
21363

21464
/** A file. */
215-
class File extends Container, @file {
65+
class File extends FSInst::File {
21666
/**
21767
* Gets the location of this file.
21868
*
21969
* Note that files have special locations starting and ending at line zero, column zero.
22070
*/
22171
Location getLocation() { hasLocation(this, result) }
22272

223-
override string getAbsolutePath() { files(this, result) }
224-
22573
/** Gets the number of lines in this file. */
22674
int getNumberOfLines() { result = sum(int loc | numlines(this, loc, _, _) | loc) }
22775

@@ -234,9 +82,6 @@ class File extends Container, @file {
23482
/** Gets a toplevel piece of JavaScript code in this file. */
23583
TopLevel getATopLevel() { result.getFile() = this }
23684

237-
/** Gets the URL of this file. */
238-
override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
239-
24085
/**
24186
* Holds if line number `lineno` of this file is indented to depth `d`
24287
* using character `c`.

javascript/ql/lib/semmle/javascript/Locations.qll

Lines changed: 17 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,32 @@
11
/** Provides classes for working with locations and program elements that have locations. */
22

33
import javascript
4+
private import codeql.utils.Locations as Loc
5+
private import codeql.utils.FileSystem as FS
6+
7+
private module LocationsImpl implements Loc::LocationsSig<FSImpl> {
8+
class AtLocation = @location;
9+
10+
predicate locations(
11+
AtLocation loc, FSImpl::AtFile file, int startLine, int startColum, int endLine, int endColumn
12+
) {
13+
locations_default(loc, file, startLine, startColum, endLine, endColumn)
14+
}
15+
}
416

517
/**
618
* A location as given by a file, a start line, a start column,
719
* an end line, and an end column.
820
*
921
* For more information about locations see [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
1022
*/
11-
class Location extends @location {
12-
/** Gets the file for this location. */
13-
File getFile() { locations_default(this, result, _, _, _, _) }
14-
15-
/** Gets the 1-based line number (inclusive) where this location starts. */
16-
int getStartLine() { locations_default(this, _, result, _, _, _) }
17-
18-
/** Gets the 1-based column number (inclusive) where this location starts. */
19-
int getStartColumn() { locations_default(this, _, _, result, _, _) }
20-
21-
/** Gets the 1-based line number (inclusive) where this location ends. */
22-
int getEndLine() { locations_default(this, _, _, _, result, _) }
23-
24-
/** Gets the 1-based column number (inclusive) where this location ends. */
25-
int getEndColumn() { locations_default(this, _, _, _, _, result) }
26-
27-
/** Gets the number of lines covered by this location. */
28-
int getNumLines() { result = this.getEndLine() - this.getStartLine() + 1 }
29-
30-
/** Holds if this location starts before location `that`. */
31-
pragma[inline]
32-
predicate startsBefore(Location that) {
33-
exists(File f, int sl1, int sc1, int sl2, int sc2 |
34-
locations_default(this, f, sl1, sc1, _, _) and
35-
locations_default(that, f, sl2, sc2, _, _)
36-
|
37-
sl1 < sl2
38-
or
39-
sl1 = sl2 and sc1 < sc2
40-
)
41-
}
42-
43-
/** Holds if this location ends after location `that`. */
44-
pragma[inline]
45-
predicate endsAfter(Location that) {
46-
exists(File f, int el1, int ec1, int el2, int ec2 |
47-
locations_default(this, f, _, _, el1, ec1) and
48-
locations_default(that, f, _, _, el2, ec2)
49-
|
50-
el1 > el2
51-
or
52-
el1 = el2 and ec1 > ec2
53-
)
54-
}
55-
56-
/**
57-
* Holds if this location contains location `that`, meaning that it starts
58-
* before and ends after it.
59-
*/
60-
predicate contains(Location that) { this.startsBefore(that) and this.endsAfter(that) }
61-
62-
/** Holds if this location is empty. */
63-
predicate isEmpty() { exists(int l, int c | locations_default(this, _, l, c, l, c - 1)) }
64-
65-
/** Gets a textual representation of this element. */
66-
string toString() { result = this.getFile().getBaseName() + ":" + this.getStartLine().toString() }
67-
23+
class Location extends Loc::Make<FSImpl, LocationsImpl>::Location {
6824
/**
69-
* Holds if this element is at the specified location.
70-
* The location spans column `startcolumn` of line `startline` to
71-
* column `endcolumn` of line `endline` in file `filepath`.
72-
* For more information, see
73-
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
25+
* Holds if this location starts strictly before the specified location.
26+
* Alias for `strictlyBefore`.
7427
*/
75-
predicate hasLocationInfo(
76-
string filepath, int startline, int startcolumn, int endline, int endcolumn
77-
) {
78-
exists(File f |
79-
locations_default(this, f, startline, startcolumn, endline, endcolumn) and
80-
filepath = f.getAbsolutePath()
81-
)
82-
}
28+
pragma[inline]
29+
predicate startsBefore(Location other) { this.strictlyBefore(other) }
8330
}
8431

8532
/** A program element with a location. */

ruby/ql/lib/codeql/Locations.qll

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,4 @@
1-
private import codeql.utils.Locations as L
2-
import codeql.files.FileSystem
3-
4-
private module LocationsImpl implements L::LocationsSig {
5-
class File_ = File;
6-
7-
class AtLocation = @location_default;
8-
9-
predicate locations(
10-
AtLocation loc, File file, int startLine, int startColum, int endLine, int endColumn
11-
) {
12-
locations_default(loc, file, startLine, startColum, endLine, endColumn)
13-
}
14-
15-
string getAbsolutePath(File f) { result = f.getAbsolutePath() }
16-
}
17-
18-
import L::Make<LocationsImpl>
1+
private import codeql.utils.Locations as Locs
2+
private import codeql.files.FilesImpl::FilesImpl as FSImpl
3+
private import codeql.LocationsImpl::LocationsImpl as LImpl
4+
import Locs::Make<FSImpl, LImpl>

ruby/ql/lib/codeql/LocationsImpl.qll

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
private import codeql.utils.Locations as Locs
2+
private import codeql.files.FilesImpl::FilesImpl as FSImpl
3+
4+
module LocationsImpl implements Locs::LocationsSig<FSImpl> {
5+
class AtLocation = @location_default;
6+
7+
predicate locations(
8+
AtLocation loc, FSImpl::AtFile file, int startLine, int startColum, int endLine, int endColumn
9+
) {
10+
locations_default(loc, file, startLine, startColum, endLine, endColumn)
11+
}
12+
}

0 commit comments

Comments
 (0)