Skip to content

Commit 0660db3

Browse files
author
Esben Sparre Andreasen
committed
JS: introduce SemVer matching library
1 parent 7d57d19 commit 0660db3

File tree

11 files changed

+211
-0
lines changed

11 files changed

+211
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* Provides classes for working SemVer (Semantic Versioning).
3+
*/
4+
5+
import semmle.javascript.dependencies.Dependencies
6+
7+
/**
8+
* A SemVer-formatted version string in a dependency.
9+
*
10+
* Pre-release information and build metadata is not yet supported.
11+
*/
12+
class DependencySemVer extends string {
13+
Dependency dep;
14+
15+
string normalized;
16+
17+
DependencySemVer() {
18+
dep.info(_, this) and
19+
normalized = normalizeSemver(this)
20+
}
21+
22+
/**
23+
* Holds if this version may be before `last`.
24+
*/
25+
bindingset[last]
26+
predicate maybeBefore(string last) { normalized < normalizeSemver(last) }
27+
28+
/**
29+
* Holds if this version may be after `first`.
30+
*/
31+
bindingset[first]
32+
predicate maybeAfter(string first) { normalizeSemver(first) < normalized }
33+
34+
/**
35+
* Holds if this version may be between `first` (inclusive) and `last` (exclusive).
36+
*/
37+
bindingset[first, last]
38+
predicate maybeBetween(string first, string last) {
39+
normalizeSemver(first) <= normalized and
40+
normalized < normalizeSemver(last)
41+
}
42+
43+
/**
44+
* Holds if this version is equivalent to `other`.
45+
*/
46+
bindingset[other]
47+
predicate is(string other) { normalized = normalizeSemver(other) }
48+
49+
/**
50+
* Gets the dependency that uses this string.
51+
*/
52+
Dependency getDependency() { result = dep }
53+
}
54+
55+
bindingset[str]
56+
private string leftPad(string str) { result = ("000" + str).suffix(str.length()) }
57+
58+
/**
59+
* Normalizes a SemVer string such that the lexicographical ordering
60+
* of two normalized strings is consistent with the SemVer ordering.
61+
*
62+
* Pre-release information and build metadata is not yet supported.
63+
*/
64+
bindingset[orig]
65+
private string normalizeSemver(string orig) {
66+
exists(string pattern, string major, string minor, string patch |
67+
pattern = "(\\d+)\\.(\\d+)\\.(\\d+)" and
68+
major = orig.regexpCapture(pattern, 1) and
69+
minor = orig.regexpCapture(pattern, 2) and
70+
patch = orig.regexpCapture(pattern, 3)
71+
|
72+
result = leftPad(major) + "." + leftPad(minor) + "." + leftPad(patch)
73+
)
74+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
| src/dir-with-complex-package-json/tst.js:6:7:6:7 | x |
2+
| src/dir-with-complex-package-json/tst.js:8:10:8:10 | x |
3+
| src/dir-with-package-json/tst.js:7:7:7:7 | x |
4+
| src/dir-with-package-json/tst.js:13:10:13:10 | x |
5+
| src/dir-with-package-lock-json/tst.js:10:7:10:7 | x |
6+
| src/dir-with-package-lock-json/tst.js:15:7:15:7 | x |
7+
| src/dir-without-package-json/tst.js:10:7:10:7 | x |
8+
| src/dir-without-package-json/tst.js:15:7:15:7 | x |
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import javascript
2+
import semmle.javascript.dependencies.Dependencies
3+
import semmle.javascript.dependencies.SemVer
4+
5+
class SampleVersionSink extends DataFlow::Node {
6+
SampleVersionSink() {
7+
exists(
8+
string dependencyName, Dependency dep, DependencySemVer vDep, string vFrom, string vTo,
9+
string functionName, int argNumber
10+
|
11+
dependencyName = "a" and
12+
vFrom = "0.1.0" and
13+
vTo = "1.1.0" and
14+
functionName = "m1" and
15+
argNumber = 0
16+
or
17+
dependencyName = "b" and
18+
vFrom = "1.1.0" and
19+
vTo = "2.1.0" and
20+
functionName = "m1" and
21+
argNumber = 0
22+
or
23+
dependencyName = "c" and
24+
vFrom = "0.1.0" and
25+
vTo = "1.1.0" and
26+
functionName = "m1" and
27+
argNumber = 1
28+
or
29+
dependencyName = "d" and
30+
vFrom = "0.1.0" and
31+
vTo = "1.0.0" and
32+
functionName = "m1" and
33+
argNumber = 0
34+
|
35+
this = DataFlow::dependencyModuleImport(dep)
36+
.getAMemberCall(functionName)
37+
.getArgument(argNumber) and
38+
dep.info(dependencyName, vDep) and
39+
vDep.maybeBetween(vFrom, vTo)
40+
)
41+
}
42+
}
43+
44+
select any(SampleVersionSink s)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"dependencies": {
3+
"a": "^1.0.0",
4+
"c": ">=1.0.0"
5+
}
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const a = require('a'),
2+
c = require('c');
3+
4+
5+
(function() {
6+
a.m1(x); // flagged
7+
8+
c.m1(0, x); // flagged
9+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"dependencies": {
3+
"a": "1.0.0",
4+
"b": "1.0.0",
5+
"c": "1.0.0",
6+
"d": "1.0.0"
7+
}
8+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const a = require('a'),
2+
b = require('b'),
3+
c = require('c'),
4+
d = require('d');
5+
6+
(function() {
7+
a.m1(x); // flagged
8+
a.m2(x); // not flagged: other method
9+
10+
b.m1(x); // not flagged: early version
11+
12+
c.m1(x); // not flagged: other argument
13+
c.m1(0, x); // flagged
14+
15+
d.m1(x); // not flagged: late version
16+
});

javascript/ql/test/library-tests/DependencyModuleImports/src/dir-with-package-lock-json/package-lock.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const a = require('a'),
2+
b = require('b'),
3+
c = require('c'),
4+
d = require('d');
5+
6+
(function() {
7+
a.m1(x); // flagged
8+
a.m2(x); // not flagged: other method
9+
10+
b.m1(x); // not flagged: early version
11+
12+
c.m1(x); // not flagged: other argument
13+
c.m1(0, x); // flagged
14+
15+
d.m1(x); // not flagged: late version
16+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const a = require('a'),
2+
b = require('b'),
3+
c = require('c'),
4+
d = require('d');
5+
6+
(function() {
7+
a.m1(x); // not flagged: not a dependency
8+
a.m2(x); // not flagged: not a dependency
9+
10+
b.m1(x); // flagged
11+
12+
c.m1(x); // not flagged: not a dependency
13+
c.m1(0, x); // not flagged: not a dependency
14+
15+
d.m1(x); // flagged
16+
});

0 commit comments

Comments
 (0)