diff --git a/csharp/ql/lib/qlpack.yml b/csharp/ql/lib/qlpack.yml
index 3e0b633019af..3f118d8115f4 100644
--- a/csharp/ql/lib/qlpack.yml
+++ b/csharp/ql/lib/qlpack.yml
@@ -8,6 +8,7 @@ upgrades: upgrades
dependencies:
codeql/ssa: ${workspace}
codeql/tutorial: ${workspace}
+ codeql/util: ${workspace}
dataExtensions:
- ext/*.model.yml
- ext/generated/*.model.yml
diff --git a/csharp/ql/lib/semmle/code/csharp/File.qll b/csharp/ql/lib/semmle/code/csharp/File.qll
index e4e0d3c6c26a..79406aec2f6c 100644
--- a/csharp/ql/lib/semmle/code/csharp/File.qll
+++ b/csharp/ql/lib/semmle/code/csharp/File.qll
@@ -3,184 +3,34 @@
*/
private import Comments
+private import codeql.util.FileSystem
-/** A file or folder. */
-class Container extends @container {
- /**
- * Gets the absolute, canonical path of this container, using forward slashes
- * as path separator.
- *
- * The path starts with a _root prefix_ followed by zero or more _path
- * segments_ separated by forward slashes.
- *
- * The root prefix is of one of the following forms:
- *
- * 1. A single forward slash `/` (Unix-style)
- * 2. An upper-case drive letter followed by a colon and a forward slash,
- * such as `C:/` (Windows-style)
- * 3. Two forward slashes, a computer name, and then another forward slash,
- * such as `//FileServer/` (UNC-style)
- *
- * Path segments are never empty (that is, absolute paths never contain two
- * contiguous slashes, except as part of a UNC-style root prefix). Also, path
- * segments never contain forward slashes, and no path segment is of the
- * form `.` (one dot) or `..` (two dots).
- *
- * Note that an absolute path never ends with a forward slash, except if it is
- * a bare root prefix, that is, the path has no path segments. A container
- * whose absolute path has no segments is always a `Folder`, not a `File`.
- */
- string getAbsolutePath() { none() }
-
- /**
- * Gets a URL representing the location of this container.
- *
- * For more information see [Providing URLs](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls).
- */
- string getURL() { none() }
-
- /**
- * Gets the relative path of this file or folder from the root folder of the
- * analyzed source location. The relative path of the root folder itself is
- * the empty string.
- *
- * This has no result if the container is outside the source root, that is,
- * if the root folder is not a reflexive, transitive parent of this container.
- */
- string getRelativePath() {
- exists(string absPath, string pref |
- absPath = this.getAbsolutePath() and sourceLocationPrefix(pref)
- |
- absPath = pref and result = ""
- or
- absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
- not result.matches("/%")
- )
- }
-
- /**
- * Gets the base name of this container including extension, that is, the last
- * segment of its absolute path, or the empty string if it has no segments.
- *
- * Here are some examples of absolute paths and the corresponding base names
- * (surrounded with quotes to avoid ambiguity):
- *
- *
- *
Absolute path
Base name
- *
"/tmp/tst.cs"
"tst.cs"
- *
"C:/Program Files (x86)"
"Program Files (x86)"
- *
"/"
""
- *
"C:/"
""
- *
"D:/"
""
- *
"//FileServer/"
""
- *
- */
- string getBaseName() {
- result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
- }
-
- /**
- * Gets the extension of this container, that is, the suffix of its base name
- * after the last dot character, if any.
- *
- * In particular,
- *
- * - if the name does not include a dot, there is no extension, so this
- * predicate has no result;
- * - if the name ends in a dot, the extension is the empty string;
- * - if the name contains multiple dots, the extension follows the last dot.
- *
- * Here are some examples of absolute paths and the corresponding extensions
- * (surrounded with quotes to avoid ambiguity):
- *
- *
- *
Absolute path
Extension
- *
"/tmp/tst.cs"
"cs"
- *
"/tmp/.classpath"
"classpath"
- *
"/bin/bash"
not defined
- *
"/tmp/tst2."
""
- *
"/tmp/x.tar.gz"
"gz"
- *
- */
- string getExtension() {
- result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
- }
-
- /**
- * Gets the stem of this container, that is, the prefix of its base name up to
- * (but not including) the last dot character if there is one, or the entire
- * base name if there is not.
- *
- * Here are some examples of absolute paths and the corresponding stems
- * (surrounded with quotes to avoid ambiguity):
- *
- *
- *
Absolute path
Stem
- *
"/tmp/tst.cs"
"tst"
- *
"/tmp/.classpath"
""
- *
"/bin/bash"
"bash"
- *
"/tmp/tst2."
"tst2"
- *
"/tmp/x.tar.gz"
"x.tar"
- *
- */
- string getStem() {
- result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1)
- }
-
- /** Gets the parent container of this file or folder, if any. */
- Container getParentContainer() { containerparent(result, this) }
+private module Input implements InputSig {
+ abstract class ContainerBase extends @container {
+ abstract string getAbsolutePath();
- /** Gets a file or sub-folder in this container. */
- Container getAChildContainer() { this = result.getParentContainer() }
+ ContainerBase getParentContainer() { containerparent(result, this) }
- /** Gets a file in this container. */
- File getAFile() { result = this.getAChildContainer() }
-
- /** Gets the file in this container that has the given `baseName`, if any. */
- File getFile(string baseName) {
- result = this.getAFile() and
- result.getBaseName() = baseName
+ string toString() { result = this.getAbsolutePath() }
}
- /** Gets a sub-folder in this container. */
- Folder getAFolder() { result = this.getAChildContainer() }
-
- /** Gets the sub-folder in this container that has the given `baseName`, if any. */
- Folder getFolder(string baseName) {
- result = this.getAFolder() and
- result.getBaseName() = baseName
+ class FolderBase extends ContainerBase, @folder {
+ override string getAbsolutePath() { folders(this, result) }
}
- /** Gets the file or sub-folder in this container that has the given `name`, if any. */
- Container getChildContainer(string name) {
- result = this.getAChildContainer() and
- result.getBaseName() = name
+ class FileBase extends ContainerBase, @file {
+ override string getAbsolutePath() { files(this, result) }
}
- /** Gets the file in this container that has the given `stem` and `extension`, if any. */
- File getFile(string stem, string extension) {
- result = this.getAChildContainer() and
- result.getStem() = stem and
- result.getExtension() = extension
- }
+ predicate hasSourceLocationPrefix = sourceLocationPrefix/1;
+}
- /** Gets a sub-folder contained in this container. */
- Folder getASubFolder() { result = this.getAChildContainer() }
+private module Impl = Make;
- /**
- * Gets a textual representation of the path of this container.
- *
- * This is the absolute path of the container.
- */
- string toString() { result = this.getAbsolutePath() }
-}
+class Container = Impl::Container;
/** A folder. */
-class Folder extends Container, @folder {
- override string getAbsolutePath() { folders(this, result) }
-
- override string getURL() { result = "folder://" + this.getAbsolutePath() }
-}
+class Folder extends Container, Impl::Folder { }
bindingset[flag]
private predicate fileHasExtractionFlag(File f, int flag) {
@@ -191,9 +41,7 @@ private predicate fileHasExtractionFlag(File f, int flag) {
}
/** A file. */
-class File extends Container, @file {
- override string getAbsolutePath() { files(this, result) }
-
+class File extends Container, Impl::File {
/** Gets the number of lines in this file. */
int getNumberOfLines() { numlines(this, result, _, _) }
diff --git a/ruby/ql/lib/codeql/files/FileSystem.qll b/ruby/ql/lib/codeql/files/FileSystem.qll
index 552b85a4673f..f6a5f9592878 100644
--- a/ruby/ql/lib/codeql/files/FileSystem.qll
+++ b/ruby/ql/lib/codeql/files/FileSystem.qll
@@ -1,177 +1,37 @@
/** Provides classes for working with files and folders. */
private import codeql.Locations
+private import codeql.util.FileSystem
-/** A file or folder. */
-abstract class Container extends @container {
- /** Gets a file or sub-folder in this container. */
- Container getAChildContainer() { this = result.getParentContainer() }
+private module Input implements InputSig {
+ abstract class ContainerBase extends @container {
+ abstract string getAbsolutePath();
- /** Gets a file in this container. */
- File getAFile() { result = this.getAChildContainer() }
+ ContainerBase getParentContainer() { containerparent(result, this) }
- /** Gets a sub-folder in this container. */
- Folder getAFolder() { result = this.getAChildContainer() }
-
- /**
- * Gets the absolute, canonical path of this container, using forward slashes
- * as path separator.
- *
- * The path starts with a _root prefix_ followed by zero or more _path
- * segments_ separated by forward slashes.
- *
- * The root prefix is of one of the following forms:
- *
- * 1. A single forward slash `/` (Unix-style)
- * 2. An upper-case drive letter followed by a colon and a forward slash,
- * such as `C:/` (Windows-style)
- * 3. Two forward slashes, a computer name, and then another forward slash,
- * such as `//FileServer/` (UNC-style)
- *
- * Path segments are never empty (that is, absolute paths never contain two
- * contiguous slashes, except as part of a UNC-style root prefix). Also, path
- * segments never contain forward slashes, and no path segment is of the
- * form `.` (one dot) or `..` (two dots).
- *
- * Note that an absolute path never ends with a forward slash, except if it is
- * a bare root prefix, that is, the path has no path segments. A container
- * whose absolute path has no segments is always a `Folder`, not a `File`.
- */
- abstract string getAbsolutePath();
-
- /**
- * Gets the base name of this container including extension, that is, the last
- * segment of its absolute path, or the empty string if it has no segments.
- *
- * Here are some examples of absolute paths and the corresponding base names
- * (surrounded with quotes to avoid ambiguity):
- *
- *
- *
Absolute path
Base name
- *
"/tmp/tst.go"
"tst.go"
- *
"C:/Program Files (x86)"
"Program Files (x86)"
- *
"/"
""
- *
"C:/"
""
- *
"D:/"
""
- *
"//FileServer/"
""
- *
- */
- string getBaseName() {
- result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
- }
-
- /**
- * Gets the extension of this container, that is, the suffix of its base name
- * after the last dot character, if any.
- *
- * In particular,
- *
- * - if the name does not include a dot, there is no extension, so this
- * predicate has no result;
- * - if the name ends in a dot, the extension is the empty string;
- * - if the name contains multiple dots, the extension follows the last dot.
- *
- * Here are some examples of absolute paths and the corresponding extensions
- * (surrounded with quotes to avoid ambiguity):
- *
- *
- *
Absolute path
Extension
- *
"/tmp/tst.go"
"go"
- *
"/tmp/.classpath"
"classpath"
- *
"/bin/bash"
not defined
- *
"/tmp/tst2."
""
- *
"/tmp/x.tar.gz"
"gz"
- *
- */
- string getExtension() {
- result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
+ string toString() { result = this.getAbsolutePath() }
}
- /** Gets the file in this container that has the given `baseName`, if any. */
- File getFile(string baseName) {
- result = this.getAFile() and
- result.getBaseName() = baseName
+ class FolderBase extends ContainerBase, @folder {
+ override string getAbsolutePath() { folders(this, result) }
}
- /** Gets the sub-folder in this container that has the given `baseName`, if any. */
- Folder getFolder(string baseName) {
- result = this.getAFolder() and
- result.getBaseName() = baseName
- }
-
- /** Gets the parent container of this file or folder, if any. */
- Container getParentContainer() { containerparent(result, this) }
-
- /**
- * Gets the relative path of this file or folder from the root folder of the
- * analyzed source location. The relative path of the root folder itself is
- * the empty string.
- *
- * This has no result if the container is outside the source root, that is,
- * if the root folder is not a reflexive, transitive parent of this container.
- */
- string getRelativePath() {
- exists(string absPath, string pref |
- absPath = this.getAbsolutePath() and sourceLocationPrefix(pref)
- |
- absPath = pref and result = ""
- or
- absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
- not result.matches("/%")
- )
+ class FileBase extends ContainerBase, @file {
+ override string getAbsolutePath() { files(this, result) }
}
- /**
- * Gets the stem of this container, that is, the prefix of its base name up to
- * (but not including) the last dot character if there is one, or the entire
- * base name if there is not.
- *
- * Here are some examples of absolute paths and the corresponding stems
- * (surrounded with quotes to avoid ambiguity):
- *
- *
- *
Absolute path
Stem
- *
"/tmp/tst.go"
"tst"
- *
"/tmp/.classpath"
""
- *
"/bin/bash"
"bash"
- *
"/tmp/tst2."
"tst2"
- *
"/tmp/x.tar.gz"
"x.tar"
- *
- */
- string getStem() {
- result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1)
- }
+ predicate hasSourceLocationPrefix = sourceLocationPrefix/1;
+}
- /**
- * Gets a URL representing the location of this container.
- *
- * For more information see https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls.
- */
- abstract string getURL();
+private module Impl = Make;
- /**
- * Gets a textual representation of the path of this container.
- *
- * This is the absolute path of the container.
- */
- string toString() { result = this.getAbsolutePath() }
-}
+class Container = Impl::Container;
/** A folder. */
-class Folder extends Container, @folder {
- override string getAbsolutePath() { folders(this, result) }
-
- /** Gets the URL of this folder. */
- override string getURL() { result = "folder://" + this.getAbsolutePath() }
-}
+class Folder extends Container, Impl::Folder { }
/** A file. */
-class File extends Container, @file {
- override string getAbsolutePath() { files(this, result) }
-
- /** Gets the URL of this file. */
- override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
-
+class File extends Container, Impl::File {
/** Holds if this file was extracted from ordinary source code. */
predicate fromSource() { any() }
}
diff --git a/shared/util/codeql/util/FileSystem.qll b/shared/util/codeql/util/FileSystem.qll
new file mode 100644
index 000000000000..f685cf9c3e34
--- /dev/null
+++ b/shared/util/codeql/util/FileSystem.qll
@@ -0,0 +1,212 @@
+/** Provides classes for working with files and folders. */
+
+/** Provides the input specification of the files and folders implementation. */
+signature module InputSig {
+ /**
+ * A base class for files and folders.
+ *
+ * Typically `@container`.
+ */
+ class ContainerBase {
+ /**
+ * Gets the absolute path of this container.
+ *
+ * Typically `containerparent(result, this)`.
+ */
+ string getAbsolutePath();
+
+ /** Gets the parent container of this container, if any. */
+ ContainerBase getParentContainer();
+ }
+
+ /**
+ * A base class for files.
+ *
+ * Typically `@file`.
+ */
+ class FileBase extends ContainerBase;
+
+ /**
+ * A base class for folders.
+ *
+ * Typically `@folder`.
+ */
+ class FolderBase extends ContainerBase;
+
+ /**
+ * Holds if `s` is the source location prefix.
+ *
+ * Typically `sourceLocationPrefix(s)`.
+ */
+ predicate hasSourceLocationPrefix(string s);
+}
+
+/** Provides a class hierarchy for working with files and folders. */
+module Make {
+ /** A file or folder. */
+ class Container instanceof Input::ContainerBase {
+ /** Gets a file or sub-folder in this container. */
+ Container getAChildContainer() { this = result.getParentContainer() }
+
+ /** Gets a file in this container. */
+ File getAFile() { result = this.getAChildContainer() }
+
+ /** Gets a sub-folder in this container. */
+ Folder getAFolder() { result = this.getAChildContainer() }
+
+ /**
+ * Gets the absolute, canonical path of this container, using forward slashes
+ * as path separator.
+ *
+ * The path starts with a _root prefix_ followed by zero or more _path
+ * segments_ separated by forward slashes.
+ *
+ * The root prefix is of one of the following forms:
+ *
+ * 1. A single forward slash `/` (Unix-style)
+ * 2. An upper-case drive letter followed by a colon and a forward slash,
+ * such as `C:/` (Windows-style)
+ * 3. Two forward slashes, a computer name, and then another forward slash,
+ * such as `//FileServer/` (UNC-style)
+ *
+ * Path segments are never empty (that is, absolute paths never contain two
+ * contiguous slashes, except as part of a UNC-style root prefix). Also, path
+ * segments never contain forward slashes, and no path segment is of the
+ * form `.` (one dot) or `..` (two dots).
+ *
+ * Note that an absolute path never ends with a forward slash, except if it is
+ * a bare root prefix, that is, the path has no path segments. A container
+ * whose absolute path has no segments is always a `Folder`, not a `File`.
+ */
+ string getAbsolutePath() { result = super.getAbsolutePath() }
+
+ /**
+ * Gets the base name of this container including extension, that is, the last
+ * segment of its absolute path, or the empty string if it has no segments.
+ *
+ * Here are some examples of absolute paths and the corresponding base names
+ * (surrounded with quotes to avoid ambiguity):
+ *
+ *
+ *
Absolute path
Base name
+ *
"/tmp/tst.txt"
"tst.txt"
+ *
"C:/Program Files (x86)"
"Program Files (x86)"
+ *
"/"
""
+ *
"C:/"
""
+ *
"D:/"
""
+ *
"//FileServer/"
""
+ *
+ */
+ string getBaseName() {
+ result = this.getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
+ }
+
+ /**
+ * Gets the extension of this container, that is, the suffix of its base name
+ * after the last dot character, if any.
+ *
+ * In particular,
+ *
+ * - if the name does not include a dot, there is no extension, so this
+ * predicate has no result;
+ * - if the name ends in a dot, the extension is the empty string;
+ * - if the name contains multiple dots, the extension follows the last dot.
+ *
+ * Here are some examples of absolute paths and the corresponding extensions
+ * (surrounded with quotes to avoid ambiguity):
+ *
+ *
+ *
Absolute path
Extension
+ *
"/tmp/tst.txt"
"txt"
+ *
"/tmp/.classpath"
"classpath"
+ *
"/bin/bash"
not defined
+ *
"/tmp/tst2."
""
+ *
"/tmp/x.tar.gz"
"gz"
+ *
+ */
+ string getExtension() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
+ }
+
+ /** Gets the file in this container that has the given `baseName`, if any. */
+ File getFile(string baseName) {
+ result = this.getAFile() and
+ result.getBaseName() = baseName
+ }
+
+ /** Gets the sub-folder in this container that has the given `baseName`, if any. */
+ Folder getFolder(string baseName) {
+ result = this.getAFolder() and
+ result.getBaseName() = baseName
+ }
+
+ /** Gets the parent container of this file or folder, if any. */
+ Container getParentContainer() { result = super.getParentContainer() }
+
+ /**
+ * Gets the relative path of this file or folder from the root folder of the
+ * analyzed source location. The relative path of the root folder itself is
+ * the empty string.
+ *
+ * This has no result if the container is outside the source root, that is,
+ * if the root folder is not a reflexive, transitive parent of this container.
+ */
+ string getRelativePath() {
+ exists(string absPath, string pref |
+ absPath = this.getAbsolutePath() and Input::hasSourceLocationPrefix(pref)
+ |
+ absPath = pref and result = ""
+ or
+ absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
+ not result.matches("/%")
+ )
+ }
+
+ /**
+ * Gets the stem of this container, that is, the prefix of its base name up to
+ * (but not including) the last dot character if there is one, or the entire
+ * base name if there is not.
+ *
+ * Here are some examples of absolute paths and the corresponding stems
+ * (surrounded with quotes to avoid ambiguity):
+ *
+ *
+ *
Absolute path
Stem
+ *
"/tmp/tst.txt"
"tst"
+ *
"/tmp/.classpath"
""
+ *
"/bin/bash"
"bash"
+ *
"/tmp/tst2."
"tst2"
+ *
"/tmp/x.tar.gz"
"x.tar"
+ *
+ */
+ string getStem() {
+ result = this.getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1)
+ }
+
+ /**
+ * Gets a URL representing the location of this container.
+ *
+ * For more information see https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls.
+ */
+ string getURL() { none() }
+
+ /**
+ * Gets a textual representation of the path of this container.
+ *
+ * This is the absolute path of the container.
+ */
+ string toString() { result = this.getAbsolutePath() }
+ }
+
+ /** A folder. */
+ class Folder extends Container instanceof Input::FolderBase {
+ /** Gets the URL of this folder. */
+ override string getURL() { result = "folder://" + this.getAbsolutePath() }
+ }
+
+ /** A file. */
+ class File extends Container instanceof Input::FileBase {
+ /** Gets the URL of this file. */
+ override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
+ }
+}