mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
JS: introduce SemVer matching library
This commit is contained in:
74
javascript/ql/src/semmle/javascript/dependencies/SemVer.qll
Normal file
74
javascript/ql/src/semmle/javascript/dependencies/SemVer.qll
Normal 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)
|
||||
)
|
||||
}
|
||||
@@ -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 |
|
||||
@@ -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)
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"a": "^1.0.0",
|
||||
"c": ">=1.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
const a = require('a'),
|
||||
c = require('c');
|
||||
|
||||
|
||||
(function() {
|
||||
a.m1(x); // flagged
|
||||
|
||||
c.m1(0, x); // flagged
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"a": "1.0.0",
|
||||
"b": "1.0.0",
|
||||
"c": "1.0.0",
|
||||
"d": "1.0.0"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"a": "1.0.0",
|
||||
"b": "1.0.0",
|
||||
"c": "1.0.0",
|
||||
"d": "1.0.0"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"b": "1.2.0",
|
||||
"d": "0.2.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user