Skip to content

Commit cb55cf1

Browse files
authored
Merge pull request #22495 from hvitved/rust/path-resolution-crate-fallback-uniqueness
Rust: Make crate fallback logic more conservative in path resolution library
2 parents f1f964c + df7d49d commit cb55cf1

5 files changed

Lines changed: 98 additions & 103 deletions

File tree

go/ql/lib/semmle/go/dependencies/SemVer.qll

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ overlay[local?]
55
module;
66

77
import semmle.go.dependencies.Dependencies
8+
private import codeql.util.SemVer
89

910
/**
1011
* A SemVer-formatted version string in a dependency.
@@ -17,63 +18,42 @@ class DependencySemVer extends string {
1718

1819
DependencySemVer() {
1920
this = dep.getDepVersion() and
20-
normalized = normalizeSemver(this)
21+
normalized = padSemVer(this)
2122
}
2223

2324
/**
2425
* Holds if this version may be before `last`.
2526
*/
2627
bindingset[last]
27-
predicate maybeBefore(string last) { normalized < normalizeSemver(last) }
28+
predicate maybeBefore(string last) { normalized < padSemVer(last) }
2829

2930
/**
3031
* Holds if this version may be after `first`.
3132
*/
3233
bindingset[first]
33-
predicate maybeAfter(string first) { normalizeSemver(first) < normalized }
34+
predicate maybeAfter(string first) { padSemVer(first) < normalized }
3435

3536
/**
3637
* Holds if this version may be between `first` (inclusive) and `last` (exclusive).
3738
*/
3839
bindingset[first, last]
3940
predicate maybeBetween(string first, string last) {
40-
normalizeSemver(first) <= normalized and
41-
normalized < normalizeSemver(last)
41+
padSemVer(first) <= normalized and
42+
normalized < padSemVer(last)
4243
}
4344

4445
/**
4546
* Holds if this version is equivalent to `other`.
4647
*/
4748
bindingset[other]
48-
predicate is(string other) { normalized = normalizeSemver(other) }
49+
predicate is(string other) { normalized = padSemVer(other) }
4950

5051
/**
5152
* Gets the dependency that uses this string.
5253
*/
5354
Dependency getDependency() { result = dep }
5455
}
5556

56-
bindingset[str]
57-
private string leftPad(string str) { result = ("000" + str).suffix(str.length()) }
58-
59-
/**
60-
* Normalizes a SemVer string such that the lexicographical ordering
61-
* of two normalized strings is consistent with the SemVer ordering.
62-
*
63-
* Pre-release information and build metadata is not yet supported.
64-
*/
65-
bindingset[orig]
66-
private string normalizeSemver(string orig) {
67-
exists(string pattern, string major, string minor, string patch |
68-
pattern = "v?(\\d+)\\.(\\d+)\\.(\\d+)(\\D.*)?" and
69-
major = orig.regexpCapture(pattern, 1) and
70-
minor = orig.regexpCapture(pattern, 2) and
71-
patch = orig.regexpCapture(pattern, 3)
72-
|
73-
result = leftPad(major) + "." + leftPad(minor) + "." + leftPad(patch)
74-
)
75-
}
76-
7757
/**
7858
* A version string in a dependency that has a SemVer, but also contains a git commit SHA.
7959
*

javascript/ql/lib/semmle/javascript/dependencies/SemVer.qll

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import semmle.javascript.dependencies.Dependencies
6+
private import codeql.util.SemVer
67

78
/**
89
* A SemVer-formatted version string in a dependency.
@@ -15,59 +16,38 @@ class DependencySemVer extends string {
1516

1617
DependencySemVer() {
1718
dep.info(_, this) and
18-
normalized = normalizeSemver(this)
19+
normalized = padSemVer(this)
1920
}
2021

2122
/**
2223
* Holds if this version may be before `last`.
2324
*/
2425
bindingset[last]
25-
predicate maybeBefore(string last) { normalized < normalizeSemver(last) }
26+
predicate maybeBefore(string last) { normalized < padSemVer(last) }
2627

2728
/**
2829
* Holds if this version may be after `first`.
2930
*/
3031
bindingset[first]
31-
predicate maybeAfter(string first) { normalizeSemver(first) < normalized }
32+
predicate maybeAfter(string first) { padSemVer(first) < normalized }
3233

3334
/**
3435
* Holds if this version may be between `first` (inclusive) and `last` (exclusive).
3536
*/
3637
bindingset[first, last]
3738
predicate maybeBetween(string first, string last) {
38-
normalizeSemver(first) <= normalized and
39-
normalized < normalizeSemver(last)
39+
padSemVer(first) <= normalized and
40+
normalized < padSemVer(last)
4041
}
4142

