JS: introduce SemVer matching library

This commit is contained in:
Esben Sparre Andreasen
2019-04-17 15:22:12 +02:00
parent 7d57d1915a
commit 0660db37f6
11 changed files with 211 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
/**
* Provides classes for working SemVer (Semantic Versioning).
*/
import semmle.javascript.dependencies.Dependencies
/**
* A SemVer-formatted version string in a dependency.
*
* Pre-release information and build metadata is not yet supported.
*/
class DependencySemVer extends string {
Dependency dep;
string normalized;
DependencySemVer() {
dep.info(_, this) and
normalized = normalizeSemver(this)
}
/**
* Holds if this version may be before `last`.
*/
bindingset[last]
predicate maybeBefore(string last) { normalized < normalizeSemver(last) }
/**
* Holds if this version may be after `first`.
*/
bindingset[first]
predicate maybeAfter(string first) { normalizeSemver(first) < normalized }
/**
* Holds if this version may be between `first` (inclusive) and `last` (exclusive).
*/
bindingset[first, last]
predicate maybeBetween(string first, string last) {
normalizeSemver(first) <= normalized and
normalized < normalizeSemver(last)
}
/**
* Holds if this version is equivalent to `other`.
*/
bindingset[other]
predicate is(string other) { normalized = normalizeSemver(other) }
/**
* Gets the dependency that uses this string.
*/
Dependency getDependency() { result = dep }
}
bindingset[str]
private string leftPad(string str) { result = ("000" + str).suffix(str.length()) }
/**
* Normalizes a SemVer string such that the lexicographical ordering
* of two normalized strings is consistent with the SemVer ordering.
*
* Pre-release information and build metadata is not yet supported.
*/
bindingset[orig]
private string normalizeSemver(string orig) {
exists(string pattern, string major, string minor, string patch |
pattern = "(\\d+)\\.(\\d+)\\.(\\d+)" and
major = orig.regexpCapture(pattern, 1) and
minor = orig.regexpCapture(pattern, 2) and
patch = orig.regexpCapture(pattern, 3)
|
result = leftPad(major) + "." + leftPad(minor) + "." + leftPad(patch)
)
}

View File

@@ -0,0 +1,8 @@
| src/dir-with-complex-package-json/tst.js:6:7:6:7 | x |
| src/dir-with-complex-package-json/tst.js:8:10:8:10 | x |
| src/dir-with-package-json/tst.js:7:7:7:7 | x |
| src/dir-with-package-json/tst.js:13:10:13:10 | x |
| src/dir-with-package-lock-json/tst.js:10:7:10:7 | x |
| src/dir-with-package-lock-json/tst.js:15:7:15:7 | x |
| src/dir-without-package-json/tst.js:10:7:10:7 | x |
| src/dir-without-package-json/tst.js:15:7:15:7 | x |

View File

@@ -0,0 +1,44 @@
import javascript
import semmle.javascript.dependencies.Dependencies
import semmle.javascript.dependencies.SemVer
class SampleVersionSink extends DataFlow::Node {
SampleVersionSink() {
exists(
string dependencyName, Dependency dep, DependencySemVer vDep, string vFrom, string vTo,
string functionName, int argNumber
|
dependencyName = "a" and
vFrom = "0.1.0" and
vTo = "1.1.0" and
functionName = "m1" and
argNumber = 0
or
dependencyName = "b" and
vFrom = "1.1.0" and
vTo = "2.1.0" and
functionName = "m1" and
argNumber = 0
or
dependencyName = "c" and
vFrom = "0.1.0" and
vTo = "1.1.0" and
functionName = "m1" and
argNumber = 1
or
dependencyName = "d" and
vFrom = "0.1.0" and
vTo = "1.0.0" and
functionName = "m1" and
argNumber = 0
|
this = DataFlow::dependencyModuleImport(dep)
.getAMemberCall(functionName)
.getArgument(argNumber) and
dep.info(dependencyName, vDep) and
vDep.maybeBetween(vFrom, vTo)
)
}
}
select any(SampleVersionSink s)

View File

@@ -0,0 +1,6 @@
{
"dependencies": {
"a": "^1.0.0",
"c": ">=1.0.0"
}
}

View File

@@ -0,0 +1,9 @@
const a = require('a'),
c = require('c');
(function() {
a.m1(x); // flagged
c.m1(0, x); // flagged
});

View File

@@ -0,0 +1,8 @@
{
"dependencies": {
"a": "1.0.0",
"b": "1.0.0",
"c": "1.0.0",
"d": "1.0.0"
}
}

View File

@@ -0,0 +1,16 @@
const a = require('a'),
b = require('b'),
c = require('c'),
d = require('d');
(function() {
a.m1(x); // flagged
a.m2(x); // not flagged: other method
b.m1(x); // not flagged: early version
c.m1(x); // not flagged: other argument
c.m1(0, x); // flagged
d.m1(x); // not flagged: late version
});

View File

@@ -0,0 +1,8 @@
{
"dependencies": {
"a": "1.0.0",
"b": "1.0.0",
"c": "1.0.0",
"d": "1.0.0"
}
}

View File

@@ -0,0 +1,16 @@
const a = require('a'),
b = require('b'),
c = require('c'),
d = require('d');
(function() {
a.m1(x); // flagged
a.m2(x); // not flagged: other method
b.m1(x); // not flagged: early version
c.m1(x); // not flagged: other argument
c.m1(0, x); // flagged
d.m1(x); // not flagged: late version
});

View File

@@ -0,0 +1,16 @@
const a = require('a'),
b = require('b'),
c = require('c'),
d = require('d');
(function() {
a.m1(x); // not flagged: not a dependency
a.m2(x); // not flagged: not a dependency
b.m1(x); // flagged
c.m1(x); // not flagged: not a dependency
c.m1(0, x); // not flagged: not a dependency
d.m1(x); // flagged
});

View File

@@ -0,0 +1,6 @@
{
"dependencies": {
"b": "1.2.0",
"d": "0.2.0"
}
}