Skip to content

Commit 739adcd

Browse files
committed
Add Folder::Resolve as a generalisation of Folder::Append
1 parent 825c4c1 commit 739adcd

1 file changed

Lines changed: 110 additions & 26 deletions

File tree

shared/util/codeql/util/FileSystem.qll

Lines changed: 110 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -232,46 +232,130 @@ module Make<InputSig Input> {
232232

233233
/** Provides the `append` predicate for appending a relative path onto a folder. */
234234
module Append<shouldAppendSig/2 shouldAppend> {
235+
private module Config implements ResolveSig {
236+
predicate shouldResolve(Container c, string name) { shouldAppend(c, name) }
237+
}
238+
239+
predicate append = Resolve<Config>::resolve/2;
240+
}
241+
242+
/**
243+
* Signature for modules to pass to `Resolve`.
244+
*/
245+
signature module ResolveSig {
246+
/**
247+
* Holds if `path` should be resolved to a file or folder, relative to `base`.
248+
*/
249+
predicate shouldResolve(Container base, string path);
250+
251+
/**
252+
* Gets an additional file or folder to consider a child of `base`.
253+
*/
254+
default Container getAnAdditionalChild(Container base, string name) { none() }
255+
256+
/**
257+
* Holds if `component` may be treated as `.` if it does not match a child.
258+
*/
259+
default predicate isOptionalPathComponent(string component) { none() }
260+
261+
/**
262+
* Holds if globs should be interpreted in the paths being resolved.
263+
*
264+
* The following types of globs are supported:
265+
* - `*` (matches any child)
266+
* - `**` (matches any child recursively)
267+
* - Complex patterns like `foo-*.txt` are also supported
268+
*/
269+
default predicate allowGlobs() { none() }
270+
}
271+
272+
/**
273+
* Provides a mechanism for resolving file paths relative to a given directory.
274+
*/
275+
module Resolve<ResolveSig Config> {
276+
private import Config
277+
235278
pragma[nomagic]
236-
private string getComponent(string relativePath, int i) {
237-
shouldAppend(_, relativePath) and
238-
result = relativePath.replaceAll("\\", "/").regexpFind("[^/]+", i, _)
279+
private string getPathSegment(string path, int n) {
280+
shouldResolve(_, path) and
281+
result = path.replaceAll("\\", "/").splitAt("/", n)
239282
}
240283

241-
private int getNumberOfComponents(string relativePath) {
242-
result = strictcount(int i | exists(getComponent(relativePath, i)) | i)
284+
pragma[nomagic]
285+
private string getPathSegmentAsGlobRegexp(string segment) {
286+
allowGlobs() and
287+
segment = getPathSegment(_, _) and
288+
segment.matches("%*%") and
289+
not segment = ["*", "**"] and // these are special-cased
290+
result = segment.regexpReplaceAll("[^a-zA-Z0-9*]", "\\\\$0").replaceAll("*", ".*")
291+
}
292+
293+
pragma[nomagic]
294+
private int getNumPathSegment(string path) {
295+
result = strictcount(int n | exists(getPathSegment(path, n)))
296+
}
297+
298+
private Container getChild(Container base, string name) {
299+
result = getAChildContainer(base, name)
243300
or
244-
relativePath = "" and
245-
result = 0
301+
result = getAnAdditionalChild(base, name)
246302
}
247303

248304
pragma[nomagic]
249-
private Container appendStep(Folder f, string relativePath, int i) {
250-
i = -1 and
251-
shouldAppend(f, relativePath) and
252-
result = f
305+
private Container resolve(Container base, string path, int n) {
306+
shouldResolve(base, path) and n = 0 and result = base
253307
or
254-
exists(Container mid, string comp |
255-
mid = appendStep(f, relativePath, i - 1) and
256-
comp = getComponent(relativePath, i) and
257-
if comp = ".."
258-
then result = mid.getParentContainer()
259-
else
260-
if comp = "."
261-
then result = mid
262-
else result = getAChildContainer(mid, comp)
308+
exists(Container current, string segment |
309+
current = resolve(base, path, n - 1) and
310+
segment = getPathSegment(path, n - 1)
311+
|
312+
result = getChild(current, segment)
313+
or
314+
segment = [".", ""] and
315+
result = current
316+
or
317+
segment = ".." and
318+
result = current.getParentContainer()
319+
or
320+
isOptionalPathComponent(segment) and
321+
not exists(getChild(current, segment)) and
322+
result = current
323+
or
324+
allowGlobs() and
325+
(
326+
segment = "*" and
327+
result = getChild(current, _)
328+
or
329+
segment = "**" and // allow empty match
330+
result = current
331+
or
332+
exists(string name |
333+
result = getChild(current, name) and
334+
name.regexpMatch(getPathSegmentAsGlobRegexp(segment))
335+
)
336+
)
337+
)
338+
or
339+
exists(Container current, string segment |
340+
current = resolve(base, path, n) and
341+
segment = getPathSegment(path, n)
342+
|
343+
// Follow child without advancing 'n'
344+
allowGlobs() and
345+
segment = "**" and
346+
result = getChild(current, _)
263347
)
264348
}
265349

266350
/**
267-
* Gets the file or folder obtained by appending `relativePath` onto `f`.
351+
* Gets the file or folder that `path` resolves to when resolved from `base`.
352+
*
353+
* Only has results for the `(base, path)` pairs provided by `shouldResolve`
354+
* in the instantiation of this module.
268355
*/
269356
pragma[nomagic]
270-
Container append(Folder f, string relativePath) {
271-
exists(int last |
272-
last = getNumberOfComponents(relativePath) - 1 and
273-
result = appendStep(f, relativePath, last)
274-
)
357+
Container resolve(Container base, string path) {
358+
result = resolve(base, path, getNumPathSegment(path))
275359
}
276360
}
277361
}

0 commit comments

Comments
 (0)