4243
/**
4344
* Holds if this version is equivalent to `other`.
4445
*/
4546
bindingset[other]
46-
predicate is(string other) { normalized = normalizeSemver(other) }
47+
predicate is(string other) { normalized = padSemVer(other) }
4748

4849
/**
4950
* Gets the dependency that uses this string.
5051
*/
5152
Dependency getDependency() { result = dep }
5253
}
53-
54-
bindingset[str]
55-
private string leftPad(string str) { result = ("000" + str).suffix(str.length()) }
56-
57-
/**
58-
* Normalizes a SemVer string such that the lexicographical ordering
59-
* of two normalized strings is consistent with the SemVer ordering.
60-
*
61-
* Pre-release information and build metadata is not yet supported.
62-
*/
63-
bindingset[orig]
64-
private string normalizeSemver(string orig) {
65-
exists(string pattern, string major, string minor, string patch |
66-
pattern = "(\\d+)\\.(\\d+)\\.(\\d+)" and
67-
major = orig.regexpCapture(pattern, 1) and
68-
minor = orig.regexpCapture(pattern, 2) and
69-
patch = orig.regexpCapture(pattern, 3)
70-
|
71-
result = leftPad(major) + "." + leftPad(minor) + "." + leftPad(patch)
72-
)
73-
}

ruby/ql/lib/codeql/ruby/frameworks/Gemfile.qll

Lines changed: 6 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
private import codeql.ruby.AST
6+
private import codeql.util.SemVer
67

78
/**
89
* Provides classes and predicates for Gemfiles, including version constraint logic.
@@ -138,7 +139,7 @@ module Gemfile {
138139
exists(int thisMajor, int thisMinor, int otherMajor, int otherMinor |
139140
thisMajor = this.getVersion().getMajor() and
140141
thisMinor = this.getVersion().getMinor() and
141-
exists(string maj, string mi | normalizeSemver(other, _, maj, mi, _) |
142+
exists(string maj, string mi | exists(padSemVer(other, maj, mi, _)) |
142143
otherMajor = maj.toInt() and otherMinor = mi.toInt()
143144
)
144145
|
@@ -171,26 +172,26 @@ module Gemfile {
171172

172173
Version() {
173174
this = any(Gem c).getAVersionConstraint().getVersionString() and
174-
normalized = normalizeSemver(this)
175+
normalized = padSemVer(this)
175176
}
176177

177178
/**
178179
* Holds if this version is strictly before the version defined by `other`.
179180
*/
180181
bindingset[other]
181-
predicate before(string other) { normalized < normalizeSemver(other) }
182+
predicate before(string other) { normalized < padSemVer(other) }
182183

183184
/**
184185
* Holds if this versino is equal to the version defined by `other`.
185186
*/
186187
bindingset[other]
187-
predicate equal(string other) { normalized = normalizeSemver(other) }
188+
predicate equal(string other) { normalized = padSemVer(other) }
188189

189190
/**
190191
* Holds if this version is strictly after the version defined by `other`.
191192
*/
192193
bindingset[other]
193-
predicate after(string other) { normalized > normalizeSemver(other) }
194+
predicate after(string other) { normalized > padSemVer(other) }
194195

195196
/**
196197
* Holds if this version defines a patch number.
@@ -212,43 +213,4 @@ module Gemfile {
212213
*/
213214
int getPatch() { result = getPatch(normalized).toInt() }
214215
}
215-
216-
/**
217-
* Normalizes a SemVer string such that the lexicographical ordering
218-
* of two normalized strings is consistent with the SemVer ordering.
219-
*
220-
* Pre-release information and build metadata is not supported.
221-
*/
222-
bindingset[orig]
223-
private predicate normalizeSemver(
224-
string orig, string normalized, string major, string minor, string patch
225-
) {
226-
major = getMajor(orig) and
227-
(
228-
minor = getMinor(orig)
229-
or
230-
not exists(getMinor(orig)) and minor = "0"
231-
) and
232-
(
233-
patch = getPatch(orig)
234-
or
235-
not exists(getPatch(orig)) and patch = "0"
236-
) and
237-
normalized = leftPad(major) + "." + leftPad(minor) + "." + leftPad(patch)
238-
}
239-
240-
bindingset[orig]
241-
private string normalizeSemver(string orig) { normalizeSemver(orig, result, _, _, _) }
242-
243-
bindingset[s]
244-
private string getMajor(string s) { result = s.regexpCapture("(\\d+).*", 1) }
245-
246-
bindingset[s]
247-
private string getMinor(string s) { result = s.regexpCapture("(\\d+)\\.(\\d+).*", 2) }
248-
249-
bindingset[s]
250-
private string getPatch(string s) { result = s.regexpCapture("(\\d+)\\.(\\d+)\\.(\\d+).*", 3) }
251-
252-
bindingset[str]
253-
private string leftPad(string str) { result = ("000" + str).suffix(str.length()) }
254216
}

rust/ql/lib/codeql/rust/internal/PathResolution.qll

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ private import codeql.rust.elements.internal.CallExprImpl::Impl as CallExprImpl
4848
private import codeql.rust.internal.CachedStages
4949
private import codeql.rust.frameworks.stdlib.Builtins as Builtins
5050
private import codeql.util.Option
51+
private import codeql.util.SemVer
5152

5253
private newtype TNamespace =
5354
TTypeNamespace() or
@@ -568,6 +569,16 @@ class CrateItemNode extends NamedItemNode instanceof Crate {
568569
)
569570
}
570571

572+
pragma[nomagic]
573+
predicate isLatestVersion(string name) {
574+
this =
575+
max(CrateItemNode c, string ver |
576+
name = c.getName() and ver = padSemVer(c.(Crate).getVersion())
577+
|
578+
c order by ver
579+
)
580+
}
581+
571582
override string getName() { result = Crate.super.getName() }
572583

573584
override Namespace getNamespace() {
@@ -1529,11 +1540,11 @@ private predicate crateDependencyEdge(SourceFileItemNode file, string name, Crat
15291540
crateDependency(file, name, dep)
15301541
or
15311542
// As a fallback, give all files access to crates that do not conflict with known dependencies
1532-
// and declarations. This is in order to workaround incomplete crate dependency information
1533-
// provided by the extractor, as well as `CrateItemNode.getASourceFile()` being unable to map
1534-
// a given file to its crate (for example, if the file is `mod` imported inside a macro that the
1535-
// extractor is unable to expand).
1536-
name = dep.getName() and
1543+
// and declarations, as long as those crates have a unique latest version.
1544+
// This is in order to workaround incomplete crate dependency information provided by the extractor,
1545+
// as well as `CrateItemNode.getASourceFile()` being unable to map a given file to its crate (for
1546+
// example, if the file is `mod` imported inside a macro that the extractor is unable to expand).
1547+
dep = unique(CrateItemNode dep0 | dep0.isLatestVersion(name)) and
15371548
not hasDeclOrDep(file, name)
15381549
}
15391550

@@ -2385,6 +2396,11 @@ private module Debug {
23852396
useImportEdge(use, name, item, kind)
23862397
}
23872398

2399+
predicate debugCrateDependencyEdge(SourceFileItemNode file, string name, CrateItemNode dep) {
2400+
file = getRelevantLocatable() and
2401+
crateDependencyEdge(file, name, dep)
2402+
}
2403+
23882404
ItemNode debugGetASuccessor(ItemNode i, string name, SuccessorKind kind) {
23892405
i = getRelevantLocatable() and
23902406
result = i.getASuccessor(name, kind, _)

shared/util/codeql/util/SemVer.qll

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Provides logic for working SemVer (Semantic Versioning).
3+
*/
4+
overlay[local?]
5+
module;
6+
7+
bindingset[str]
8+
private string leftPad(string str) { result = ("0000" + str).suffix(str.length()) }
9+
10+
/**
11+
* Gets the major number of a SemVer string.
12+
*/
13+
bindingset[s]
14+
string getMajor(string s) { result = s.regexpCapture("v?(\\d+).*", 1) }
15+
16+
/**
17+
* Gets the minor number of a SemVer string.
18+
*/
19+
bindingset[s]
20+
string getMinor(string s) { result = s.regexpCapture("v?(\\d+)\\.(\\d+).*", 2) }
21+
22+
/**
23+
* Gets the patch number of a SemVer string.
24+
*/
25+
bindingset[s]
26+
string getPatch(string s) { result = s.regexpCapture("v?(\\d+)\\.(\\d+)\\.(\\d+).*", 3) }
27+
28+
/**
29+
* Normalizes a SemVer string such that the lexicographical ordering
30+
* of two normalized strings is consistent with the SemVer ordering.
31+
*
32+
* Pre-release information and build metadata is not yet supported.
33+
*/
34+
bindingset[orig]
35+
string padSemVer(string orig, string major, string minor, string patch) {
36+
major = getMajor(orig) and
37+
(
38+
minor = getMinor(orig)
39+
or
40+
not exists(getMinor(orig)) and minor = "0"
41+
) and
42+
(
43+
patch = getPatch(orig)
44+
or
45+
not exists(getPatch(orig)) and patch = "0"
46+
) and
47+
result = leftPad(major) + "." + leftPad(minor) + "." + leftPad(patch)
48+
}
49+
50+
/**
51+
* Normalizes a SemVer string such that the lexicographical ordering
52+
* of two normalized strings is consistent with the SemVer ordering.
53+
*
54+
* Pre-release information and build metadata is not yet supported.
55+
*/
56+
bindingset[orig]
57+
string padSemVer(string orig) { result = padSemVer(orig, _, _, _) }

0 commit comments

Comments
 (0)