diff --git a/cpp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs b/cpp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs index 846d8333030..06057b971b5 100644 --- a/cpp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs +++ b/cpp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs @@ -1,5 +1,6 @@ using Xunit; using Semmle.Autobuild.Shared; +using Semmle.Util; using System.Collections.Generic; using System; using System.Linq; @@ -75,6 +76,15 @@ namespace Semmle.Autobuild.Cpp.Tests throw new ArgumentException("Missing RunProcess " + pattern); } + int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary? env, BuildOutputHandler onOutput, BuildOutputHandler onError) + { + var ret = (this as IBuildActions).RunProcess(cmd, args, workingDirectory, env, out var stdout); + + stdout.ForEach(line => onOutput(line)); + + return ret; + } + public IList DirectoryDeleteIn = new List(); void IBuildActions.DirectoryDelete(string dir, bool recursive) @@ -184,6 +194,15 @@ namespace Semmle.Autobuild.Cpp.Tests if (!DownloadFiles.Contains((address, fileName))) throw new ArgumentException($"Missing DownloadFile, {address}, {fileName}"); } + + public IDiagnosticsWriter CreateDiagnosticsWriter(string filename) => new TestDiagnosticWriter(); + } + + internal class TestDiagnosticWriter : IDiagnosticsWriter + { + public IList Diagnostics { get; } = new List(); + + public void AddEntry(DiagnosticMessage message) => this.Diagnostics.Add(message); } /// @@ -243,6 +262,7 @@ namespace Semmle.Autobuild.Cpp.Tests Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_TRAP_DIR"] = ""; Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_SOURCE_ARCHIVE_DIR"] = ""; Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_ROOT"] = $@"C:\codeql\{codeqlUpperLanguage.ToLowerInvariant()}"; + Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_DIAGNOSTIC_DIR"] = ""; Actions.GetEnvironmentVariable["CODEQL_JAVA_HOME"] = @"C:\codeql\tools\java"; Actions.GetEnvironmentVariable["CODEQL_PLATFORM"] = "win64"; Actions.GetEnvironmentVariable["SEMMLE_DIST"] = @"C:\odasa"; diff --git a/cpp/autobuilder/Semmle.Autobuild.Cpp/CppAutobuilder.cs b/cpp/autobuilder/Semmle.Autobuild.Cpp/CppAutobuilder.cs index 1503dedb376..e3853b44a0c 100644 --- a/cpp/autobuilder/Semmle.Autobuild.Cpp/CppAutobuilder.cs +++ b/cpp/autobuilder/Semmle.Autobuild.Cpp/CppAutobuilder.cs @@ -1,4 +1,5 @@ using Semmle.Autobuild.Shared; +using Semmle.Util; namespace Semmle.Autobuild.Cpp { @@ -21,7 +22,7 @@ namespace Semmle.Autobuild.Cpp public class CppAutobuilder : Autobuilder { - public CppAutobuilder(IBuildActions actions, CppAutobuildOptions options) : base(actions, options) { } + public CppAutobuilder(IBuildActions actions, CppAutobuildOptions options) : base(actions, options, new DiagnosticClassifier()) { } public override BuildScript GetBuildScript() { diff --git a/cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll index 0afcba55582..d574f95d181 100644 --- a/cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll @@ -959,6 +959,17 @@ module Impl { not inBarrier(p) } + /** + * Gets an additional term that is added to `branch` and `join` when deciding whether + * the amount of forward or backward branching is within the limit specified by the + * configuration. + */ + pragma[nomagic] + private int getLanguageSpecificFlowIntoCallNodeCand1(ArgNodeEx arg, ParamNodeEx p) { + flowIntoCallNodeCand1(_, arg, p) and + result = getAdditionalFlowIntoCallNodeTerm(arg.projectToNode(), p.projectToNode()) + } + /** * Gets the amount of forward branching on the origin of a cross-call path * edge in the graph of paths between sources and sinks that ignores call @@ -968,6 +979,7 @@ module Impl { private int branch(NodeEx n1) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + + sum(ParamNodeEx p1 | | getLanguageSpecificFlowIntoCallNodeCand1(n1, p1)) } /** @@ -979,6 +991,7 @@ module Impl { private int join(NodeEx n2) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + + sum(ArgNodeEx arg2 | | getLanguageSpecificFlowIntoCallNodeCand1(arg2, n2)) } /** diff --git a/cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index 58569ef2425..a9c067fcc0a 100644 --- a/cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/experimental/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -565,3 +565,12 @@ private class MyConsistencyConfiguration extends Consistency::ConsistencyConfigu any() } } + +/** + * Gets an additional term that is added to the `join` and `branch` computations to reflect + * an additional forward or backwards branching factor that is not taken into account + * when calculating the (virtual) dispatch cost. + * + * Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter. + */ +int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() } diff --git a/cpp/ql/lib/semmle/code/cpp/Declaration.qll b/cpp/ql/lib/semmle/code/cpp/Declaration.qll index d7cc581c384..6c27ef7b3ec 100644 --- a/cpp/ql/lib/semmle/code/cpp/Declaration.qll +++ b/cpp/ql/lib/semmle/code/cpp/Declaration.qll @@ -186,7 +186,7 @@ class Declaration extends Locatable, @declaration { predicate hasDefinition() { exists(this.getDefinition()) } /** DEPRECATED: Use `hasDefinition` instead. */ - predicate isDefined() { this.hasDefinition() } + deprecated predicate isDefined() { this.hasDefinition() } /** Gets the preferred location of this declaration, if any. */ override Location getLocation() { none() } diff --git a/cpp/ql/lib/semmle/code/cpp/Function.qll b/cpp/ql/lib/semmle/code/cpp/Function.qll index 98b0e8525ce..eec7a433774 100644 --- a/cpp/ql/lib/semmle/code/cpp/Function.qll +++ b/cpp/ql/lib/semmle/code/cpp/Function.qll @@ -41,7 +41,7 @@ class Function extends Declaration, ControlFlowNode, AccessHolder, @function { * `min(int, int) -> int`, and the full signature of the uninstantiated * template on the first line would be `min(T, T) -> T`. */ - string getFullSignature() { + deprecated string getFullSignature() { exists(string name, string templateArgs, string args | result = name + templateArgs + args + " -> " + this.getType().toString() and name = this.getQualifiedName() and diff --git a/cpp/ql/lib/semmle/code/cpp/commons/Alloc.qll b/cpp/ql/lib/semmle/code/cpp/commons/Alloc.qll index ce3f6993525..a6fb84d3227 100644 --- a/cpp/ql/lib/semmle/code/cpp/commons/Alloc.qll +++ b/cpp/ql/lib/semmle/code/cpp/commons/Alloc.qll @@ -12,7 +12,9 @@ predicate freeFunction(Function f, int argNum) { argNum = f.(DeallocationFunctio * * DEPRECATED: Use `DeallocationExpr` instead (this also includes `delete` expressions). */ -predicate freeCall(FunctionCall fc, Expr arg) { arg = fc.(DeallocationExpr).getFreedExpr() } +deprecated predicate freeCall(FunctionCall fc, Expr arg) { + arg = fc.(DeallocationExpr).getFreedExpr() +} /** * Is e some kind of allocation or deallocation (`new`, `alloc`, `realloc`, `delete`, `free` etc)? diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll index 0afcba55582..d574f95d181 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll @@ -959,6 +959,17 @@ module Impl { not inBarrier(p) } + /** + * Gets an additional term that is added to `branch` and `join` when deciding whether + * the amount of forward or backward branching is within the limit specified by the + * configuration. + */ + pragma[nomagic] + private int getLanguageSpecificFlowIntoCallNodeCand1(ArgNodeEx arg, ParamNodeEx p) { + flowIntoCallNodeCand1(_, arg, p) and + result = getAdditionalFlowIntoCallNodeTerm(arg.projectToNode(), p.projectToNode()) + } + /** * Gets the amount of forward branching on the origin of a cross-call path * edge in the graph of paths between sources and sinks that ignores call @@ -968,6 +979,7 @@ module Impl { private int branch(NodeEx n1) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + + sum(ParamNodeEx p1 | | getLanguageSpecificFlowIntoCallNodeCand1(n1, p1)) } /** @@ -979,6 +991,7 @@ module Impl { private int join(NodeEx n2) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + + sum(ArgNodeEx arg2 | | getLanguageSpecificFlowIntoCallNodeCand1(arg2, n2)) } /** diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll index 1749de42f2c..f54e3b9a9cb 100644 --- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll @@ -318,3 +318,12 @@ private class MyConsistencyConfiguration extends Consistency::ConsistencyConfigu // consistency alerts enough that most of them are interesting. } } + +/** + * Gets an additional term that is added to the `join` and `branch` computations to reflect + * an additional forward or backwards branching factor that is not taken into account + * when calculating the (virtual) dispatch cost. + * + * Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter. + */ +int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll index 0afcba55582..d574f95d181 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll @@ -959,6 +959,17 @@ module Impl { not inBarrier(p) } + /** + * Gets an additional term that is added to `branch` and `join` when deciding whether + * the amount of forward or backward branching is within the limit specified by the + * configuration. + */ + pragma[nomagic] + private int getLanguageSpecificFlowIntoCallNodeCand1(ArgNodeEx arg, ParamNodeEx p) { + flowIntoCallNodeCand1(_, arg, p) and + result = getAdditionalFlowIntoCallNodeTerm(arg.projectToNode(), p.projectToNode()) + } + /** * Gets the amount of forward branching on the origin of a cross-call path * edge in the graph of paths between sources and sinks that ignores call @@ -968,6 +979,7 @@ module Impl { private int branch(NodeEx n1) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + + sum(ParamNodeEx p1 | | getLanguageSpecificFlowIntoCallNodeCand1(n1, p1)) } /** @@ -979,6 +991,7 @@ module Impl { private int join(NodeEx n2) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + + sum(ArgNodeEx arg2 | | getLanguageSpecificFlowIntoCallNodeCand1(arg2, n2)) } /** diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index e068f81f542..e232896e8d9 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -414,3 +414,12 @@ private class MyConsistencyConfiguration extends Consistency::ConsistencyConfigu any() } } + +/** + * Gets an additional term that is added to the `join` and `branch` computations to reflect + * an additional forward or backwards branching factor that is not taken into account + * when calculating the (virtual) dispatch cost. + * + * Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter. + */ +int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() } diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.ql b/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.ql index a6b70d9c506..9b9684f12e2 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.ql +++ b/cpp/ql/src/experimental/Security/CWE/CWE-415/DoubleFree.ql @@ -14,8 +14,8 @@ import cpp from FunctionCall fc, FunctionCall fc2, LocalScopeVariable v where - freeCall(fc, v.getAnAccess()) and - freeCall(fc2, v.getAnAccess()) and + fc.(DeallocationExpr).getFreedExpr() = v.getAnAccess() and + fc2.(DeallocationExpr).getFreedExpr() = v.getAnAccess() and fc != fc2 and fc.getASuccessor*() = fc2 and not exists(Expr exptmp | diff --git a/cpp/ql/test/library-tests/CPP-205/elements.expected b/cpp/ql/test/library-tests/CPP-205/elements.expected index 8fbe5a0734f..f64b9d4e08b 100644 --- a/cpp/ql/test/library-tests/CPP-205/elements.expected +++ b/cpp/ql/test/library-tests/CPP-205/elements.expected @@ -1,14 +1,14 @@ | CPP-205.cpp:0:0:0:0 | CPP-205.cpp | | | CPP-205.cpp:1:20:1:20 | T | | | CPP-205.cpp:1:20:1:20 | definition of T | | -| CPP-205.cpp:2:5:2:5 | definition of fn | function declaration entry for fn(int) -> int | -| CPP-205.cpp:2:5:2:5 | fn | function fn(int) -> int | -| CPP-205.cpp:2:5:2:6 | definition of fn | function declaration entry for fn(T) -> int | -| CPP-205.cpp:2:5:2:6 | fn | function fn(T) -> int | -| CPP-205.cpp:2:10:2:12 | definition of out | parameter declaration entry for fn(T) -> int | -| CPP-205.cpp:2:10:2:12 | definition of out | parameter declaration entry for fn(int) -> int | -| CPP-205.cpp:2:10:2:12 | out | parameter for fn(T) -> int | -| CPP-205.cpp:2:10:2:12 | out | parameter for fn(int) -> int | +| CPP-205.cpp:2:5:2:5 | definition of fn | function declaration entry for int fn(int) | +| CPP-205.cpp:2:5:2:5 | fn | function int fn(int) | +| CPP-205.cpp:2:5:2:6 | definition of fn | function declaration entry for int fn(T) | +| CPP-205.cpp:2:5:2:6 | fn | function int fn(T) | +| CPP-205.cpp:2:10:2:12 | definition of out | parameter declaration entry for int fn(T) | +| CPP-205.cpp:2:10:2:12 | definition of out | parameter declaration entry for int fn(int) | +| CPP-205.cpp:2:10:2:12 | out | parameter for int fn(T) | +| CPP-205.cpp:2:10:2:12 | out | parameter for int fn(int) | | CPP-205.cpp:2:15:5:1 | { ... } | | | CPP-205.cpp:2:15:5:1 | { ... } | | | CPP-205.cpp:3:3:3:33 | declaration | | @@ -20,16 +20,16 @@ | CPP-205.cpp:4:3:4:11 | return ... | | | CPP-205.cpp:4:10:4:10 | 0 | | | CPP-205.cpp:4:10:4:10 | 0 | | -| CPP-205.cpp:7:5:7:8 | definition of main | function declaration entry for main() -> int | -| CPP-205.cpp:7:5:7:8 | main | function main() -> int | +| CPP-205.cpp:7:5:7:8 | definition of main | function declaration entry for int main() | +| CPP-205.cpp:7:5:7:8 | main | function int main() | | CPP-205.cpp:7:12:9:1 | { ... } | | | CPP-205.cpp:8:3:8:15 | return ... | | | CPP-205.cpp:8:10:8:11 | call to fn | | | CPP-205.cpp:8:13:8:13 | 0 | | -| file://:0:0:0:0 | (unnamed parameter 0) | parameter for __va_list_tag::operator=(__va_list_tag &&) -> __va_list_tag & | -| file://:0:0:0:0 | (unnamed parameter 0) | parameter for __va_list_tag::operator=(const __va_list_tag &) -> __va_list_tag & | +| file://:0:0:0:0 | (unnamed parameter 0) | parameter for __va_list_tag& __va_list_tag::operator=(__va_list_tag const&) | +| file://:0:0:0:0 | (unnamed parameter 0) | parameter for __va_list_tag& __va_list_tag::operator=(__va_list_tag&&) | | file://:0:0:0:0 | __super | | | file://:0:0:0:0 | __va_list_tag | | -| file://:0:0:0:0 | operator= | function __va_list_tag::operator=(__va_list_tag &&) -> __va_list_tag & | -| file://:0:0:0:0 | operator= | function __va_list_tag::operator=(const __va_list_tag &) -> __va_list_tag & | +| file://:0:0:0:0 | operator= | function __va_list_tag& __va_list_tag::operator=(__va_list_tag const&) | +| file://:0:0:0:0 | operator= | function __va_list_tag& __va_list_tag::operator=(__va_list_tag&&) | | file://:0:0:0:0 | y | | diff --git a/cpp/ql/test/library-tests/CPP-205/elements.ql b/cpp/ql/test/library-tests/CPP-205/elements.ql index 0b75bca9bb2..9388a799dbc 100644 --- a/cpp/ql/test/library-tests/CPP-205/elements.ql +++ b/cpp/ql/test/library-tests/CPP-205/elements.ql @@ -1,17 +1,19 @@ import cpp +import semmle.code.cpp.Print string describe(Element e) { - result = "function " + e.(Function).getFullSignature() + e instanceof Function and + result = "function " + getIdentityString(e) or result = "function declaration entry for " + - e.(FunctionDeclarationEntry).getFunction().getFullSignature() + getIdentityString(e.(FunctionDeclarationEntry).getFunction()) or - result = "parameter for " + e.(Parameter).getFunction().getFullSignature() + result = "parameter for " + getIdentityString(e.(Parameter).getFunction()) or result = "parameter declaration entry for " + - e.(ParameterDeclarationEntry).getFunctionDeclarationEntry().getFunction().getFullSignature() + getIdentityString(e.(ParameterDeclarationEntry).getFunctionDeclarationEntry().getFunction()) } from Element e diff --git a/cpp/ql/test/library-tests/allocators/allocators.expected b/cpp/ql/test/library-tests/allocators/allocators.expected index 5fdf7e50b90..5e7d573c83e 100644 --- a/cpp/ql/test/library-tests/allocators/allocators.expected +++ b/cpp/ql/test/library-tests/allocators/allocators.expected @@ -1,51 +1,51 @@ newExprs -| allocators.cpp:49:3:49:9 | new | int | operator new(unsigned long) -> void * | 4 | 4 | | | -| allocators.cpp:50:3:50:15 | new | int | operator new(size_t, float) -> void * | 4 | 4 | | | -| allocators.cpp:51:3:51:11 | new | int | operator new(unsigned long) -> void * | 4 | 4 | | | -| allocators.cpp:52:3:52:14 | new | String | operator new(unsigned long) -> void * | 8 | 8 | | | -| allocators.cpp:53:3:53:27 | new | String | operator new(size_t, float) -> void * | 8 | 8 | | | -| allocators.cpp:54:3:54:17 | new | Overaligned | operator new(unsigned long, align_val_t) -> void * | 256 | 128 | aligned | | -| allocators.cpp:55:3:55:25 | new | Overaligned | operator new(size_t, align_val_t, float) -> void * | 256 | 128 | aligned | | -| allocators.cpp:107:3:107:18 | new | FailedInit | FailedInit::operator new(size_t) -> void * | 1 | 1 | | | -| allocators.cpp:109:3:109:35 | new | FailedInitOveraligned | FailedInitOveraligned::operator new(size_t, align_val_t, float) -> void * | 128 | 128 | aligned | | -| allocators.cpp:129:3:129:21 | new | int | operator new(size_t, void *) -> void * | 4 | 4 | | & ... | -| allocators.cpp:135:3:135:26 | new | int | operator new(size_t, const nothrow_t &) -> void * | 4 | 4 | | | +| allocators.cpp:49:3:49:9 | new | int | void* operator new(unsigned long) | 4 | 4 | | | +| allocators.cpp:50:3:50:15 | new | int | void* operator new(size_t, float) | 4 | 4 | | | +| allocators.cpp:51:3:51:11 | new | int | void* operator new(unsigned long) | 4 | 4 | | | +| allocators.cpp:52:3:52:14 | new | String | void* operator new(unsigned long) | 8 | 8 | | | +| allocators.cpp:53:3:53:27 | new | String | void* operator new(size_t, float) | 8 | 8 | | | +| allocators.cpp:54:3:54:17 | new | Overaligned | void* operator new(unsigned long, std::align_val_t) | 256 | 128 | aligned | | +| allocators.cpp:55:3:55:25 | new | Overaligned | void* operator new(size_t, std::align_val_t, float) | 256 | 128 | aligned | | +| allocators.cpp:107:3:107:18 | new | FailedInit | void* FailedInit::operator new(size_t) | 1 | 1 | | | +| allocators.cpp:109:3:109:35 | new | FailedInitOveraligned | void* FailedInitOveraligned::operator new(size_t, std::align_val_t, float) | 128 | 128 | aligned | | +| allocators.cpp:129:3:129:21 | new | int | void* operator new(std::size_t, void*) | 4 | 4 | | & ... | +| allocators.cpp:135:3:135:26 | new | int | void* operator new(std::size_t, std::nothrow_t const&) | 4 | 4 | | | newArrayExprs -| allocators.cpp:68:3:68:12 | new[] | int[] | int | operator new[](unsigned long) -> void * | 4 | 4 | | n | | -| allocators.cpp:69:3:69:18 | new[] | int[] | int | operator new[](size_t, float) -> void * | 4 | 4 | | n | | -| allocators.cpp:70:3:70:15 | new[] | String[] | String | operator new[](unsigned long) -> void * | 8 | 8 | | n | | -| allocators.cpp:71:3:71:20 | new[] | Overaligned[] | Overaligned | operator new[](unsigned long, align_val_t) -> void * | 256 | 128 | aligned | n | | -| allocators.cpp:72:3:72:16 | new[] | String[10] | String | operator new[](unsigned long) -> void * | 8 | 8 | | | | -| allocators.cpp:108:3:108:19 | new[] | FailedInit[] | FailedInit | FailedInit::operator new[](size_t) -> void * | 1 | 1 | | n | | -| allocators.cpp:110:3:110:37 | new[] | FailedInitOveraligned[10] | FailedInitOveraligned | FailedInitOveraligned::operator new[](size_t, align_val_t, float) -> void * | 128 | 128 | aligned | | | -| allocators.cpp:132:3:132:17 | new[] | int[1] | int | operator new[](size_t, void *) -> void * | 4 | 4 | | | buf | -| allocators.cpp:136:3:136:26 | new[] | int[2] | int | operator new[](size_t, const nothrow_t &) -> void * | 4 | 4 | | | | -| allocators.cpp:142:13:142:27 | new[] | char[][10] | char[10] | operator new[](unsigned long) -> void * | 10 | 1 | | x | | -| allocators.cpp:143:13:143:28 | new[] | char[20][20] | char[20] | operator new[](unsigned long) -> void * | 20 | 1 | | | | -| allocators.cpp:144:13:144:31 | new[] | char[][30][30] | char[30][30] | operator new[](unsigned long) -> void * | 900 | 1 | | x | | +| allocators.cpp:68:3:68:12 | new[] | int[] | int | void* operator new[](unsigned long) | 4 | 4 | | n | | +| allocators.cpp:69:3:69:18 | new[] | int[] | int | void* operator new[](size_t, float) | 4 | 4 | | n | | +| allocators.cpp:70:3:70:15 | new[] | String[] | String | void* operator new[](unsigned long) | 8 | 8 | | n | | +| allocators.cpp:71:3:71:20 | new[] | Overaligned[] | Overaligned | void* operator new[](unsigned long, std::align_val_t) | 256 | 128 | aligned | n | | +| allocators.cpp:72:3:72:16 | new[] | String[10] | String | void* operator new[](unsigned long) | 8 | 8 | | | | +| allocators.cpp:108:3:108:19 | new[] | FailedInit[] | FailedInit | void* FailedInit::operator new[](size_t) | 1 | 1 | | n | | +| allocators.cpp:110:3:110:37 | new[] | FailedInitOveraligned[10] | FailedInitOveraligned | void* FailedInitOveraligned::operator new[](size_t, std::align_val_t, float) | 128 | 128 | aligned | | | +| allocators.cpp:132:3:132:17 | new[] | int[1] | int | void* operator new[](std::size_t, void*) | 4 | 4 | | | buf | +| allocators.cpp:136:3:136:26 | new[] | int[2] | int | void* operator new[](std::size_t, std::nothrow_t const&) | 4 | 4 | | | | +| allocators.cpp:142:13:142:27 | new[] | char[][10] | char[10] | void* operator new[](unsigned long) | 10 | 1 | | x | | +| allocators.cpp:143:13:143:28 | new[] | char[20][20] | char[20] | void* operator new[](unsigned long) | 20 | 1 | | | | +| allocators.cpp:144:13:144:31 | new[] | char[][30][30] | char[30][30] | void* operator new[](unsigned long) | 900 | 1 | | x | | newExprDeallocators -| allocators.cpp:52:3:52:14 | new | String | operator delete(void *, unsigned long) -> void | 8 | 8 | sized | -| allocators.cpp:53:3:53:27 | new | String | operator delete(void *, float) -> void | 8 | 8 | | -| allocators.cpp:107:3:107:18 | new | FailedInit | FailedInit::operator delete(void *, size_t) -> void | 1 | 1 | sized | -| allocators.cpp:109:3:109:35 | new | FailedInitOveraligned | FailedInitOveraligned::operator delete(void *, align_val_t, float) -> void | 128 | 128 | aligned | +| allocators.cpp:52:3:52:14 | new | String | void operator delete(void*, unsigned long) | 8 | 8 | sized | +| allocators.cpp:53:3:53:27 | new | String | void operator delete(void*, float) | 8 | 8 | | +| allocators.cpp:107:3:107:18 | new | FailedInit | void FailedInit::operator delete(void*, size_t) | 1 | 1 | sized | +| allocators.cpp:109:3:109:35 | new | FailedInitOveraligned | void FailedInitOveraligned::operator delete(void*, std::align_val_t, float) | 128 | 128 | aligned | newArrayExprDeallocators -| allocators.cpp:70:3:70:15 | new[] | String | operator delete[](void *, unsigned long) -> void | 8 | 8 | sized | -| allocators.cpp:72:3:72:16 | new[] | String | operator delete[](void *, unsigned long) -> void | 8 | 8 | sized | -| allocators.cpp:108:3:108:19 | new[] | FailedInit | FailedInit::operator delete[](void *, size_t) -> void | 1 | 1 | sized | -| allocators.cpp:110:3:110:37 | new[] | FailedInitOveraligned | FailedInitOveraligned::operator delete[](void *, align_val_t, float) -> void | 128 | 128 | aligned | +| allocators.cpp:70:3:70:15 | new[] | String | void operator delete[](void*, unsigned long) | 8 | 8 | sized | +| allocators.cpp:72:3:72:16 | new[] | String | void operator delete[](void*, unsigned long) | 8 | 8 | sized | +| allocators.cpp:108:3:108:19 | new[] | FailedInit | void FailedInit::operator delete[](void*, size_t) | 1 | 1 | sized | +| allocators.cpp:110:3:110:37 | new[] | FailedInitOveraligned | void FailedInitOveraligned::operator delete[](void*, std::align_val_t, float) | 128 | 128 | aligned | deleteExprs -| allocators.cpp:59:3:59:35 | delete | int | operator delete(void *, unsigned long) -> void | 4 | 4 | sized | -| allocators.cpp:60:3:60:38 | delete | String | operator delete(void *, unsigned long) -> void | 8 | 8 | sized | -| allocators.cpp:61:3:61:44 | delete | SizedDealloc | SizedDealloc::operator delete(void *, size_t) -> void | 32 | 1 | sized | -| allocators.cpp:62:3:62:43 | delete | Overaligned | operator delete(void *, unsigned long, align_val_t) -> void | 256 | 128 | sized aligned | -| allocators.cpp:64:3:64:44 | delete | const String | operator delete(void *, unsigned long) -> void | 8 | 8 | sized | +| allocators.cpp:59:3:59:35 | delete | int | void operator delete(void*, unsigned long) | 4 | 4 | sized | +| allocators.cpp:60:3:60:38 | delete | String | void operator delete(void*, unsigned long) | 8 | 8 | sized | +| allocators.cpp:61:3:61:44 | delete | SizedDealloc | void SizedDealloc::operator delete(void*, size_t) | 32 | 1 | sized | +| allocators.cpp:62:3:62:43 | delete | Overaligned | void operator delete(void*, unsigned long, std::align_val_t) | 256 | 128 | sized aligned | +| allocators.cpp:64:3:64:44 | delete | const String | void operator delete(void*, unsigned long) | 8 | 8 | sized | deleteArrayExprs -| allocators.cpp:78:3:78:37 | delete[] | int | operator delete[](void *, unsigned long) -> void | 4 | 4 | sized | -| allocators.cpp:79:3:79:40 | delete[] | String | operator delete[](void *, unsigned long) -> void | 8 | 8 | sized | -| allocators.cpp:80:3:80:46 | delete[] | SizedDealloc | SizedDealloc::operator delete[](void *, size_t) -> void | 32 | 1 | sized | -| allocators.cpp:81:3:81:45 | delete[] | Overaligned | operator delete[](void *, unsigned long, align_val_t) -> void | 256 | 128 | sized aligned | -| allocators.cpp:82:3:82:49 | delete[] | PolymorphicBase | operator delete[](void *, unsigned long) -> void | 8 | 8 | sized | -| allocators.cpp:83:3:83:23 | delete[] | int | operator delete[](void *, unsigned long) -> void | 4 | 4 | sized | +| allocators.cpp:78:3:78:37 | delete[] | int | void operator delete[](void*, unsigned long) | 4 | 4 | sized | +| allocators.cpp:79:3:79:40 | delete[] | String | void operator delete[](void*, unsigned long) | 8 | 8 | sized | +| allocators.cpp:80:3:80:46 | delete[] | SizedDealloc | void SizedDealloc::operator delete[](void*, size_t) | 32 | 1 | sized | +| allocators.cpp:81:3:81:45 | delete[] | Overaligned | void operator delete[](void*, unsigned long, std::align_val_t) | 256 | 128 | sized aligned | +| allocators.cpp:82:3:82:49 | delete[] | PolymorphicBase | void operator delete[](void*, unsigned long) | 8 | 8 | sized | +| allocators.cpp:83:3:83:23 | delete[] | int | void operator delete[](void*, unsigned long) | 4 | 4 | sized | allocationFunctions | allocators.cpp:7:7:7:18 | operator new | getSizeArg = 0, requiresDealloc | | allocators.cpp:8:7:8:20 | operator new[] | getSizeArg = 0, requiresDealloc | diff --git a/cpp/ql/test/library-tests/allocators/allocators.ql b/cpp/ql/test/library-tests/allocators/allocators.ql index a2126cdfbce..2d70f2f5083 100644 --- a/cpp/ql/test/library-tests/allocators/allocators.ql +++ b/cpp/ql/test/library-tests/allocators/allocators.ql @@ -1,12 +1,13 @@ import cpp import semmle.code.cpp.models.implementations.Allocation +import semmle.code.cpp.Print query predicate newExprs( NewExpr expr, string type, string sig, int size, int alignment, string form, string placement ) { exists(Function allocator, Type allocatedType | expr.getAllocator() = allocator and - sig = allocator.getFullSignature() and + sig = getIdentityString(allocator) and allocatedType = expr.getAllocatedType() and type = allocatedType.toString() and size = allocatedType.getSize() and @@ -24,7 +25,7 @@ query predicate newArrayExprs( ) { exists(Function allocator, Type arrayType, Type elementType | expr.getAllocator() = allocator and - sig = allocator.getFullSignature() and + sig = getIdentityString(allocator) and arrayType = expr.getAllocatedType() and t1 = arrayType.toString() and elementType = expr.getAllocatedElementType() and @@ -44,7 +45,7 @@ query predicate newExprDeallocators( ) { exists(Function deallocator, Type allocatedType | expr.getDeallocator() = deallocator and - sig = deallocator.getFullSignature() and + sig = getIdentityString(deallocator) and allocatedType = expr.getAllocatedType() and type = allocatedType.toString() and size = allocatedType.getSize() and @@ -62,7 +63,7 @@ query predicate newArrayExprDeallocators( ) { exists(Function deallocator, Type elementType | expr.getDeallocator() = deallocator and - sig = deallocator.getFullSignature() and + sig = getIdentityString(deallocator) and elementType = expr.getAllocatedElementType() and type = elementType.toString() and size = elementType.getSize() and @@ -80,7 +81,7 @@ query predicate deleteExprs( ) { exists(Function deallocator, Type deletedType | expr.getDeallocator() = deallocator and - sig = deallocator.getFullSignature() and + sig = getIdentityString(deallocator) and deletedType = expr.getDeletedObjectType() and type = deletedType.toString() and size = deletedType.getSize() and @@ -98,7 +99,7 @@ query predicate deleteArrayExprs( ) { exists(Function deallocator, Type elementType | expr.getDeallocator() = deallocator and - sig = deallocator.getFullSignature() and + sig = getIdentityString(deallocator) and elementType = expr.getDeletedElementType() and type = elementType.toString() and size = elementType.getSize() and diff --git a/cpp/ql/test/library-tests/noexcept/copy_from_prototype/copy_from_prototype.expected b/cpp/ql/test/library-tests/noexcept/copy_from_prototype/copy_from_prototype.expected index 5488cf803e6..a5250dfccc5 100644 --- a/cpp/ql/test/library-tests/noexcept/copy_from_prototype/copy_from_prototype.expected +++ b/cpp/ql/test/library-tests/noexcept/copy_from_prototype/copy_from_prototype.expected @@ -1,30 +1,30 @@ -| copy_from_prototype.cpp:3:7:3:7 | a | a::a(a &&) -> void | copy_from_prototype.cpp:3:7:3:7 | a | | -| copy_from_prototype.cpp:3:7:3:7 | a | a::a(const a &) -> void | copy_from_prototype.cpp:3:7:3:7 | a | | -| copy_from_prototype.cpp:3:7:3:7 | operator= | a::operator=(a &&) -> a & | copy_from_prototype.cpp:3:7:3:7 | a | | -| copy_from_prototype.cpp:3:7:3:7 | operator= | a::operator=(const a &) -> a & | copy_from_prototype.cpp:3:7:3:7 | a | | -| copy_from_prototype.cpp:4:26:4:26 | a | a<>::a<(unnamed template parameter)>() -> void | copy_from_prototype.cpp:3:7:3:7 | a<> | 123 | -| copy_from_prototype.cpp:4:26:4:26 | a | a::a<(unnamed template parameter)>() -> void | copy_from_prototype.cpp:3:7:3:7 | a | | -| copy_from_prototype.cpp:7:7:7:7 | b | b::b() -> void | copy_from_prototype.cpp:7:7:7:7 | b | | -| copy_from_prototype.cpp:7:7:7:7 | b | b::b(b &&) -> void | copy_from_prototype.cpp:7:7:7:7 | b | | -| copy_from_prototype.cpp:7:7:7:7 | b | b::b(const b &) -> void | copy_from_prototype.cpp:7:7:7:7 | b | | -| copy_from_prototype.cpp:7:7:7:7 | operator= | b::operator=(b &&) -> b & | copy_from_prototype.cpp:7:7:7:7 | b | | -| copy_from_prototype.cpp:7:7:7:7 | operator= | b::operator=(const b &) -> b & | copy_from_prototype.cpp:7:7:7:7 | b | | -| copy_from_prototype.cpp:13:7:13:7 | c | c::c(c &&) -> void | copy_from_prototype.cpp:13:7:13:7 | c | | -| copy_from_prototype.cpp:13:7:13:7 | c | c::c(const c &) -> void | copy_from_prototype.cpp:13:7:13:7 | c | | -| copy_from_prototype.cpp:13:7:13:7 | operator= | c::operator=(c &&) -> c & | copy_from_prototype.cpp:13:7:13:7 | c | | -| copy_from_prototype.cpp:13:7:13:7 | operator= | c::operator=(const c &) -> c & | copy_from_prototype.cpp:13:7:13:7 | c | | -| copy_from_prototype.cpp:14:26:14:26 | c | c::c<(unnamed template parameter)>() -> void | copy_from_prototype.cpp:13:7:13:7 | c | X | -| copy_from_prototype.cpp:14:26:14:26 | c | c::c<(unnamed template parameter)>() -> void | copy_from_prototype.cpp:13:7:13:7 | c | | -| copy_from_prototype.cpp:17:7:17:7 | d | d::d() -> void | copy_from_prototype.cpp:17:7:17:7 | d | | -| copy_from_prototype.cpp:17:7:17:7 | d | d::d(const d &) -> void | copy_from_prototype.cpp:17:7:17:7 | d | | -| copy_from_prototype.cpp:17:7:17:7 | d | d::d(d &&) -> void | copy_from_prototype.cpp:17:7:17:7 | d | | -| copy_from_prototype.cpp:17:7:17:7 | operator= | d::operator=(const d &) -> d & | copy_from_prototype.cpp:17:7:17:7 | d | | -| copy_from_prototype.cpp:17:7:17:7 | operator= | d::operator=(d &&) -> d & | copy_from_prototype.cpp:17:7:17:7 | d | | -| copy_from_prototype.cpp:22:8:22:8 | e | e::e(const e &) -> void | copy_from_prototype.cpp:22:8:22:8 | e | | -| copy_from_prototype.cpp:22:8:22:8 | e | e::e(e &&) -> void | copy_from_prototype.cpp:22:8:22:8 | e | | -| copy_from_prototype.cpp:22:8:22:8 | operator= | e::operator=(const e &) -> e & | copy_from_prototype.cpp:22:8:22:8 | e | | -| copy_from_prototype.cpp:22:8:22:8 | operator= | e::operator=(e &&) -> e & | copy_from_prototype.cpp:22:8:22:8 | e | | -| copy_from_prototype.cpp:23:26:23:26 | e | e::e<(unnamed template parameter)>() -> void | copy_from_prototype.cpp:22:8:22:8 | e | 456 | -| copy_from_prototype.cpp:26:35:26:43 | e | e::e<(unnamed template parameter)>() -> void | copy_from_prototype.cpp:22:8:22:8 | e | 456 | -| file://:0:0:0:0 | operator= | __va_list_tag::operator=(__va_list_tag &&) -> __va_list_tag & | file://:0:0:0:0 | __va_list_tag | | -| file://:0:0:0:0 | operator= | __va_list_tag::operator=(const __va_list_tag &) -> __va_list_tag & | file://:0:0:0:0 | __va_list_tag | | +| copy_from_prototype.cpp:3:7:3:7 | a | void a::a(a const&) | copy_from_prototype.cpp:3:7:3:7 | a | | +| copy_from_prototype.cpp:3:7:3:7 | a | void a::a(a&&) | copy_from_prototype.cpp:3:7:3:7 | a | | +| copy_from_prototype.cpp:3:7:3:7 | operator= | a& a::operator=(a const&) | copy_from_prototype.cpp:3:7:3:7 | a | | +| copy_from_prototype.cpp:3:7:3:7 | operator= | a& a::operator=(a&&) | copy_from_prototype.cpp:3:7:3:7 | a | | +| copy_from_prototype.cpp:4:26:4:26 | a | void a<(unnamed template parameter)>::a<(unnamed template parameter)>() | copy_from_prototype.cpp:3:7:3:7 | a<> | 123 | +| copy_from_prototype.cpp:4:26:4:26 | a | void a::a<(unnamed template parameter)>() | copy_from_prototype.cpp:3:7:3:7 | a | | +| copy_from_prototype.cpp:7:7:7:7 | b | void b::b() | copy_from_prototype.cpp:7:7:7:7 | b | | +| copy_from_prototype.cpp:7:7:7:7 | b | void b::b(b const&) | copy_from_prototype.cpp:7:7:7:7 | b | | +| copy_from_prototype.cpp:7:7:7:7 | b | void b::b(b&&) | copy_from_prototype.cpp:7:7:7:7 | b | | +| copy_from_prototype.cpp:7:7:7:7 | operator= | b& b::operator=(b const&) | copy_from_prototype.cpp:7:7:7:7 | b | | +| copy_from_prototype.cpp:7:7:7:7 | operator= | b& b::operator=(b&&) | copy_from_prototype.cpp:7:7:7:7 | b | | +| copy_from_prototype.cpp:13:7:13:7 | c | void c::c(c const&) | copy_from_prototype.cpp:13:7:13:7 | c | | +| copy_from_prototype.cpp:13:7:13:7 | c | void c::c(c&&) | copy_from_prototype.cpp:13:7:13:7 | c | | +| copy_from_prototype.cpp:13:7:13:7 | operator= | c& c::operator=(c const&) | copy_from_prototype.cpp:13:7:13:7 | c | | +| copy_from_prototype.cpp:13:7:13:7 | operator= | c& c::operator=(c&&) | copy_from_prototype.cpp:13:7:13:7 | c | | +| copy_from_prototype.cpp:14:26:14:26 | c | void c::c<(unnamed template parameter)>() | copy_from_prototype.cpp:13:7:13:7 | c | X | +| copy_from_prototype.cpp:14:26:14:26 | c | void c::c<(unnamed template parameter)>() | copy_from_prototype.cpp:13:7:13:7 | c | | +| copy_from_prototype.cpp:17:7:17:7 | d | void d::d() | copy_from_prototype.cpp:17:7:17:7 | d | | +| copy_from_prototype.cpp:17:7:17:7 | d | void d::d(d const&) | copy_from_prototype.cpp:17:7:17:7 | d | | +| copy_from_prototype.cpp:17:7:17:7 | d | void d::d(d&&) | copy_from_prototype.cpp:17:7:17:7 | d | | +| copy_from_prototype.cpp:17:7:17:7 | operator= | d& d::operator=(d const&) | copy_from_prototype.cpp:17:7:17:7 | d | | +| copy_from_prototype.cpp:17:7:17:7 | operator= | d& d::operator=(d&&) | copy_from_prototype.cpp:17:7:17:7 | d | | +| copy_from_prototype.cpp:22:8:22:8 | e | void e::e(e const&) | copy_from_prototype.cpp:22:8:22:8 | e | | +| copy_from_prototype.cpp:22:8:22:8 | e | void e::e(e&&) | copy_from_prototype.cpp:22:8:22:8 | e | | +| copy_from_prototype.cpp:22:8:22:8 | operator= | e& e::operator=(e const&) | copy_from_prototype.cpp:22:8:22:8 | e | | +| copy_from_prototype.cpp:22:8:22:8 | operator= | e& e::operator=(e&&) | copy_from_prototype.cpp:22:8:22:8 | e | | +| copy_from_prototype.cpp:23:26:23:26 | e | void e::e<(unnamed template parameter)>() | copy_from_prototype.cpp:22:8:22:8 | e | 456 | +| copy_from_prototype.cpp:26:35:26:43 | e | void e::e<(unnamed template parameter)>() | copy_from_prototype.cpp:22:8:22:8 | e | 456 | +| file://:0:0:0:0 | operator= | __va_list_tag& __va_list_tag::operator=(__va_list_tag const&) | file://:0:0:0:0 | __va_list_tag | | +| file://:0:0:0:0 | operator= | __va_list_tag& __va_list_tag::operator=(__va_list_tag&&) | file://:0:0:0:0 | __va_list_tag | | diff --git a/cpp/ql/test/library-tests/noexcept/copy_from_prototype/copy_from_prototype.ql b/cpp/ql/test/library-tests/noexcept/copy_from_prototype/copy_from_prototype.ql index 9e1cc557510..be8ab9565f4 100644 --- a/cpp/ql/test/library-tests/noexcept/copy_from_prototype/copy_from_prototype.ql +++ b/cpp/ql/test/library-tests/noexcept/copy_from_prototype/copy_from_prototype.ql @@ -1,4 +1,5 @@ import cpp +import semmle.code.cpp.Print from Function f, string e where @@ -8,4 +9,4 @@ where then e = f.getADeclarationEntry().getNoExceptExpr().toString() else e = "" else e = "" -select f, f.getFullSignature(), f.getDeclaringType(), e +select f, getIdentityString(f), f.getDeclaringType(), e diff --git a/cpp/ql/test/query-tests/Critical/MissingCheckScanf/MissingCheckScanf.expected b/cpp/ql/test/query-tests/Critical/MissingCheckScanf/MissingCheckScanf.expected index bbedf4ecc3f..a34e79ace7b 100644 --- a/cpp/ql/test/query-tests/Critical/MissingCheckScanf/MissingCheckScanf.expected +++ b/cpp/ql/test/query-tests/Critical/MissingCheckScanf/MissingCheckScanf.expected @@ -19,3 +19,6 @@ | test.cpp:302:8:302:12 | ptr_i | This variable is read, but may not have been written. It should be guarded by a check that the $@ returns at least 1. | test.cpp:301:3:301:7 | call to scanf | call to scanf | | test.cpp:310:7:310:7 | i | This variable is read, but may not have been written. It should be guarded by a check that the $@ returns at least 1. | test.cpp:309:3:309:7 | call to scanf | call to scanf | | test.cpp:404:25:404:25 | u | This variable is read, but may not have been written. It should be guarded by a check that the $@ returns at least 1. | test.cpp:403:6:403:11 | call to sscanf | call to sscanf | +| test.cpp:416:7:416:7 | i | This variable is read, but may not have been written. It should be guarded by a check that the $@ returns at least 1. | test.cpp:413:7:413:11 | call to scanf | call to scanf | +| test.cpp:423:7:423:7 | i | This variable is read, but may not have been written. It should be guarded by a check that the $@ returns at least 1. | test.cpp:420:7:420:11 | call to scanf | call to scanf | +| test.cpp:430:6:430:6 | i | This variable is read, but may not have been written. It should be guarded by a check that the $@ returns at least 1. | test.cpp:429:2:429:6 | call to scanf | call to scanf | diff --git a/cpp/ql/test/query-tests/Critical/MissingCheckScanf/test.cpp b/cpp/ql/test/query-tests/Critical/MissingCheckScanf/test.cpp index 38af4d7efcd..5940ad39529 100644 --- a/cpp/ql/test/query-tests/Critical/MissingCheckScanf/test.cpp +++ b/cpp/ql/test/query-tests/Critical/MissingCheckScanf/test.cpp @@ -406,3 +406,26 @@ char *my_string_copy() { *ptr++ = 0; return DST_STRING; } + +void scan_and_write() { + { + int i; + if (scanf("%d", &i) < 1) { + i = 0; + } + use(i); // GOOD [FALSE POSITIVE]: variable is overwritten with a default value when scanf fails + } + { + int i; + if (scanf("%d", &i) != 1) { + i = 0; + } + use(i); // GOOD [FALSE POSITIVE]: variable is overwritten with a default value when scanf fails + } +} + +void scan_and_static_variable() { + static int i; + scanf("%d", &i); + use(i); // GOOD [FALSE POSITIVE]: static variables are always 0-initialized +} diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs index ec7c63a022d..9590e132229 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs @@ -1,5 +1,6 @@ using Xunit; using Semmle.Autobuild.Shared; +using Semmle.Util; using System.Collections.Generic; using System; using System.Linq; @@ -85,6 +86,15 @@ namespace Semmle.Autobuild.CSharp.Tests return ret; } + int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary? env, BuildOutputHandler onOutput, BuildOutputHandler onError) + { + var ret = (this as IBuildActions).RunProcess(cmd, args, workingDirectory, env, out var stdout); + + stdout.ForEach(line => onOutput(line)); + + return ret; + } + public IList DirectoryDeleteIn { get; } = new List(); void IBuildActions.DirectoryDelete(string dir, bool recursive) @@ -200,6 +210,16 @@ namespace Semmle.Autobuild.CSharp.Tests if (!DownloadFiles.Contains((address, fileName))) throw new ArgumentException($"Missing DownloadFile, {address}, {fileName}"); } + + + public IDiagnosticsWriter CreateDiagnosticsWriter(string filename) => new TestDiagnosticWriter(); + } + + internal class TestDiagnosticWriter : IDiagnosticsWriter + { + public IList Diagnostics { get; } = new List(); + + public void AddEntry(DiagnosticMessage message) => this.Diagnostics.Add(message); } /// @@ -391,6 +411,7 @@ namespace Semmle.Autobuild.CSharp.Tests actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_TRAP_DIR"] = ""; actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_SOURCE_ARCHIVE_DIR"] = ""; actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_ROOT"] = $@"C:\codeql\{codeqlUpperLanguage.ToLowerInvariant()}"; + actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_DIAGNOSTIC_DIR"] = ""; actions.GetEnvironmentVariable["CODEQL_JAVA_HOME"] = @"C:\codeql\tools\java"; actions.GetEnvironmentVariable["CODEQL_PLATFORM"] = isWindows ? "win64" : "linux64"; actions.GetEnvironmentVariable["LGTM_INDEX_VSTOOLS_VERSION"] = vsToolsVersion; diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp/AutoBuildRule.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp/AutoBuildRule.cs new file mode 100644 index 00000000000..cd138d62af5 --- /dev/null +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp/AutoBuildRule.cs @@ -0,0 +1,69 @@ +using Semmle.Autobuild.Shared; +using Semmle.Extraction.CSharp; + +namespace Semmle.Autobuild.CSharp +{ + internal class AutoBuildRule : IBuildRule + { + private readonly CSharpAutobuilder autobuilder; + + public DotNetRule DotNetRule { get; } + + public MsBuildRule MsBuildRule { get; } + + public BuildCommandAutoRule BuildCommandAutoRule { get; } + + public AutoBuildRule(CSharpAutobuilder autobuilder) + { + this.autobuilder = autobuilder; + this.DotNetRule = new DotNetRule(); + this.MsBuildRule = new MsBuildRule(); + this.BuildCommandAutoRule = new BuildCommandAutoRule(DotNetRule.WithDotNet); + } + + public BuildScript Analyse(IAutobuilder builder, bool auto) + { + var cleanTrapFolder = + BuildScript.DeleteDirectory(this.autobuilder.TrapDir); + var cleanSourceArchive = + BuildScript.DeleteDirectory(this.autobuilder.SourceArchiveDir); + var tryCleanExtractorArgsLogs = + BuildScript.Create(actions => + { + foreach (var file in Extractor.GetCSharpArgsLogs()) + { + try + { + actions.FileDelete(file); + } + catch // lgtm[cs/catch-of-all-exceptions] lgtm[cs/empty-catch-block] + { } + } + + return 0; + }); + var attemptExtractorCleanup = + BuildScript.Try(cleanTrapFolder) & + BuildScript.Try(cleanSourceArchive) & + tryCleanExtractorArgsLogs & + BuildScript.DeleteFile(Extractor.GetCSharpLogPath()); + + /// + /// Execute script `s` and check that the C# extractor has been executed. + /// If either fails, attempt to cleanup any artifacts produced by the extractor, + /// and exit with code 1, in order to proceed to the next attempt. + /// + BuildScript IntermediateAttempt(BuildScript s) => + (s & this.autobuilder.CheckExtractorRun(false)) | + (attemptExtractorCleanup & BuildScript.Failure); + + return + // First try .NET Core + IntermediateAttempt(this.DotNetRule.Analyse(this.autobuilder, true)) | + // Then MSBuild + (() => IntermediateAttempt(this.MsBuildRule.Analyse(this.autobuilder, true))) | + // And finally look for a script that might be a build script + (() => this.BuildCommandAutoRule.Analyse(this.autobuilder, true) & this.autobuilder.CheckExtractorRun(true)); + } + } +} diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp/CSharpAutobuilder.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp/CSharpAutobuilder.cs index 71891fa4e8b..ed2ed4013ef 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp/CSharpAutobuilder.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp/CSharpAutobuilder.cs @@ -1,6 +1,8 @@ using Semmle.Extraction.CSharp; using Semmle.Util.Logging; using Semmle.Autobuild.Shared; +using Semmle.Util; +using System.Linq; namespace Semmle.Autobuild.CSharp { @@ -29,25 +31,16 @@ namespace Semmle.Autobuild.CSharp public class CSharpAutobuilder : Autobuilder { - public CSharpAutobuilder(IBuildActions actions, CSharpAutobuildOptions options) : base(actions, options) { } + private const string buildCommandDocsUrl = + "https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages"; + + private readonly AutoBuildRule autoBuildRule; + + public CSharpAutobuilder(IBuildActions actions, CSharpAutobuildOptions options) : base(actions, options, new CSharpDiagnosticClassifier()) => + this.autoBuildRule = new AutoBuildRule(this); public override BuildScript GetBuildScript() { - /// - /// A script that checks that the C# extractor has been executed. - /// - BuildScript CheckExtractorRun(bool warnOnFailure) => - BuildScript.Create(actions => - { - if (actions.FileExists(Extractor.GetCSharpLogPath())) - return 0; - - if (warnOnFailure) - Log(Severity.Error, "No C# code detected during build."); - - return 1; - }); - var attempt = BuildScript.Failure; switch (GetCSharpBuildStrategy()) { @@ -65,47 +58,9 @@ namespace Semmle.Autobuild.CSharp attempt = new DotNetRule().Analyse(this, false) & CheckExtractorRun(true); break; case CSharpBuildStrategy.Auto: - var cleanTrapFolder = - BuildScript.DeleteDirectory(TrapDir); - var cleanSourceArchive = - BuildScript.DeleteDirectory(SourceArchiveDir); - var tryCleanExtractorArgsLogs = - BuildScript.Create(actions => - { - foreach (var file in Extractor.GetCSharpArgsLogs()) - { - try - { - actions.FileDelete(file); - } - catch // lgtm[cs/catch-of-all-exceptions] lgtm[cs/empty-catch-block] - { } - } - - return 0; - }); - var attemptExtractorCleanup = - BuildScript.Try(cleanTrapFolder) & - BuildScript.Try(cleanSourceArchive) & - tryCleanExtractorArgsLogs & - BuildScript.DeleteFile(Extractor.GetCSharpLogPath()); - - /// - /// Execute script `s` and check that the C# extractor has been executed. - /// If either fails, attempt to cleanup any artifacts produced by the extractor, - /// and exit with code 1, in order to proceed to the next attempt. - /// - BuildScript IntermediateAttempt(BuildScript s) => - (s & CheckExtractorRun(false)) | - (attemptExtractorCleanup & BuildScript.Failure); - attempt = - // First try .NET Core - IntermediateAttempt(new DotNetRule().Analyse(this, true)) | - // Then MSBuild - (() => IntermediateAttempt(new MsBuildRule().Analyse(this, true))) | - // And finally look for a script that might be a build script - (() => new BuildCommandAutoRule(DotNetRule.WithDotNet).Analyse(this, true) & CheckExtractorRun(true)) | + // Attempt a few different build strategies to see if one works + this.autoBuildRule.Analyse(this, true) | // All attempts failed: print message AutobuildFailure(); break; @@ -114,6 +69,127 @@ namespace Semmle.Autobuild.CSharp return attempt; } + /// + /// A script that checks that the C# extractor has been executed. + /// + public BuildScript CheckExtractorRun(bool warnOnFailure) => + BuildScript.Create(actions => + { + if (actions.FileExists(Extractor.GetCSharpLogPath())) + return 0; + + if (warnOnFailure) + Log(Severity.Error, "No C# code detected during build."); + + return 1; + }); + + protected override void AutobuildFailureDiagnostic() + { + // if `ScriptPath` is not null here, the `BuildCommandAuto` rule was + // run and found at least one script to execute + if (this.autoBuildRule.BuildCommandAutoRule.ScriptPath is not null) + { + var relScriptPath = this.MakeRelative(autoBuildRule.BuildCommandAutoRule.ScriptPath); + + // if we found multiple build scripts in the project directory, then we can say + // as much to indicate that we may have picked the wrong one; + // otherwise, we just report that the one script we found didn't work + DiagnosticMessage message = + this.autoBuildRule.BuildCommandAutoRule.CandidatePaths.Count() > 1 ? + new( + this.Options.Language, + "multiple-build-scripts", + "There are multiple potential build scripts", + markdownMessage: + "CodeQL found multiple potential build scripts for your project and " + + $"attempted to run `{relScriptPath}`, which failed. " + + "This may not be the right build script for your project. " + + $"Set up a [manual build command]({buildCommandDocsUrl})." + ) : + new( + this.Options.Language, + "script-failure", + "Unable to build project using build script", + markdownMessage: + "CodeQL attempted to build your project using a script located at " + + $"`{relScriptPath}`, which failed. " + + $"Set up a [manual build command]({buildCommandDocsUrl})." + ); + + AddDiagnostic(message); + } + + // project files which don't exist get marked as not .NET core projects, but we don't want + // to show an error for this if the files don't exist + var foundNotDotNetProjects = autoBuildRule.DotNetRule.NotDotNetProjects.Where( + proj => this.Actions.FileExists(proj.FullPath) + ); + + // both dotnet and msbuild builds require project or solution files; if we haven't found any + // then neither of those rules would've worked + if (this.ProjectsOrSolutionsToBuild.Count == 0) + { + this.AddDiagnostic(new( + this.Options.Language, + "no-projects-or-solutions", + "No project or solutions files found", + markdownMessage: + "CodeQL could not find any project or solution files in your repository. " + + $"Set up a [manual build command]({buildCommandDocsUrl})." + )); + } + // show a warning if there are projects which are not compatible with .NET Core, in case that is unintentional + else if (foundNotDotNetProjects.Any()) + { + this.AddDiagnostic(new( + this.Options.Language, + "dotnet-incompatible-projects", + "Some projects are incompatible with .NET Core", + severity: DiagnosticMessage.TspSeverity.Warning, + markdownMessage: $""" + CodeQL found some projects which cannot be built with .NET Core: + + {autoBuildRule.DotNetRule.NotDotNetProjects.Select(p => this.MakeRelative(p.FullPath)).ToMarkdownList(MarkdownUtil.CodeFormatter, 5)} + """ + )); + } + + // report any projects that failed to build with .NET Core + if (autoBuildRule.DotNetRule.FailedProjectsOrSolutions.Any()) + { + this.AddDiagnostic(new( + this.Options.Language, + "dotnet-build-failure", + "Some projects or solutions failed to build using .NET Core", + markdownMessage: $""" + CodeQL was unable to build the following projects using .NET Core: + + {autoBuildRule.DotNetRule.FailedProjectsOrSolutions.Select(p => this.MakeRelative(p.FullPath)).ToMarkdownList(MarkdownUtil.CodeFormatter, 10)} + + Set up a [manual build command]({buildCommandDocsUrl}). + """ + )); + } + + // report any projects that failed to build with MSBuild + if (autoBuildRule.MsBuildRule.FailedProjectsOrSolutions.Any()) + { + this.AddDiagnostic(new( + this.Options.Language, + "msbuild-build-failure", + "Some projects or solutions failed to build using MSBuild", + markdownMessage: $""" + CodeQL was unable to build the following projects using MSBuild: + + {autoBuildRule.MsBuildRule.FailedProjectsOrSolutions.Select(p => this.MakeRelative(p.FullPath)).ToMarkdownList(MarkdownUtil.CodeFormatter, 10)} + + Set up a [manual build command]({buildCommandDocsUrl}). + """ + )); + } + } + /// /// Gets the build strategy that the autobuilder should apply, based on the /// options in the `lgtm.yml` file. diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp/CSharpDiagnosticClassifier.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp/CSharpDiagnosticClassifier.cs new file mode 100644 index 00000000000..e72c46ff361 --- /dev/null +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp/CSharpDiagnosticClassifier.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Semmle.Autobuild.Shared; +using Semmle.Util; + +namespace Semmle.Autobuild.CSharp +{ + /// + /// A diagnostic rule which tries to identify missing Xamarin SDKs. + /// + public class MissingXamarinSdkRule : DiagnosticRule + { + private const string docsUrl = "https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-xamarin-applications"; + + public class Result : IDiagnosticsResult + { + /// + /// The name of the SDK that is missing. + /// + public string SDKName { get; } + + public Result(string sdkName) + { + this.SDKName = sdkName; + } + + public DiagnosticMessage ToDiagnosticMessage(Autobuilder builder, DiagnosticMessage.TspSeverity? severity = null) where T : AutobuildOptionsShared => new( + builder.Options.Language, + $"missing-xamarin-{this.SDKName.ToLower()}-sdk", + $"Missing Xamarin SDK for {this.SDKName}", + severity: severity ?? DiagnosticMessage.TspSeverity.Error, + markdownMessage: $"[Configure your workflow]({docsUrl}) for this SDK before running CodeQL." + ); + } + + public MissingXamarinSdkRule() : + base("MSB4019:[^\"]*\"[^\"]*Xamarin\\.(?[^\\.]*)\\.CSharp\\.targets\"") + { + } + + public override void Fire(DiagnosticClassifier classifier, Match match) + { + if (!match.Groups.TryGetValue("sdkName", out var sdkName)) + throw new ArgumentException("Expected regular expression match to contain sdkName"); + + var xamarinResults = classifier.Results.OfType().Where(result => + result.SDKName.Equals(sdkName.Value) + ); + + if (!xamarinResults.Any()) + classifier.Results.Add(new Result(sdkName.Value)); + } + } + + public class MissingProjectFileRule : DiagnosticRule + { + private const string runsOnDocsUrl = "https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idruns-on"; + private const string checkoutDocsUrl = "https://github.com/actions/checkout#usage"; + + public class Result : IDiagnosticsResult + { + /// + /// A set of missing project files. + /// + public HashSet MissingProjectFiles { get; } + + public Result() + { + this.MissingProjectFiles = new HashSet(); + } + + public DiagnosticMessage ToDiagnosticMessage(Autobuilder builder, DiagnosticMessage.TspSeverity? severity = null) where T : AutobuildOptionsShared => new( + builder.Options.Language, + "missing-project-files", + "Missing project files", + severity: severity ?? DiagnosticMessage.TspSeverity.Warning, + markdownMessage: $""" + Some project files were not found when CodeQL built your project: + + {this.MissingProjectFiles.AsEnumerable().Select(p => builder.MakeRelative(p)).ToMarkdownList(MarkdownUtil.CodeFormatter, 5)} + + This may lead to subsequent failures. You can check for common causes for missing project files: + + - Ensure that the project is built using the {runsOnDocsUrl.ToMarkdownLink("intended operating system")} and that filenames on case-sensitive platforms are correctly specified. + - If your repository uses Git submodules, ensure that those are {checkoutDocsUrl.ToMarkdownLink("checked out")} before the CodeQL action is run. + - If you auto-generate some project files as part of your build process, ensure that these are generated before the CodeQL action is run. + """ + ); + } + + public MissingProjectFileRule() : + base("MSB3202: The project file \"(?[^\"]+)\" was not found. \\[(?[^\\]]+)\\]") + { + } + + public override void Fire(DiagnosticClassifier classifier, Match match) + { + if (!match.Groups.TryGetValue("projectFile", out var projectFile)) + throw new ArgumentException("Expected regular expression match to contain projectFile"); + if (!match.Groups.TryGetValue("location", out var location)) + throw new ArgumentException("Expected regular expression match to contain location"); + + var result = classifier.Results.OfType().FirstOrDefault(); + + // if we do not yet have a result for this rule, create one and add it to the list + // of results the classifier knows about + if (result is null) + { + result = new Result(); + classifier.Results.Add(result); + } + + // then add the missing project file + result.MissingProjectFiles.Add(projectFile.Value); + } + } + + /// + /// Implements a which applies C#-specific rules to + /// the build output. + /// + public class CSharpDiagnosticClassifier : DiagnosticClassifier + { + public CSharpDiagnosticClassifier() + { + // add C#-specific rules to this classifier + this.AddRule(new MissingXamarinSdkRule()); + this.AddRule(new MissingProjectFileRule()); + } + } +} diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs index 394349e2a40..e18058339f5 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs @@ -15,6 +15,15 @@ namespace Semmle.Autobuild.CSharp /// internal class DotNetRule : IBuildRule { + public readonly List FailedProjectsOrSolutions = new(); + + /// + /// A list of projects which are incompatible with DotNet. + /// + public IEnumerable> NotDotNetProjects { get; private set; } + + public DotNetRule() => NotDotNetProjects = new List>(); + public BuildScript Analyse(IAutobuilder builder, bool auto) { if (!builder.ProjectsOrSolutionsToBuild.Any()) @@ -22,10 +31,12 @@ namespace Semmle.Autobuild.CSharp if (auto) { - var notDotNetProject = builder.ProjectsOrSolutionsToBuild + NotDotNetProjects = builder.ProjectsOrSolutionsToBuild .SelectMany(p => Enumerators.Singleton(p).Concat(p.IncludedProjects)) .OfType>() - .FirstOrDefault(p => !p.DotNetProject); + .Where(p => !p.DotNetProject); + var notDotNetProject = NotDotNetProjects.FirstOrDefault(); + if (notDotNetProject is not null) { builder.Log(Severity.Info, "Not using .NET Core because of incompatible project {0}", notDotNetProject); @@ -50,7 +61,10 @@ namespace Semmle.Autobuild.CSharp var build = GetBuildScript(builder, dotNetPath, environment, projectOrSolution.FullPath); - ret &= BuildScript.Try(clean) & BuildScript.Try(restore) & build; + ret &= BuildScript.Try(clean) & BuildScript.Try(restore) & BuildScript.OnFailure(build, ret => + { + FailedProjectsOrSolutions.Add(projectOrSolution); + }); } return ret; }); diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/AutobuildOptions.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/AutobuildOptions.cs index d51612272d0..8a02370bcf6 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Shared/AutobuildOptions.cs +++ b/csharp/autobuilder/Semmle.Autobuild.Shared/AutobuildOptions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using Semmle.Util; namespace Semmle.Autobuild.Shared { diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/Autobuilder.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/Autobuilder.cs index 1ef5ebd815d..f00f59b488a 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Shared/Autobuilder.cs +++ b/csharp/autobuilder/Semmle.Autobuild.Shared/Autobuilder.cs @@ -1,3 +1,4 @@ +using Semmle.Util; using Semmle.Util.Logging; using System; using System.Collections.Generic; @@ -189,10 +190,11 @@ namespace Semmle.Autobuild.Shared /// solution file and tools. /// /// The command line options. - protected Autobuilder(IBuildActions actions, TAutobuildOptions options) + protected Autobuilder(IBuildActions actions, TAutobuildOptions options, DiagnosticClassifier diagnosticClassifier) { Actions = actions; Options = options; + DiagnosticClassifier = diagnosticClassifier; pathsLazy = new Lazy>(() => { @@ -232,24 +234,53 @@ namespace Semmle.Autobuild.Shared return ret ?? new List(); }); - CodeQLExtractorLangRoot = Actions.GetEnvironmentVariable($"CODEQL_EXTRACTOR_{this.Options.Language.UpperCaseName}_ROOT"); - CodeQlPlatform = Actions.GetEnvironmentVariable("CODEQL_PLATFORM"); + CodeQLExtractorLangRoot = Actions.GetEnvironmentVariable(EnvVars.Root(this.Options.Language)); + CodeQlPlatform = Actions.GetEnvironmentVariable(EnvVars.Platform); - TrapDir = - Actions.GetEnvironmentVariable($"CODEQL_EXTRACTOR_{this.Options.Language.UpperCaseName}_TRAP_DIR") ?? - throw new InvalidEnvironmentException($"The environment variable CODEQL_EXTRACTOR_{this.Options.Language.UpperCaseName}_TRAP_DIR has not been set."); + TrapDir = RequireEnvironmentVariable(EnvVars.TrapDir(this.Options.Language)); + SourceArchiveDir = RequireEnvironmentVariable(EnvVars.SourceArchiveDir(this.Options.Language)); + DiagnosticsDir = RequireEnvironmentVariable(EnvVars.DiagnosticDir(this.Options.Language)); - SourceArchiveDir = - Actions.GetEnvironmentVariable($"CODEQL_EXTRACTOR_{this.Options.Language.UpperCaseName}_SOURCE_ARCHIVE_DIR") ?? - throw new InvalidEnvironmentException($"The environment variable CODEQL_EXTRACTOR_{this.Options.Language.UpperCaseName}_SOURCE_ARCHIVE_DIR has not been set."); + this.diagnostics = actions.CreateDiagnosticsWriter(Path.Combine(DiagnosticsDir, $"autobuilder-{DateTime.UtcNow:yyyyMMddHHmm}.jsonc")); } - protected string TrapDir { get; } + /// + /// Retrieves the value of an environment variable named or throws + /// an exception if no such environment variable has been set. + /// + /// The name of the environment variable. + /// The value of the environment variable. + /// + /// Thrown if the environment variable is not set. + /// + protected string RequireEnvironmentVariable(string name) + { + return Actions.GetEnvironmentVariable(name) ?? + throw new InvalidEnvironmentException($"The environment variable {name} has not been set."); + } - protected string SourceArchiveDir { get; } + public string TrapDir { get; } + + public string SourceArchiveDir { get; } + + public string DiagnosticsDir { get; } + + protected DiagnosticClassifier DiagnosticClassifier { get; } private readonly ILogger logger = new ConsoleLogger(Verbosity.Info); + private readonly IDiagnosticsWriter diagnostics; + + /// + /// Makes relative to the root source directory. + /// + /// The path which to make relative. + /// The relative path. + public string MakeRelative(string path) + { + return Path.GetRelativePath(this.RootDirectory, path); + } + /// /// Log a given build event to the console. /// @@ -260,6 +291,15 @@ namespace Semmle.Autobuild.Shared logger.Log(severity, format, args); } + /// + /// Write to the diagnostics file. + /// + /// The diagnostics entry to write. + public void AddDiagnostic(DiagnosticMessage diagnostic) + { + diagnostics.AddEntry(diagnostic); + } + /// /// Attempt to build this project. /// @@ -283,7 +323,19 @@ namespace Semmle.Autobuild.Shared Log(silent ? Severity.Debug : Severity.Info, $"Exit code {ret}{(string.IsNullOrEmpty(msg) ? "" : $": {msg}")}"); } - return script.Run(Actions, startCallback, exitCallback); + var onOutput = BuildOutputHandler(Console.Out); + var onError = BuildOutputHandler(Console.Error); + + var buildResult = script.Run(Actions, startCallback, exitCallback, onOutput, onError); + + // if the build succeeded, all diagnostics we captured from the build output should be warnings; + // otherwise they should all be errors + var diagSeverity = buildResult == 0 ? DiagnosticMessage.TspSeverity.Warning : DiagnosticMessage.TspSeverity.Error; + this.DiagnosticClassifier.Results + .Select(result => result.ToDiagnosticMessage(this, diagSeverity)) + .ForEach(AddDiagnostic); + + return buildResult; } /// @@ -291,13 +343,58 @@ namespace Semmle.Autobuild.Shared /// public abstract BuildScript GetBuildScript(); + + /// + /// Produces a diagnostic for the tool status page that we were unable to automatically + /// build the user's project and that they can manually specify a build command. This + /// can be overriden to implement more specific messages depending on the origin of + /// the failure. + /// + protected virtual void AutobuildFailureDiagnostic() => AddDiagnostic(new DiagnosticMessage( + this.Options.Language, + "autobuild-failure", + "Unable to build project", + visibility: new DiagnosticMessage.TspVisibility(statusPage: true), + plaintextMessage: """ + We were unable to automatically build your project. + Set up a manual build command. + """ + )); + + /// + /// Returns a build script that can be run upon autobuild failure. + /// + /// + /// A build script that reports that we could not automatically detect a suitable build method. + /// protected BuildScript AutobuildFailure() => BuildScript.Create(actions => { Log(Severity.Error, "Could not auto-detect a suitable build method"); + + AutobuildFailureDiagnostic(); + return 1; }); + /// + /// Constructs a which uses the + /// to classify build output. All data also gets written to . + /// + /// + /// The to which the build output would have normally been written to. + /// This is normally or . + /// + /// The constructed . + protected BuildOutputHandler BuildOutputHandler(TextWriter writer) => new(data => + { + if (data is not null) + { + writer.WriteLine(data); + DiagnosticClassifier.ClassifyLine(data); + } + }); + /// /// Value of CODEQL_EXTRACTOR__ROOT environment variable. /// diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/BuildActions.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/BuildActions.cs index f3f9ae15da6..0982a520bfd 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Shared/BuildActions.cs +++ b/csharp/autobuilder/Semmle.Autobuild.Shared/BuildActions.cs @@ -11,11 +11,26 @@ using System.Runtime.InteropServices; namespace Semmle.Autobuild.Shared { + public delegate void BuildOutputHandler(string? data); + /// /// Wrapper around system calls so that the build scripts can be unit-tested. /// public interface IBuildActions { + + /// + /// Runs a process, captures its output, and provides it asynchronously. + /// + /// The exe to run. + /// The other command line arguments. + /// The working directory (null for current directory). + /// Additional environment variables. + /// A handler for stdout output. + /// A handler for stderr output. + /// The process exit code. + int RunProcess(string exe, string args, string? workingDirectory, IDictionary? env, BuildOutputHandler onOutput, BuildOutputHandler onError); + /// /// Runs a process and captures its output. /// @@ -152,6 +167,17 @@ namespace Semmle.Autobuild.Shared /// Downloads the resource with the specified URI to a local file. /// void DownloadFile(string address, string fileName); + + /// + /// Creates an for the given . + /// + /// + /// The path suggesting where the diagnostics should be written to. + /// + /// + /// A to which diagnostic entries can be added. + /// + IDiagnosticsWriter CreateDiagnosticsWriter(string filename); } /// @@ -182,6 +208,26 @@ namespace Semmle.Autobuild.Shared return pi; } + int IBuildActions.RunProcess(string exe, string args, string? workingDirectory, System.Collections.Generic.IDictionary? env, BuildOutputHandler onOutput, BuildOutputHandler onError) + { + var pi = GetProcessStartInfo(exe, args, workingDirectory, env, true); + using var p = new Process + { + StartInfo = pi + }; + p.StartInfo.RedirectStandardError = true; + p.OutputDataReceived += new DataReceivedEventHandler((sender, e) => onOutput(e.Data)); + p.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => onError(e.Data)); + + p.Start(); + + p.BeginErrorReadLine(); + p.BeginOutputReadLine(); + + p.WaitForExit(); + return p.ExitCode; + } + int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary? environment) { var pi = GetProcessStartInfo(cmd, args, workingDirectory, environment, false); @@ -253,6 +299,8 @@ namespace Semmle.Autobuild.Shared public void DownloadFile(string address, string fileName) => DownloadFileAsync(address, fileName).Wait(); + public IDiagnosticsWriter CreateDiagnosticsWriter(string filename) => new DiagnosticsStream(filename); + public static IBuildActions Instance { get; } = new SystemBuildActions(); } } diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/BuildCommandAutoRule.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/BuildCommandAutoRule.cs index 0d44f0dad4d..c0990a75ae1 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Shared/BuildCommandAutoRule.cs +++ b/csharp/autobuilder/Semmle.Autobuild.Shared/BuildCommandAutoRule.cs @@ -11,22 +11,42 @@ namespace Semmle.Autobuild.Shared { private readonly WithDotNet withDotNet; + /// + /// A list of paths to files in the project directory which we classified as scripts. + /// + public IEnumerable CandidatePaths { get; private set; } + + /// + /// The path of the script we decided to run, if any. + /// + public string? ScriptPath { get; private set; } + public BuildCommandAutoRule(WithDotNet withDotNet) { this.withDotNet = withDotNet; + this.CandidatePaths = new List(); } + /// + /// A list of extensions that we consider to be for scripts on Windows. + /// private readonly IEnumerable winExtensions = new List { ".bat", ".cmd", ".exe" }; + /// + /// A list of extensions that we consider to be for scripts on Linux. + /// private readonly IEnumerable linuxExtensions = new List { "", ".sh" }; + /// + /// A list of filenames without extensions that we think might be build scripts. + /// private readonly IEnumerable buildScripts = new List { "build" }; @@ -35,18 +55,25 @@ namespace Semmle.Autobuild.Shared { builder.Log(Severity.Info, "Attempting to locate build script"); + // a list of extensions for files that we consider to be scripts on the current platform var extensions = builder.Actions.IsWindows() ? winExtensions : linuxExtensions; + // a list of combined base script names with the current platform's script extensions + // e.g. for Linux: build, build.sh var scripts = buildScripts.SelectMany(s => extensions.Select(e => s + e)); - var scriptPath = builder.Paths.Where(p => scripts.Any(p.Item1.ToLower().EndsWith)).OrderBy(p => p.Item2).Select(p => p.Item1).FirstOrDefault(); + // search through the files in the project directory for paths which end in one of + // the names given by `scripts`, then order them by their distance from the root + this.CandidatePaths = builder.Paths.Where(p => scripts.Any(p.Item1.ToLower().EndsWith)).OrderBy(p => p.Item2).Select(p => p.Item1); + // pick the first matching path, if there is one + this.ScriptPath = this.CandidatePaths.FirstOrDefault(); - if (scriptPath is null) + if (this.ScriptPath is null) return BuildScript.Failure; var chmod = new CommandBuilder(builder.Actions); - chmod.RunCommand("/bin/chmod", $"u+x {scriptPath}"); + chmod.RunCommand("/bin/chmod", $"u+x {this.ScriptPath}"); var chmodScript = builder.Actions.IsWindows() ? BuildScript.Success : BuildScript.Try(chmod.Script); - var dir = builder.Actions.GetDirectoryName(scriptPath); + var dir = builder.Actions.GetDirectoryName(this.ScriptPath); // A specific .NET Core version may be required return chmodScript & withDotNet(builder, environment => @@ -58,7 +85,7 @@ namespace Semmle.Autobuild.Shared if (vsTools is not null) command.CallBatFile(vsTools.Path); - command.RunCommand(scriptPath); + command.RunCommand(this.ScriptPath); return command.Script; }); } diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/BuildScript.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/BuildScript.cs index 9840d63c5e4..573c6f3043b 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Shared/BuildScript.cs +++ b/csharp/autobuilder/Semmle.Autobuild.Shared/BuildScript.cs @@ -46,6 +46,33 @@ namespace Semmle.Autobuild.Shared /// The exit code from this build script. public abstract int Run(IBuildActions actions, Action startCallback, Action exitCallBack, out IList stdout); + /// + /// Runs this build command. + /// + /// + /// The interface used to implement the build actions. + /// + /// + /// A call back that is called every time a new process is started. The + /// argument to the call back is a textual representation of the process. + /// + /// + /// A call back that is called every time a new process exits. The first + /// argument to the call back is the exit code, and the second argument is + /// an exit message. + /// + /// + /// A handler for data read from stdout. + /// + /// + /// A handler for data read from stderr. + /// + /// The exit code from this build script. + public abstract int Run(IBuildActions actions, Action startCallback, Action exitCallBack, BuildOutputHandler onOutput, BuildOutputHandler onError); + + /// + /// A build script which executes an external program or script. + /// private class BuildCommand : BuildScript { private readonly string exe, arguments; @@ -110,8 +137,29 @@ namespace Semmle.Autobuild.Shared return ret; } + public override int Run(IBuildActions actions, Action startCallback, Action exitCallBack, BuildOutputHandler onOutput, BuildOutputHandler onError) + { + startCallback(this.ToString(), silent); + var ret = 1; + var retMessage = ""; + try + { + ret = actions.RunProcess(exe, arguments, workingDirectory, environment, onOutput, onError); + } + catch (Exception ex) + when (ex is System.ComponentModel.Win32Exception || ex is FileNotFoundException) + { + retMessage = ex.Message; + } + exitCallBack(ret, retMessage, silent); + return ret; + } + } + /// + /// A build script which runs a C# function. + /// private class ReturnBuildCommand : BuildScript { private readonly Func func; @@ -127,8 +175,13 @@ namespace Semmle.Autobuild.Shared stdout = Array.Empty(); return func(actions); } + + public override int Run(IBuildActions actions, Action startCallback, Action exitCallBack, BuildOutputHandler onOutput, BuildOutputHandler onError) => func(actions); } + /// + /// Allows two build scripts to be composed sequentially. + /// private class BindBuildScript : BuildScript { private readonly BuildScript s1; @@ -175,6 +228,32 @@ namespace Semmle.Autobuild.Shared stdout = @out; return ret2; } + + public override int Run(IBuildActions actions, Action startCallback, Action exitCallBack, BuildOutputHandler onOutput, BuildOutputHandler onError) + { + int ret1; + if (s2a is not null) + { + var stdout1 = new List(); + var onOutputWrapper = new BuildOutputHandler(data => + { + if (data is not null) + stdout1.Add(data); + + onOutput(data); + }); + ret1 = s1.Run(actions, startCallback, exitCallBack, onOutputWrapper, onError); + return s2a(stdout1, ret1).Run(actions, startCallback, exitCallBack, onOutput, onError); + } + + if (s2b is not null) + { + ret1 = s1.Run(actions, startCallback, exitCallBack, onOutput, onError); + return s2b(ret1).Run(actions, startCallback, exitCallBack, onOutput, onError); + } + + throw new InvalidOperationException("Unexpected error"); + } } /// @@ -260,6 +339,23 @@ namespace Semmle.Autobuild.Shared /// public static BuildScript Try(BuildScript s) => s | Success; + /// + /// Creates a build script that runs the build script . If + /// running fails, is invoked with + /// the exit code. + /// + /// The build script to run. + /// + /// The callback that is invoked if failed. + /// + /// The build script which implements this. + public static BuildScript OnFailure(BuildScript s, Action k) => + new BindBuildScript(s, ret => Create(actions => + { + if (!Succeeded(ret)) k(ret); + return ret; + })); + /// /// Creates a build script that deletes the given directory. /// diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/DiagnosticClassifier.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/DiagnosticClassifier.cs new file mode 100644 index 00000000000..94af39ca959 --- /dev/null +++ b/csharp/autobuilder/Semmle.Autobuild.Shared/DiagnosticClassifier.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Semmle.Util; + +namespace Semmle.Autobuild.Shared +{ + /// + /// Direct results result from the successful application of a , + /// which can later be converted to a corresponding . + /// + public interface IDiagnosticsResult + { + /// + /// Produces a corresponding to this result. + /// + /// + /// The autobuilder to use for constructing the base . + /// + /// + /// An optional severity value which overrides the default severity of the diagnostic. + /// + /// The corresponding to this result. + DiagnosticMessage ToDiagnosticMessage(Autobuilder builder, DiagnosticMessage.TspSeverity? severity = null) where T : AutobuildOptionsShared; + } + + public class DiagnosticRule + { + /// + /// The pattern against which this rule matches build output. + /// + public Regex Pattern { get; } + + /// + /// Constructs a diagnostic rule for the given . + /// + /// + public DiagnosticRule(Regex pattern) + { + this.Pattern = pattern; + } + + /// + /// Constructs a diagnostic rule for the given regular expression . + /// + /// + public DiagnosticRule(string pattern) + { + this.Pattern = new Regex(pattern, RegexOptions.Compiled); + } + + /// + /// Used by a to + /// signal that the rule has matched some build output with . + /// + /// The classifier which is firing the rule. + /// The that resulted from applying the rule. + public virtual void Fire(DiagnosticClassifier classifier, Match match) { } + } + + public class DiagnosticClassifier + { + private readonly List rules; + public readonly List Results; + + public DiagnosticClassifier() + { + this.rules = new List(); + this.Results = new List(); + } + + /// + /// Adds to this classifier. + /// + /// The rule to add. + protected void AddRule(DiagnosticRule rule) + { + this.rules.Add(rule); + } + + /// + /// Applies all of this classifier's rules to to see which match. + /// + /// The line to which the rules should be applied to. + public void ClassifyLine(string line) + { + this.rules.ForEach(rule => + { + var match = rule.Pattern.Match(line); + if (match.Success) + { + rule.Fire(this, match); + } + }); + } + } +} diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/EnvVars.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/EnvVars.cs new file mode 100644 index 00000000000..5d1197f0a9e --- /dev/null +++ b/csharp/autobuilder/Semmle.Autobuild.Shared/EnvVars.cs @@ -0,0 +1,13 @@ +using Semmle.Util; + +namespace Semmle.Autobuild.Shared +{ + public static class EnvVars + { + public const string Platform = "CODEQL_PLATFORM"; + public static string Root(Language language) => $"CODEQL_EXTRACTOR_{language.UpperCaseName}_ROOT"; + public static string TrapDir(Language language) => $"CODEQL_EXTRACTOR_{language.UpperCaseName}_TRAP_DIR"; + public static string SourceArchiveDir(Language language) => $"CODEQL_EXTRACTOR_{language.UpperCaseName}_SOURCE_ARCHIVE_DIR"; + public static string DiagnosticDir(Language language) => $"CODEQL_EXTRACTOR_{language.UpperCaseName}_DIAGNOSTIC_DIR"; + } +} diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/MarkdownUtil.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/MarkdownUtil.cs new file mode 100644 index 00000000000..13a3533eb31 --- /dev/null +++ b/csharp/autobuilder/Semmle.Autobuild.Shared/MarkdownUtil.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Semmle.Autobuild.Shared +{ + public static class MarkdownUtil + { + /// + /// Formats items as markdown inline code. + /// + /// A function which formats items as markdown inline code. + public static readonly Func CodeFormatter = item => $"`{item}`"; + + /// + /// Formats the string as a markdown link. + /// + /// The URL for the link. + /// The text that is displayed. + /// A string containing a markdown-formatted link. + public static string ToMarkdownLink(this string link, string title) => $"[{title}]({link})"; + + /// + /// Renders as a markdown list of the project paths. + /// + /// + /// The list of projects whose paths should be rendered as a markdown list. + /// + /// The maximum number of items to include in the list. + /// Returns the markdown list as a string. + public static string ToMarkdownList(this IEnumerable projects, int? limit = null) + { + return projects.ToMarkdownList(p => $"`{p.FullPath}`", limit); + } + + /// + /// Renders as a markdown list. + /// + /// The item type. + /// The list that should be formatted as a markdown list. + /// A function which converts individual items into a string representation. + /// The maximum number of items to include in the list. + /// Returns the markdown list as a string. + public static string ToMarkdownList(this IEnumerable items, Func formatter, int? limit = null) + { + var sb = new StringBuilder(); + + // if there is a limit, take at most that many items from the start of the list + var list = limit is not null ? items.Take(limit.Value) : items; + sb.Append(string.Join('\n', list.Select(item => $"- {formatter(item)}"))); + + // if there were more items than allowed in the list, add an extra item indicating + // how many more items there were + var length = items.Count(); + + if (limit is not null && length > limit) + { + sb.Append($"\n- and {length - limit} more. View the CodeQL logs for a full list."); + } + + return sb.ToString(); + } + } +} diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/MsBuildRule.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/MsBuildRule.cs index 56858cc87a2..e0e4404f312 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Shared/MsBuildRule.cs +++ b/csharp/autobuilder/Semmle.Autobuild.Shared/MsBuildRule.cs @@ -1,7 +1,6 @@ using Semmle.Util.Logging; -using System; +using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices; namespace Semmle.Autobuild.Shared { @@ -31,6 +30,11 @@ namespace Semmle.Autobuild.Shared /// public class MsBuildRule : IBuildRule { + /// + /// A list of solutions or projects which failed to build. + /// + public readonly List FailedProjectsOrSolutions = new(); + public BuildScript Analyse(IAutobuilder builder, bool auto) { if (!builder.ProjectsOrSolutionsToBuild.Any()) @@ -128,7 +132,13 @@ namespace Semmle.Autobuild.Shared command.Argument(builder.Options.MsBuildArguments); - ret &= command.Script; + // append the build script which invokes msbuild to the overall build script `ret`; + // we insert a check that building the current project or solution was successful: + // if it was not successful, we add it to `FailedProjectsOrSolutions` + ret &= BuildScript.OnFailure(command.Script, ret => + { + FailedProjectsOrSolutions.Add(projectOrSolution); + }); } return ret; diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/ProjectOrSolution.cs b/csharp/autobuilder/Semmle.Autobuild.Shared/ProjectOrSolution.cs index d0c9eeb9669..c0504dbcba4 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Shared/ProjectOrSolution.cs +++ b/csharp/autobuilder/Semmle.Autobuild.Shared/ProjectOrSolution.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Semmle.Util; namespace Semmle.Autobuild.Shared { diff --git a/csharp/autobuilder/Semmle.Autobuild.Shared/Language.cs b/csharp/extractor/Semmle.Util/Language.cs similarity index 95% rename from csharp/autobuilder/Semmle.Autobuild.Shared/Language.cs rename to csharp/extractor/Semmle.Util/Language.cs index c53eb7e592d..fa3d71b6154 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Shared/Language.cs +++ b/csharp/extractor/Semmle.Util/Language.cs @@ -1,4 +1,4 @@ -namespace Semmle.Autobuild.Shared +namespace Semmle.Util { public sealed class Language { diff --git a/csharp/extractor/Semmle.Util/Semmle.Util.csproj b/csharp/extractor/Semmle.Util/Semmle.Util.csproj index b2fa737f7c4..894488f9f84 100644 --- a/csharp/extractor/Semmle.Util/Semmle.Util.csproj +++ b/csharp/extractor/Semmle.Util/Semmle.Util.csproj @@ -15,6 +15,7 @@ + diff --git a/csharp/extractor/Semmle.Util/ToolStatusPage.cs b/csharp/extractor/Semmle.Util/ToolStatusPage.cs new file mode 100644 index 00000000000..d71ad876e1b --- /dev/null +++ b/csharp/extractor/Semmle.Util/ToolStatusPage.cs @@ -0,0 +1,242 @@ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; + +namespace Semmle.Util +{ + /// + /// Represents diagnostic messages for the tool status page. + /// + public class DiagnosticMessage + { + /// + /// Represents sources of diagnostic messages. + /// + public class TspSource + { + /// + /// An identifier under which it makes sense to group this diagnostic message. + /// This is used to build the SARIF reporting descriptor object. + /// + public string Id { get; } + /// + /// Display name for the ID. This is used to build the SARIF reporting descriptor object. + /// + public string Name { get; } + /// + /// Name of the CodeQL extractor. This is used to identify which tool component the reporting descriptor object should be nested under in SARIF. + /// + public string? ExtractorName { get; } + + public TspSource(string id, string name, string? extractorName = null) + { + Id = id; + Name = name; + ExtractorName = extractorName; + } + } + + /// + /// Enumerates severity levels for diagnostics. + /// + [JsonConverter(typeof(StringEnumConverter), typeof(CamelCaseNamingStrategy))] + public enum TspSeverity + { + Note, + Warning, + Error + } + + /// + /// Stores flags indicating where the diagnostic should be displayed. + /// + public class TspVisibility + { + /// + /// A read-only instance of which indicates that the + /// diagnostic should be used in all supported locations. + /// + public static readonly TspVisibility All = new(true, true, true); + + /// + /// True if the message should be displayed on the status page (defaults to false). + /// + public bool? StatusPage { get; } + /// + /// True if the message should be counted in the diagnostics summary table printed by + /// codeql database analyze (defaults to false). + /// + public bool? CLISummaryTable { get; } + /// + /// True if the message should be sent to telemetry (defaults to false). + /// + public bool? Telemetry { get; } + + public TspVisibility(bool? statusPage = null, bool? cliSummaryTable = null, bool? telemetry = null) + { + this.StatusPage = statusPage; + this.CLISummaryTable = cliSummaryTable; + this.Telemetry = telemetry; + } + } + + /// + /// Represents source code locations for diagnostic messages. + /// + public class TspLocation + { + /// + /// Path to the affected file if appropriate, relative to the source root. + /// + public string? File { get; } + /// + /// The line where the range to which the diagnostic relates to starts. + /// + public int? StartLine { get; } + /// + /// The column where the range to which the diagnostic relates to starts. + /// + public int? StartColumn { get; } + /// + /// The line where the range to which the diagnostic relates to ends. + /// + public int? EndLine { get; } + /// + /// The column where the range to which the diagnostic relates to ends. + /// + public int? EndColumn { get; } + + public TspLocation(string? file = null, int? startLine = null, int? startColumn = null, int? endLine = null, int? endColumn = null) + { + this.File = file; + this.StartLine = startLine; + this.StartColumn = startColumn; + this.EndLine = endLine; + this.EndColumn = endColumn; + } + } + + /// + /// ISO 8601 timestamp. + /// + public string Timestamp { get; } + /// + /// The source of the diagnostic message. + /// + public TspSource Source { get; } + /// + /// GitHub flavored Markdown formatted message. Should include inline links to any help pages. + /// + public string? MarkdownMessage { get; } + /// + /// Plain text message. Used by components where the string processing needed to support + /// Markdown is cumbersome. + /// + public string? PlaintextMessage { get; } + /// + /// List of help links intended to supplement . + /// + public List HelpLinks { get; } + /// + /// SARIF severity. + /// + public TspSeverity? Severity { get; } + /// + /// If true, then this message won't be presented to users. + /// + public bool Internal { get; } + public TspVisibility Visibility { get; } + public TspLocation Location { get; } + /// + /// Structured metadata about the diagnostic message. + /// + public Dictionary Attributes { get; } + + public DiagnosticMessage( + Language language, string id, string name, string? markdownMessage = null, string? plaintextMessage = null, + TspVisibility? visibility = null, TspLocation? location = null, TspSeverity? severity = TspSeverity.Error, + DateTime? timestamp = null, bool? intrnl = null + ) + { + this.Source = new TspSource( + id: $"{language.UpperCaseName.ToLower()}/autobuilder/{id}", + name: name, + extractorName: language.UpperCaseName.ToLower() + ); + this.Timestamp = (timestamp ?? DateTime.UtcNow).ToString("o", CultureInfo.InvariantCulture); + this.HelpLinks = new List(); + this.Attributes = new Dictionary(); + this.Severity = severity; + this.Visibility = visibility ?? TspVisibility.All; + this.Location = location ?? new TspLocation(); + this.Internal = intrnl ?? false; + this.MarkdownMessage = markdownMessage; + this.PlaintextMessage = plaintextMessage; + } + } + + /// + /// Provides the ability to write diagnostic messages to some output. + /// + public interface IDiagnosticsWriter + { + /// + /// Adds as a new diagnostics entry. + /// + /// The diagnostics entry to add. + void AddEntry(DiagnosticMessage message); + } + + /// + /// A wrapper around an underlying which allows + /// objects to be serialized to it. + /// + public sealed class DiagnosticsStream : IDiagnosticsWriter, IDisposable + { + private readonly JsonSerializer serializer; + private readonly StreamWriter writer; + + /// + /// Initialises a new for a file at . + /// + /// The path to the file that should be created. + public DiagnosticsStream(string path) + { + this.writer = File.CreateText(path); + + var contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }; + + serializer = new JsonSerializer + { + ContractResolver = contractResolver, + NullValueHandling = NullValueHandling.Ignore + }; + } + + /// + /// Adds as a new diagnostics entry. + /// + /// The diagnostics entry to add. + public void AddEntry(DiagnosticMessage message) + { + serializer.Serialize(writer, message); + writer.Flush(); + } + + /// + /// Releases all resources used by the object. + /// + public void Dispose() + { + writer.Dispose(); + } + } +} diff --git a/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/diagnostics.expected b/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/diagnostics.expected new file mode 100644 index 00000000000..91a9bbbb267 --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/diagnostics.expected @@ -0,0 +1,36 @@ +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL found some projects which cannot be built with .NET Core:\n\n- `test.csproj`", + "severity": "warning", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/dotnet-incompatible-projects", + "name": "Some projects are incompatible with .NET Core" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL was unable to build the following projects using MSBuild:\n\n- `test.csproj`\n\nSet up a [manual build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/msbuild-build-failure", + "name": "Some projects or solutions failed to build using MSBuild" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} diff --git a/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/test.csproj b/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/test.csproj new file mode 100644 index 00000000000..a07aa955494 --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/test.csproj @@ -0,0 +1,66 @@ + + + + + Debug + AnyCPU + {F70CE6B6-1735-4AD2-B1EB-B91FCD1012D1} + Library + Properties + Example + Example + v4.0 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + + + + + + + + + + diff --git a/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/test.py b/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/test.py new file mode 100644 index 00000000000..62c4d3934a4 --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/diag_dotnet_incompatible/test.py @@ -0,0 +1,5 @@ +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], db=None, lang="csharp", runFunction=runUnsuccessfully) +check_diagnostics() diff --git a/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/diagnostics.expected b/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/diagnostics.expected new file mode 100644 index 00000000000..da2b3d93941 --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/diagnostics.expected @@ -0,0 +1,36 @@ +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL was unable to build the following projects using MSBuild:\n\n- `test.sln`\n\nSet up a [manual build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/msbuild-build-failure", + "name": "Some projects or solutions failed to build using MSBuild" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "Some project files were not found when CodeQL built your project:\n\n- `Example.csproj`\n- `Example.Test.csproj`\n\nThis may lead to subsequent failures. You can check for common causes for missing project files:\n\n- Ensure that the project is built using the [intended operating system](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idruns-on) and that filenames on case-sensitive platforms are correctly specified.\n- If your repository uses Git submodules, ensure that those are [checked out](https://github.com/actions/checkout#usage) before the CodeQL action is run.\n- If you auto-generate some project files as part of your build process, ensure that these are generated before the CodeQL action is run.", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/missing-project-files", + "name": "Missing project files" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} diff --git a/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/test.py b/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/test.py new file mode 100644 index 00000000000..62c4d3934a4 --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/test.py @@ -0,0 +1,5 @@ +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], db=None, lang="csharp", runFunction=runUnsuccessfully) +check_diagnostics() diff --git a/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/test.sln b/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/test.sln new file mode 100644 index 00000000000..60a70f32fe2 --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/diag_missing_project_files/test.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example.csproj", "{F70CE6B6-1735-4AD2-B1EB-B91FCD1012D1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.Test", "Example.Test.csproj", "{F4587B5F-9918-4079-9291-5A08CD9761FB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F70CE6B6-1735-4AD2-B1EB-B91FCD1012D1}.Debug|x86.ActiveCfg = Debug|x86 + {F70CE6B6-1735-4AD2-B1EB-B91FCD1012D1}.Debug|x86.Build.0 = Debug|x86 + {F70CE6B6-1735-4AD2-B1EB-B91FCD1012D1}.Release|x86.ActiveCfg = Release|x86 + {F70CE6B6-1735-4AD2-B1EB-B91FCD1012D1}.Release|x86.Build.0 = Release|x86 + {F4587B5F-9918-4079-9291-5A08CD9761FB}.Debug|x86.ActiveCfg = Debug|x86 + {F4587B5F-9918-4079-9291-5A08CD9761FB}.Debug|x86.Build.0 = Debug|x86 + {F4587B5F-9918-4079-9291-5A08CD9761FB}.Release|x86.ActiveCfg = Release|x86 + {F4587B5F-9918-4079-9291-5A08CD9761FB}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/diagnostics.expected b/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/diagnostics.expected new file mode 100644 index 00000000000..0becfa08cee --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/diagnostics.expected @@ -0,0 +1,54 @@ +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL was unable to build the following projects using .NET Core:\n\n- `test.csproj`\n\nSet up a [manual build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/dotnet-build-failure", + "name": "Some projects or solutions failed to build using .NET Core" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL was unable to build the following projects using MSBuild:\n\n- `test.csproj`\n\nSet up a [manual build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/msbuild-build-failure", + "name": "Some projects or solutions failed to build using MSBuild" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "[Configure your workflow](https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-xamarin-applications) for this SDK before running CodeQL.", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/missing-xamarin-ios-sdk", + "name": "Missing Xamarin SDK for iOS" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} diff --git a/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/test.csproj b/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/test.csproj new file mode 100644 index 00000000000..5776266cc03 --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/test.csproj @@ -0,0 +1,11 @@ + + + + Exe + net7.0 + enable + enable + $(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets + + + diff --git a/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/test.py b/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/test.py new file mode 100644 index 00000000000..62c4d3934a4 --- /dev/null +++ b/csharp/ql/integration-tests/all-platforms/diag_missing_xamarin_sdk/test.py @@ -0,0 +1,5 @@ +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], db=None, lang="csharp", runFunction=runUnsuccessfully) +check_diagnostics() diff --git a/csharp/ql/integration-tests/all-platforms/dotnet_build/test.py b/csharp/ql/integration-tests/all-platforms/dotnet_build/test.py index 2568768ad6b..8bc8d33ba1d 100644 --- a/csharp/ql/integration-tests/all-platforms/dotnet_build/test.py +++ b/csharp/ql/integration-tests/all-platforms/dotnet_build/test.py @@ -1,3 +1,5 @@ from create_database_utils import * +from diagnostics_test_utils import * -run_codeql_database_create(['dotnet build'], test_db="default-db", db=None, lang="csharp") +run_codeql_database_create(['dotnet build'], db=None, lang="csharp") +check_diagnostics() diff --git a/csharp/ql/integration-tests/all-platforms/dotnet_pack/test.py b/csharp/ql/integration-tests/all-platforms/dotnet_pack/test.py index 9eac6efe388..b91e73ef11c 100644 --- a/csharp/ql/integration-tests/all-platforms/dotnet_pack/test.py +++ b/csharp/ql/integration-tests/all-platforms/dotnet_pack/test.py @@ -1,8 +1,11 @@ import os from create_database_utils import * +from diagnostics_test_utils import * -run_codeql_database_create(['dotnet pack'], test_db="default-db", db=None, lang="csharp") +run_codeql_database_create(['dotnet pack'], db=None, lang="csharp") ## Check that the NuGet package is created. if not os.path.isfile("bin/Debug/dotnet_pack.1.0.0.nupkg"): - raise Exception("The NuGet package was not created.") + raise Exception("The NuGet package was not created.") + +check_diagnostics() diff --git a/csharp/ql/integration-tests/all-platforms/dotnet_publish/test.py b/csharp/ql/integration-tests/all-platforms/dotnet_publish/test.py index 89fa2a42e0b..10e617dd32e 100644 --- a/csharp/ql/integration-tests/all-platforms/dotnet_publish/test.py +++ b/csharp/ql/integration-tests/all-platforms/dotnet_publish/test.py @@ -1,9 +1,12 @@ import os from create_database_utils import * +from diagnostics_test_utils import * artifacts = 'bin/Temp' -run_codeql_database_create([f"dotnet publish -o {artifacts}"], test_db="default-db", db=None, lang="csharp") +run_codeql_database_create([f"dotnet publish -o {artifacts}"], db=None, lang="csharp") ## Check that the publish folder is created. if not os.path.isdir(artifacts): - raise Exception("The publish artifact folder was not created.") + raise Exception("The publish artifact folder was not created.") + +check_diagnostics() diff --git a/csharp/ql/integration-tests/all-platforms/dotnet_run/test.py b/csharp/ql/integration-tests/all-platforms/dotnet_run/test.py index be1b516ce5a..12ca67463ec 100644 --- a/csharp/ql/integration-tests/all-platforms/dotnet_run/test.py +++ b/csharp/ql/integration-tests/all-platforms/dotnet_run/test.py @@ -1,5 +1,6 @@ import os from create_database_utils import * +from diagnostics_test_utils import * def run_codeql_database_create_stdout(args, dbname): stdout = open(dbname + "file.txt", 'w+') @@ -16,32 +17,40 @@ def check_build_out(msg, s): # no arguments s = run_codeql_database_create_stdout(['dotnet run'], "test-db") check_build_out("Default reply", s) +check_diagnostics() # no arguments, but `--` s = run_codeql_database_create_stdout(['dotnet clean', 'rm -rf test-db', 'dotnet run --'], "test2-db") check_build_out("Default reply", s) +check_diagnostics(diagnostics_dir="test2-db/diagnostic") # one argument, no `--` s = run_codeql_database_create_stdout(['dotnet clean', 'rm -rf test2-db', 'dotnet run hello'], "test3-db") check_build_out("Default reply", s) +check_diagnostics(diagnostics_dir="test3-db/diagnostic") # one argument, but `--` s = run_codeql_database_create_stdout(['dotnet clean', 'rm -rf test3-db', 'dotnet run -- hello'], "test4-db") check_build_out("Default reply", s) +check_diagnostics(diagnostics_dir="test4-db/diagnostic") # two arguments, no `--` s = run_codeql_database_create_stdout(['dotnet clean', 'rm -rf test4-db', 'dotnet run hello world'], "test5-db") check_build_out("hello, world", s) +check_diagnostics(diagnostics_dir="test5-db/diagnostic") # two arguments, and `--` s = run_codeql_database_create_stdout(['dotnet clean', 'rm -rf test5-db', 'dotnet run -- hello world'], "test6-db") check_build_out("hello, world", s) +check_diagnostics(diagnostics_dir="test6-db/diagnostic") # shared compilation enabled; tracer should override by changing the command # to `dotnet run -p:UseSharedCompilation=true -p:UseSharedCompilation=false -- hello world` s = run_codeql_database_create_stdout(['dotnet clean', 'rm -rf test6-db', 'dotnet run -p:UseSharedCompilation=true -- hello world'], "test7-db") check_build_out("hello, world", s) +check_diagnostics(diagnostics_dir="test7-db/diagnostic") # option passed into `dotnet run` s = run_codeql_database_create_stdout(['dotnet clean', 'rm -rf test7-db', 'dotnet build', 'dotnet run --no-build hello world'], "test8-db") check_build_out("hello, world", s) +check_diagnostics(diagnostics_dir="test8-db/diagnostic") diff --git a/csharp/ql/integration-tests/all-platforms/msbuild/test.py b/csharp/ql/integration-tests/all-platforms/msbuild/test.py index 97682d28205..7471e3374e5 100644 --- a/csharp/ql/integration-tests/all-platforms/msbuild/test.py +++ b/csharp/ql/integration-tests/all-platforms/msbuild/test.py @@ -1,4 +1,6 @@ from create_database_utils import * +from diagnostics_test_utils import * # force CodeQL to use MSBuild by setting `LGTM_INDEX_MSBUILD_TARGET` -run_codeql_database_create([], test_db="default-db", db=None, lang="csharp", extra_env={ 'LGTM_INDEX_MSBUILD_TARGET': 'Build' }) +run_codeql_database_create([], db=None, lang="csharp", extra_env={ 'LGTM_INDEX_MSBUILD_TARGET': 'Build' }) +check_diagnostics() diff --git a/csharp/ql/integration-tests/posix-only/diag_autobuild_script/build.sh b/csharp/ql/integration-tests/posix-only/diag_autobuild_script/build.sh new file mode 100755 index 00000000000..2bb8d868bd0 --- /dev/null +++ b/csharp/ql/integration-tests/posix-only/diag_autobuild_script/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 1 diff --git a/csharp/ql/integration-tests/posix-only/diag_autobuild_script/diagnostics.expected b/csharp/ql/integration-tests/posix-only/diag_autobuild_script/diagnostics.expected new file mode 100644 index 00000000000..6fe50ccfa5b --- /dev/null +++ b/csharp/ql/integration-tests/posix-only/diag_autobuild_script/diagnostics.expected @@ -0,0 +1,36 @@ +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL attempted to build your project using a script located at `build.sh`, which failed. Set up a [manual build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/script-failure", + "name": "Unable to build project using build script" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL could not find any project or solution files in your repository. Set up a [manual build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/no-projects-or-solutions", + "name": "No project or solutions files found" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} diff --git a/csharp/ql/integration-tests/posix-only/diag_autobuild_script/test.py b/csharp/ql/integration-tests/posix-only/diag_autobuild_script/test.py new file mode 100644 index 00000000000..62c4d3934a4 --- /dev/null +++ b/csharp/ql/integration-tests/posix-only/diag_autobuild_script/test.py @@ -0,0 +1,5 @@ +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], db=None, lang="csharp", runFunction=runUnsuccessfully) +check_diagnostics() diff --git a/csharp/ql/integration-tests/posix-only/diag_multiple_scripts/build.sh b/csharp/ql/integration-tests/posix-only/diag_multiple_scripts/build.sh new file mode 100755 index 00000000000..2bb8d868bd0 --- /dev/null +++ b/csharp/ql/integration-tests/posix-only/diag_multiple_scripts/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 1 diff --git a/csharp/ql/integration-tests/posix-only/diag_multiple_scripts/diagnostics.expected b/csharp/ql/integration-tests/posix-only/diag_multiple_scripts/diagnostics.expected new file mode 100644 index 00000000000..5ba4bc963eb --- /dev/null +++ b/csharp/ql/integration-tests/posix-only/diag_multiple_scripts/diagnostics.expected @@ -0,0 +1,36 @@ +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL could not find any project or solution files in your repository. Set up a [manual build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/no-projects-or-solutions", + "name": "No project or solutions files found" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL found multiple potential build scripts for your project and attempted to run `build.sh`, which failed. This may not be the right build script for your project. Set up a [manual build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/multiple-build-scripts", + "name": "There are multiple potential build scripts" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} diff --git a/csharp/ql/integration-tests/posix-only/diag_multiple_scripts/scripts/build.sh b/csharp/ql/integration-tests/posix-only/diag_multiple_scripts/scripts/build.sh new file mode 100755 index 00000000000..2bb8d868bd0 --- /dev/null +++ b/csharp/ql/integration-tests/posix-only/diag_multiple_scripts/scripts/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 1 diff --git a/csharp/ql/integration-tests/posix-only/diag_multiple_scripts/test.py b/csharp/ql/integration-tests/posix-only/diag_multiple_scripts/test.py new file mode 100644 index 00000000000..62c4d3934a4 --- /dev/null +++ b/csharp/ql/integration-tests/posix-only/diag_multiple_scripts/test.py @@ -0,0 +1,5 @@ +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], db=None, lang="csharp", runFunction=runUnsuccessfully) +check_diagnostics() diff --git a/csharp/ql/integration-tests/posix-only/dotnet_test/test.py b/csharp/ql/integration-tests/posix-only/dotnet_test/test.py index 6e3cdab30f7..7bc159e6720 100644 --- a/csharp/ql/integration-tests/posix-only/dotnet_test/test.py +++ b/csharp/ql/integration-tests/posix-only/dotnet_test/test.py @@ -1,3 +1,5 @@ from create_database_utils import * +from diagnostics_test_utils import * -run_codeql_database_create(['dotnet test'], test_db="default-db", db=None, lang="csharp") \ No newline at end of file +run_codeql_database_create(['dotnet test'], db=None, lang="csharp") +check_diagnostics() diff --git a/csharp/ql/integration-tests/posix-only/inherit-env-vars/test.py b/csharp/ql/integration-tests/posix-only/inherit-env-vars/test.py index 1b49fb18519..92d90e53ec5 100644 --- a/csharp/ql/integration-tests/posix-only/inherit-env-vars/test.py +++ b/csharp/ql/integration-tests/posix-only/inherit-env-vars/test.py @@ -1,6 +1,8 @@ from create_database_utils import * +from diagnostics_test_utils import * import os os.environ["PROJECT_TO_BUILD"] = "proj.csproj.no_auto" -run_codeql_database_create([], test_db="default-db", db=None, lang="csharp") +run_codeql_database_create([], db=None, lang="csharp") +check_diagnostics() diff --git a/csharp/ql/integration-tests/windows-only/diag_autobuild_script/build.bat b/csharp/ql/integration-tests/windows-only/diag_autobuild_script/build.bat new file mode 100644 index 00000000000..8f7ce95c0eb --- /dev/null +++ b/csharp/ql/integration-tests/windows-only/diag_autobuild_script/build.bat @@ -0,0 +1 @@ +exit /b 1 diff --git a/csharp/ql/integration-tests/windows-only/diag_autobuild_script/diagnostics.expected b/csharp/ql/integration-tests/windows-only/diag_autobuild_script/diagnostics.expected new file mode 100644 index 00000000000..347e3d64342 --- /dev/null +++ b/csharp/ql/integration-tests/windows-only/diag_autobuild_script/diagnostics.expected @@ -0,0 +1,36 @@ +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL attempted to build your project using a script located at `build.bat`, which failed. Set up a [manual build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/script-failure", + "name": "Unable to build project using build script" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL could not find any project or solution files in your repository. Set up a [manual build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/no-projects-or-solutions", + "name": "No project or solutions files found" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} diff --git a/csharp/ql/integration-tests/windows-only/diag_autobuild_script/test.py b/csharp/ql/integration-tests/windows-only/diag_autobuild_script/test.py new file mode 100644 index 00000000000..62c4d3934a4 --- /dev/null +++ b/csharp/ql/integration-tests/windows-only/diag_autobuild_script/test.py @@ -0,0 +1,5 @@ +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], db=None, lang="csharp", runFunction=runUnsuccessfully) +check_diagnostics() diff --git a/csharp/ql/integration-tests/windows-only/diag_multiple_scripts/build.bat b/csharp/ql/integration-tests/windows-only/diag_multiple_scripts/build.bat new file mode 100644 index 00000000000..8f7ce95c0eb --- /dev/null +++ b/csharp/ql/integration-tests/windows-only/diag_multiple_scripts/build.bat @@ -0,0 +1 @@ +exit /b 1 diff --git a/csharp/ql/integration-tests/windows-only/diag_multiple_scripts/diagnostics.expected b/csharp/ql/integration-tests/windows-only/diag_multiple_scripts/diagnostics.expected new file mode 100644 index 00000000000..073ec0ba9c8 --- /dev/null +++ b/csharp/ql/integration-tests/windows-only/diag_multiple_scripts/diagnostics.expected @@ -0,0 +1,36 @@ +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL could not find any project or solution files in your repository. Set up a [manual build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/no-projects-or-solutions", + "name": "No project or solutions files found" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} +{ + "attributes": {}, + "helpLinks": [], + "internal": false, + "location": {}, + "markdownMessage": "CodeQL found multiple potential build scripts for your project and attempted to run `build.bat`, which failed. This may not be the right build script for your project. Set up a [manual build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages).", + "severity": "error", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/multiple-build-scripts", + "name": "There are multiple potential build scripts" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} diff --git a/csharp/ql/integration-tests/windows-only/diag_multiple_scripts/scripts/build.bat b/csharp/ql/integration-tests/windows-only/diag_multiple_scripts/scripts/build.bat new file mode 100644 index 00000000000..8f7ce95c0eb --- /dev/null +++ b/csharp/ql/integration-tests/windows-only/diag_multiple_scripts/scripts/build.bat @@ -0,0 +1 @@ +exit /b 1 diff --git a/csharp/ql/integration-tests/windows-only/diag_multiple_scripts/test.py b/csharp/ql/integration-tests/windows-only/diag_multiple_scripts/test.py new file mode 100644 index 00000000000..62c4d3934a4 --- /dev/null +++ b/csharp/ql/integration-tests/windows-only/diag_multiple_scripts/test.py @@ -0,0 +1,5 @@ +from create_database_utils import * +from diagnostics_test_utils import * + +run_codeql_database_create([], db=None, lang="csharp", runFunction=runUnsuccessfully) +check_diagnostics() diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll index 0afcba55582..d574f95d181 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll @@ -959,6 +959,17 @@ module Impl { not inBarrier(p) } + /** + * Gets an additional term that is added to `branch` and `join` when deciding whether + * the amount of forward or backward branching is within the limit specified by the + * configuration. + */ + pragma[nomagic] + private int getLanguageSpecificFlowIntoCallNodeCand1(ArgNodeEx arg, ParamNodeEx p) { + flowIntoCallNodeCand1(_, arg, p) and + result = getAdditionalFlowIntoCallNodeTerm(arg.projectToNode(), p.projectToNode()) + } + /** * Gets the amount of forward branching on the origin of a cross-call path * edge in the graph of paths between sources and sinks that ignores call @@ -968,6 +979,7 @@ module Impl { private int branch(NodeEx n1) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + + sum(ParamNodeEx p1 | | getLanguageSpecificFlowIntoCallNodeCand1(n1, p1)) } /** @@ -979,6 +991,7 @@ module Impl { private int join(NodeEx n2) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + + sum(ArgNodeEx arg2 | | getLanguageSpecificFlowIntoCallNodeCand1(arg2, n2)) } /** diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll index 8a355d3416a..4edd6dd79bb 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll @@ -2374,3 +2374,12 @@ module Csv { ) } } + +/** + * Gets an additional term that is added to the `join` and `branch` computations to reflect + * an additional forward or backwards branching factor that is not taken into account + * when calculating the (virtual) dispatch cost. + * + * Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter. + */ +int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() } diff --git a/csharp/ql/src/Stubs/Stubs.qll b/csharp/ql/src/Stubs/Stubs.qll index e4787a9b5ce..276f3f7e8ab 100644 --- a/csharp/ql/src/Stubs/Stubs.qll +++ b/csharp/ql/src/Stubs/Stubs.qll @@ -120,15 +120,6 @@ abstract private class GeneratedType extends Type, GeneratedElement { else result = "" } - private string stubComment() { - exists(string qualifier, string name | - this.hasQualifiedName(qualifier, name) and - result = - "// Generated from `" + getQualifiedName(qualifier, name) + "` in `" + - concat(this.getALocation().toString(), "; ") + "`\n" - ) - } - /** Gets the entire C# stub code for this type. */ pragma[nomagic] final string getStub(Assembly assembly) { @@ -141,17 +132,16 @@ abstract private class GeneratedType extends Type, GeneratedElement { else ( not this instanceof DelegateType and result = - this.stubComment() + this.stubAttributes() + stubAccessibility(this) + - this.stubAbstractModifier() + this.stubStaticModifier() + this.stubPartialModifier() + - this.stubKeyword() + " " + this.getUndecoratedName() + stubGenericArguments(this) + - this.stubBaseTypesString() + stubTypeParametersConstraints(this) + "\n{\n" + - this.stubPrivateConstructor() + this.stubMembers(assembly) + "}\n\n" + this.stubAttributes() + stubAccessibility(this) + this.stubAbstractModifier() + + this.stubStaticModifier() + this.stubPartialModifier() + this.stubKeyword() + " " + + this.getUndecoratedName() + stubGenericArguments(this) + this.stubBaseTypesString() + + stubTypeParametersConstraints(this) + "\n{\n" + this.stubPrivateConstructor() + + this.stubMembers(assembly) + "}\n\n" or result = - this.stubComment() + this.stubAttributes() + stubUnsafe(this) + stubAccessibility(this) + - this.stubKeyword() + " " + stubClassName(this.(DelegateType).getReturnType()) + " " + - this.getUndecoratedName() + stubGenericArguments(this) + "(" + stubParameters(this) + - ");\n\n" + this.stubAttributes() + stubUnsafe(this) + stubAccessibility(this) + this.stubKeyword() + + " " + stubClassName(this.(DelegateType).getReturnType()) + " " + this.getUndecoratedName() + + stubGenericArguments(this) + "(" + stubParameters(this) + ");\n\n" ) } @@ -400,7 +390,7 @@ private string stubAccessibility(Member m) { if m.getDeclaringType() instanceof Interface or - exists(m.(Virtualizable).getExplicitlyImplementedInterface()) + exists(getExplicitImplementedInterface(m)) or m instanceof Constructor and m.isStatic() then result = "" @@ -443,7 +433,7 @@ private string stubStaticOrConst(Member m) { } private string stubOverride(Member m) { - if m.getDeclaringType() instanceof Interface + if m.getDeclaringType() instanceof Interface and not m.isStatic() then result = "" else if m.(Virtualizable).isVirtual() @@ -713,9 +703,49 @@ private string stubEventAccessors(Event e) { else result = ";" } +/** + * Returns an interface that `c` explicitly implements, if either of the + * following also holds. + * (1) `c` is not static. + * (2) `c` is static and an implementation of a generic with type constraints. + * (3) `c` is static and there is another member with the same name + * but different return type. + * + * We use these rules as explicit interfaces are needed in some cases + * for compilation purposes (both to distinguish members but also to ensure + * type constraints are satisfied). We can't always use explicit interface + * implementation due to the generic math support, because then in some cases + * we will only be able to access a static via a type variable with type + * constraints (C# 11 language feature). + */ +private Interface getExplicitImplementedInterface(Virtualizable c) { + result = unique(Interface i | i = c.getExplicitlyImplementedInterface()) and + ( + not c.isStatic() + or + c.isStatic() and + ( + not c instanceof Method + or + c instanceof Method and + ( + exists(TypeParameter t | t = c.getImplementee().(UnboundGeneric).getATypeParameter() | + exists(t.getConstraints().getATypeConstraint()) + ) + or + exists(Member m | + (not m.isStatic() or m.(Method).getReturnType() != c.(Method).getReturnType()) and + m.getName() = c.getName() and + m.getDeclaringType() = c.getDeclaringType() + ) + ) + ) + ) +} + private string stubExplicitImplementation(Member c) { - if exists(c.(Virtualizable).getExplicitlyImplementedInterface()) - then result = stubClassName(c.(Virtualizable).getExplicitlyImplementedInterface()) + "." + if exists(getExplicitImplementedInterface(c)) + then result = stubClassName(getExplicitImplementedInterface(c)) + "." else result = "" } @@ -740,14 +770,16 @@ private string stubOperator(Operator o, Assembly assembly) { if o instanceof ConversionOperator then result = - " " + stubModifiers(o) + stubExplicit(o) + "operator " + stubClassName(o.getReturnType()) + - "(" + stubParameters(o) + ") => throw null;\n" + " " + stubModifiers(o) + stubExplicit(o) + stubExplicitImplementation(o) + "operator " + + stubChecked(o) + stubClassName(o.getReturnType()) + "(" + stubParameters(o) + ")" + + stubImplementation(o) + ";\n" else if not o.getDeclaringType() instanceof Enum then result = - " " + stubModifiers(o) + stubClassName(o.getReturnType()) + " operator " + o.getName() + - "(" + stubParameters(o) + ") => throw null;\n" + " " + stubModifiers(o) + stubClassName(o.getReturnType()) + " " + + stubExplicitImplementation(o) + "operator " + o.getName() + "(" + stubParameters(o) + ")" + + stubImplementation(o) + ";\n" else result = " // Stub generator skipped operator: " + o.getName() + "\n" } @@ -888,7 +920,16 @@ private string stubConstructorInitializer(Constructor c) { private string stubExplicit(ConversionOperator op) { op instanceof ImplicitConversionOperator and result = "implicit " or - op instanceof ExplicitConversionOperator and result = "explicit " + ( + op instanceof ExplicitConversionOperator + or + op instanceof CheckedExplicitConversionOperator + ) and + result = "explicit " +} + +private string stubChecked(Operator o) { + if o instanceof CheckedExplicitConversionOperator then result = "checked " else result = "" } private string stubGetter(DeclarationWithGetSetAccessors p) { @@ -917,6 +958,8 @@ private string stubSemmleExtractorOptions() { /** Gets the generated C# code. */ string generatedCode(Assembly assembly) { result = - "// This file contains auto-generated code.\n" + stubSemmleExtractorOptions() + "\n" + - any(GeneratedNamespace ns | ns.isGlobalNamespace()).getStubs(assembly) + "// This file contains auto-generated code.\n" // + + "// Generated from `" + assembly.getFullName() + "`.\n" // + + stubSemmleExtractorOptions() + "\n" // + + any(GeneratedNamespace ns | ns.isGlobalNamespace()).getStubs(assembly) } diff --git a/csharp/ql/src/Stubs/make_stubs_nuget.py b/csharp/ql/src/Stubs/make_stubs_nuget.py index 2e7d093beba..09aadc366fc 100644 --- a/csharp/ql/src/Stubs/make_stubs_nuget.py +++ b/csharp/ql/src/Stubs/make_stubs_nuget.py @@ -109,7 +109,7 @@ with open(jsonFile) as json_data: print("\n --> Generated stub files: " + rawSrcOutputDir) print("\n* Formatting files") -run_cmd(['dotnet', 'format', rawSrcOutputDir]) +run_cmd(['dotnet', 'format', 'whitespace', rawSrcOutputDir]) print("\n --> Generated (formatted) stub files: " + rawSrcOutputDir) diff --git a/csharp/ql/test/query-tests/Stubs/All/AllStubs.expected b/csharp/ql/test/query-tests/Stubs/All/AllStubs.expected index 2cef6a86400..187485652aa 100644 --- a/csharp/ql/test/query-tests/Stubs/All/AllStubs.expected +++ b/csharp/ql/test/query-tests/Stubs/All/AllStubs.expected @@ -1 +1 @@ -| // This file contains auto-generated code.\n\nnamespace A1\n{\n// Generated from `A1.C1` in `Test.cs:185:18:185:19; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class C1\n{\n}\n\n}\nnamespace A2\n{\nnamespace B2\n{\n// Generated from `A2.B2.C2` in `Test.cs:192:22:192:23; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class C2\n{\n}\n\n}\n}\nnamespace A3\n{\n// Generated from `A3.C3` in `Test.cs:198:18:198:19; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class C3\n{\n}\n\n}\nnamespace A4\n{\n// Generated from `A4.C4` in `Test.cs:208:18:208:19; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class C4\n{\n}\n\nnamespace B4\n{\n// Generated from `A4.B4.D4` in `Test.cs:205:22:205:23; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class D4\n{\n}\n\n}\n}\nnamespace Test\n{\n// Generated from `Test.Class1` in `Test.cs:5:18:5:23; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Class1\n{\n// Generated from `Test.Class1+Class11` in `Test.cs:34:22:34:28; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Class11 : Test.Class1.Interface1, Test.Class1.Interface2\n{\n int Test.Class1.Interface2.this[int i] { get => throw null; }\n public void Method1() => throw null;\n void Test.Class1.Interface2.Method2() => throw null;\n}\n\n\n// Generated from `Test.Class1+Class12` in `Test.cs:51:22:51:28; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Class12 : Test.Class1.Class11\n{\n}\n\n\n// Generated from `Test.Class1+Class13` in `Test.cs:63:31:63:37; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic abstract class Class13\n{\n protected internal virtual void M() => throw null;\n public virtual void M1() where T: Test.Class1.Class13 => throw null;\n public abstract void M2();\n}\n\n\n// Generated from `Test.Class1+Class14` in `Test.cs:70:31:70:37; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic abstract class Class14 : Test.Class1.Class13\n{\n protected internal override void M() => throw null;\n public override void M1() => throw null;\n public abstract override void M2();\n}\n\n\n// Generated from `Test.Class1+Delegate1<>` in `Test.cs:47:30:47:41; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic delegate void Delegate1(T i, int j);\n\n\n// Generated from `Test.Class1+GenericType<>` in `Test.cs:56:22:56:35; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class GenericType\n{\n// Generated from `Test.Class1+GenericType<>+X` in `Test.cs:58:26:58:26; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class X\n{\n}\n\n\n}\n\n\n// Generated from `Test.Class1+Interface1` in `Test.cs:18:26:18:35; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic interface Interface1\n{\n void Method1();\n}\n\n\n// Generated from `Test.Class1+Interface2` in `Test.cs:23:38:23:47; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\nprotected internal interface Interface2\n{\n int this[int i] { get; }\n void Method2();\n}\n\n\n// Generated from `Test.Class1+Struct1` in `Test.cs:7:23:7:29; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic struct Struct1\n{\n public void Method(Test.Class1.Struct1 s = default(Test.Class1.Struct1)) => throw null;\n public int i;\n public static int j = default;\n public System.ValueTuple t1;\n public (int,int) t2;\n}\n\n\n public event Test.Class1.Delegate1 Event1;\n public Test.Class1.GenericType.X Prop { get => throw null; }\n}\n\n// Generated from `Test.Class10` in `Test.cs:138:18:138:24; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Class10\n{\n unsafe public void M1(delegate* unmanaged f) => throw null;\n}\n\n// Generated from `Test.Class3` in `Test.cs:84:18:84:23; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Class3\n{\n public object Item { get => throw null; set => throw null; }\n [System.Runtime.CompilerServices.IndexerName("MyItem")]\n public object this[string index] { get => throw null; set => throw null; }\n}\n\n// Generated from `Test.Class4` in `Test.cs:91:18:91:23; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Class4\n{\n unsafe public void M(int* p) => throw null;\n}\n\n// Generated from `Test.Class5` in `Test.cs:102:18:102:23; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Class5 : Test.IInterface1\n{\n public void M2() => throw null;\n}\n\n// Generated from `Test.Class6<>` in `Test.cs:107:18:107:26; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Class6 where T: class, Test.IInterface1\n{\n public virtual void M1() where T: class, Test.IInterface1, new() => throw null;\n}\n\n// Generated from `Test.Class7` in `Test.cs:114:18:114:23; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Class7 : Test.Class6\n{\n public override void M1() where T: class => throw null;\n}\n\n// Generated from `Test.Class8` in `Test.cs:121:18:121:23; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Class8\n{\n public static int @this = default;\n}\n\n// Generated from `Test.Class9` in `Test.cs:126:18:126:23; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Class9\n{\n// Generated from `Test.Class9+Nested` in `Test.cs:130:22:130:27; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Nested : Test.Class9\n{\n}\n\n\n public Test.Class9.Nested NestedInstance { get => throw null; }\n}\n\n// Generated from `Test.Enum1` in `Test.cs:143:17:143:21; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic enum Enum1 : int\n{\n None1 = 0,\n Some11 = 1,\n Some12 = 2,\n}\n\n// Generated from `Test.Enum2` in `Test.cs:150:17:150:21; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic enum Enum2 : int\n{\n None2 = 2,\n Some21 = 1,\n Some22 = 3,\n}\n\n// Generated from `Test.Enum3` in `Test.cs:157:17:157:21; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic enum Enum3 : int\n{\n None3 = 2,\n Some31 = 1,\n Some32 = 0,\n}\n\n// Generated from `Test.Enum4` in `Test.cs:164:17:164:21; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic enum Enum4 : int\n{\n None4 = 2,\n Some41 = 7,\n Some42 = 6,\n}\n\n// Generated from `Test.EnumLong` in `Test.cs:171:17:171:24; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic enum EnumLong : long\n{\n None = 10,\n Some = 223372036854775807,\n}\n\n// Generated from `Test.IInterface1` in `Test.cs:96:22:96:32; Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic interface IInterface1\n{\n void M1() => throw null;\n void M2();\n}\n\n}\n\n\n | +| // This file contains auto-generated code.\n// Generated from `Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`.\n\nnamespace A1\n{\npublic class C1\n{\n}\n\n}\nnamespace A2\n{\nnamespace B2\n{\npublic class C2\n{\n}\n\n}\n}\nnamespace A3\n{\npublic class C3\n{\n}\n\n}\nnamespace A4\n{\npublic class C4\n{\n}\n\nnamespace B4\n{\npublic class D4\n{\n}\n\n}\n}\nnamespace Test\n{\npublic class Class1\n{\npublic class Class11 : Test.Class1.Interface1, Test.Class1.Interface2\n{\n int Test.Class1.Interface2.this[int i] { get => throw null; }\n public void Method1() => throw null;\n void Test.Class1.Interface2.Method2() => throw null;\n}\n\n\npublic class Class12 : Test.Class1.Class11\n{\n}\n\n\npublic abstract class Class13\n{\n protected internal virtual void M() => throw null;\n public virtual void M1() where T: Test.Class1.Class13 => throw null;\n public abstract void M2();\n}\n\n\npublic abstract class Class14 : Test.Class1.Class13\n{\n protected internal override void M() => throw null;\n public override void M1() => throw null;\n public abstract override void M2();\n}\n\n\npublic delegate void Delegate1(T i, int j);\n\n\npublic class GenericType\n{\npublic class X\n{\n}\n\n\n}\n\n\npublic interface Interface1\n{\n void Method1();\n}\n\n\nprotected internal interface Interface2\n{\n int this[int i] { get; }\n void Method2();\n}\n\n\npublic struct Struct1\n{\n public void Method(Test.Class1.Struct1 s = default(Test.Class1.Struct1)) => throw null;\n public int i;\n public static int j = default;\n public System.ValueTuple t1;\n public (int,int) t2;\n}\n\n\n public event Test.Class1.Delegate1 Event1;\n public Test.Class1.GenericType.X Prop { get => throw null; }\n}\n\npublic class Class10\n{\n unsafe public void M1(delegate* unmanaged f) => throw null;\n}\n\npublic class Class11 : Test.IInterface2, Test.IInterface3\n{\n static Test.Class11 Test.IInterface2.operator *(Test.Class11 left, Test.Class11 right) => throw null;\n public static Test.Class11 operator +(Test.Class11 left, Test.Class11 right) => throw null;\n public static Test.Class11 operator -(Test.Class11 left, Test.Class11 right) => throw null;\n static Test.Class11 Test.IInterface2.operator /(Test.Class11 left, Test.Class11 right) => throw null;\n public void M1() => throw null;\n void Test.IInterface2.M2() => throw null;\n public static explicit operator System.Int16(Test.Class11 n) => throw null;\n static explicit Test.IInterface2.operator int(Test.Class11 n) => throw null;\n}\n\npublic class Class3\n{\n public object Item { get => throw null; set => throw null; }\n [System.Runtime.CompilerServices.IndexerName("MyItem")]\n public object this[string index] { get => throw null; set => throw null; }\n}\n\npublic class Class4\n{\n unsafe public void M(int* p) => throw null;\n}\n\npublic class Class5 : Test.IInterface1\n{\n public void M2() => throw null;\n}\n\npublic class Class6 where T: class, Test.IInterface1\n{\n public virtual void M1() where T: class, Test.IInterface1, new() => throw null;\n}\n\npublic class Class7 : Test.Class6\n{\n public override void M1() where T: class => throw null;\n}\n\npublic class Class8\n{\n public static int @this = default;\n}\n\npublic class Class9\n{\npublic class Nested : Test.Class9\n{\n}\n\n\n public Test.Class9.Nested NestedInstance { get => throw null; }\n}\n\npublic enum Enum1 : int\n{\n None1 = 0,\n Some11 = 1,\n Some12 = 2,\n}\n\npublic enum Enum2 : int\n{\n None2 = 2,\n Some21 = 1,\n Some22 = 3,\n}\n\npublic enum Enum3 : int\n{\n None3 = 2,\n Some31 = 1,\n Some32 = 0,\n}\n\npublic enum Enum4 : int\n{\n None4 = 2,\n Some41 = 7,\n Some42 = 6,\n}\n\npublic enum EnumLong : long\n{\n None = 10,\n Some = 223372036854775807,\n}\n\npublic interface IInterface1\n{\n void M1() => throw null;\n void M2();\n}\n\npublic interface IInterface2 where T: Test.IInterface2\n{\n static abstract T operator *(T left, T right);\n static abstract T operator +(T left, T right);\n static virtual T operator -(T left, T right) => throw null;\n static virtual T operator /(T left, T right) => throw null;\n void M1();\n void M2();\n static abstract explicit operator System.Int16(T n);\n static abstract explicit operator int(T n);\n}\n\npublic interface IInterface3 where T: Test.IInterface3\n{\n static abstract T operator +(T left, T right);\n static virtual T operator -(T left, T right) => throw null;\n void M1();\n static abstract explicit operator System.Int16(T n);\n}\n\n}\n\n\n | diff --git a/csharp/ql/test/query-tests/Stubs/All/Test.cs b/csharp/ql/test/query-tests/Stubs/All/Test.cs index 2b46b8ec987..d8a8fdb443c 100644 --- a/csharp/ql/test/query-tests/Stubs/All/Test.cs +++ b/csharp/ql/test/query-tests/Stubs/All/Test.cs @@ -140,6 +140,38 @@ namespace Test unsafe public void M1(delegate* unmanaged f) => throw null; } + public interface IInterface2 where T : IInterface2 + { + static abstract T operator +(T left, T right); + static virtual T operator -(T left, T right) => throw null; + static abstract T operator *(T left, T right); + static virtual T operator /(T left, T right) => throw null; + static abstract explicit operator short(T n); + static abstract explicit operator int(T n); + void M1(); + void M2(); + } + + public interface IInterface3 where T : IInterface3 + { + static abstract T operator +(T left, T right); + static virtual T operator -(T left, T right) => throw null; + static abstract explicit operator short(T n); + void M1(); + } + + public class Class11 : IInterface2, IInterface3 + { + public static Class11 operator +(Class11 left, Class11 right) => throw null; + public static Class11 operator -(Class11 left, Class11 right) => throw null; + static Class11 IInterface2.operator *(Class11 left, Class11 right) => throw null; + static Class11 IInterface2.operator /(Class11 left, Class11 right) => throw null; + public void M1() => throw null; + void IInterface2.M2() => throw null; + public static explicit operator short(Class11 n) => 0; + static explicit IInterface2.operator int(Class11 n) => 0; + } + public enum Enum1 { None1, diff --git a/csharp/ql/test/query-tests/Stubs/Minimal/MinimalStubsFromSource.expected b/csharp/ql/test/query-tests/Stubs/Minimal/MinimalStubsFromSource.expected index ca740f0a899..629addbb336 100644 --- a/csharp/ql/test/query-tests/Stubs/Minimal/MinimalStubsFromSource.expected +++ b/csharp/ql/test/query-tests/Stubs/Minimal/MinimalStubsFromSource.expected @@ -1 +1 @@ -| // This file contains auto-generated code.\n\nnamespace System\n{\n// Generated from `System.Uri` in `System.Private.Uri, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class Uri : System.Runtime.Serialization.ISerializable\n{\n public override bool Equals(object comparand) => throw null;\n public override int GetHashCode() => throw null;\n void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) => throw null;\n public override string ToString() => throw null;\n}\n\n}\n\n\n// This file contains auto-generated code.\n\nnamespace System\n{\nnamespace Collections\n{\n// Generated from `System.Collections.SortedList` in `System.Collections.NonGeneric, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class SortedList : System.Collections.ICollection, System.Collections.IDictionary, System.Collections.IEnumerable, System.ICloneable\n{\n public virtual void Add(object key, object value) => throw null;\n public virtual void Clear() => throw null;\n public virtual object Clone() => throw null;\n public virtual bool Contains(object key) => throw null;\n public virtual void CopyTo(System.Array array, int arrayIndex) => throw null;\n public virtual int Count { get => throw null; }\n public virtual object GetByIndex(int index) => throw null;\n public virtual System.Collections.IDictionaryEnumerator GetEnumerator() => throw null;\n System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null;\n public virtual bool IsFixedSize { get => throw null; }\n public virtual bool IsReadOnly { get => throw null; }\n public virtual bool IsSynchronized { get => throw null; }\n public virtual object this[object key] { get => throw null; set => throw null; }\n public virtual System.Collections.ICollection Keys { get => throw null; }\n public virtual void Remove(object key) => throw null;\n public virtual object SyncRoot { get => throw null; }\n public virtual System.Collections.ICollection Values { get => throw null; }\n}\n\n}\n}\n\n\n// This file contains auto-generated code.\n\nnamespace System\n{\nnamespace Collections\n{\nnamespace Generic\n{\n// Generated from `System.Collections.Generic.Stack<>` in `System.Collections, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class Stack : System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.ICollection, System.Collections.IEnumerable\n{\n void System.Collections.ICollection.CopyTo(System.Array array, int arrayIndex) => throw null;\n public int Count { get => throw null; }\n System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() => throw null;\n System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null;\n bool System.Collections.ICollection.IsSynchronized { get => throw null; }\n public T Peek() => throw null;\n object System.Collections.ICollection.SyncRoot { get => throw null; }\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n\nnamespace System\n{\nnamespace Collections\n{\nnamespace Specialized\n{\n// Generated from `System.Collections.Specialized.NameObjectCollectionBase` in `System.Collections.Specialized, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic abstract class NameObjectCollectionBase : System.Collections.ICollection, System.Collections.IEnumerable, System.Runtime.Serialization.IDeserializationCallback, System.Runtime.Serialization.ISerializable\n{\n void System.Collections.ICollection.CopyTo(System.Array array, int index) => throw null;\n public virtual int Count { get => throw null; }\n public virtual System.Collections.IEnumerator GetEnumerator() => throw null;\n public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) => throw null;\n bool System.Collections.ICollection.IsSynchronized { get => throw null; }\n public virtual void OnDeserialization(object sender) => throw null;\n object System.Collections.ICollection.SyncRoot { get => throw null; }\n}\n\n// Generated from `System.Collections.Specialized.NameValueCollection` in `System.Collections.Specialized, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class NameValueCollection : System.Collections.Specialized.NameObjectCollectionBase\n{\n public string this[string name] { get => throw null; set => throw null; }\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n\nnamespace System\n{\nnamespace ComponentModel\n{\n// Generated from `System.ComponentModel.ComponentConverter` in `System.ComponentModel.TypeConverter, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class ComponentConverter : System.ComponentModel.ReferenceConverter\n{\n}\n\n// Generated from `System.ComponentModel.DefaultEventAttribute` in `System.ComponentModel.TypeConverter, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class DefaultEventAttribute : System.Attribute\n{\n public DefaultEventAttribute(string name) => throw null;\n public override bool Equals(object obj) => throw null;\n public override int GetHashCode() => throw null;\n}\n\n// Generated from `System.ComponentModel.DefaultPropertyAttribute` in `System.ComponentModel.TypeConverter, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class DefaultPropertyAttribute : System.Attribute\n{\n public DefaultPropertyAttribute(string name) => throw null;\n public override bool Equals(object obj) => throw null;\n public override int GetHashCode() => throw null;\n}\n\n// Generated from `System.ComponentModel.ReferenceConverter` in `System.ComponentModel.TypeConverter, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class ReferenceConverter : System.ComponentModel.TypeConverter\n{\n}\n\n// Generated from `System.ComponentModel.TypeConverter` in `System.ComponentModel.TypeConverter, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class TypeConverter\n{\n}\n\n}\nnamespace Timers\n{\n// Generated from `System.Timers.TimersDescriptionAttribute` in `System.ComponentModel.TypeConverter, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class TimersDescriptionAttribute\n{\n public TimersDescriptionAttribute(string description) => throw null;\n internal TimersDescriptionAttribute(string description, string unused) => throw null;\n}\n\n}\n}\n\n\n// This file contains auto-generated code.\n\nnamespace System\n{\nnamespace ComponentModel\n{\n// Generated from `System.ComponentModel.TypeConverterAttribute` in `System.ObjectModel, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class TypeConverterAttribute : System.Attribute\n{\n public override bool Equals(object obj) => throw null;\n public override int GetHashCode() => throw null;\n public TypeConverterAttribute() => throw null;\n public TypeConverterAttribute(System.Type type) => throw null;\n public TypeConverterAttribute(string typeName) => throw null;\n}\n\n// Generated from `System.ComponentModel.TypeDescriptionProviderAttribute` in `System.ObjectModel, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class TypeDescriptionProviderAttribute : System.Attribute\n{\n public TypeDescriptionProviderAttribute(System.Type type) => throw null;\n public TypeDescriptionProviderAttribute(string typeName) => throw null;\n}\n\n}\nnamespace Windows\n{\nnamespace Markup\n{\n// Generated from `System.Windows.Markup.ValueSerializerAttribute` in `System.ObjectModel, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class ValueSerializerAttribute : System.Attribute\n{\n public ValueSerializerAttribute(System.Type valueSerializerType) => throw null;\n public ValueSerializerAttribute(string valueSerializerTypeName) => throw null;\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n\nnamespace System\n{\nnamespace Linq\n{\n// Generated from `System.Linq.Enumerable` in `System.Linq, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic static class Enumerable\n{\n public static System.Collections.Generic.IEnumerable Select(this System.Collections.Generic.IEnumerable source, System.Func selector) => throw null;\n}\n\n}\n}\n\n\n// This file contains auto-generated code.\n\nnamespace System\n{\nnamespace Linq\n{\n// Generated from `System.Linq.IQueryable` in `System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic interface IQueryable : System.Collections.IEnumerable\n{\n}\n\n}\nnamespace Runtime\n{\nnamespace CompilerServices\n{\n// Generated from `System.Runtime.CompilerServices.CallSite` in `System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class CallSite\n{\n internal CallSite(System.Runtime.CompilerServices.CallSiteBinder binder) => throw null;\n}\n\n// Generated from `System.Runtime.CompilerServices.CallSite<>` in `System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class CallSite : System.Runtime.CompilerServices.CallSite where T: class\n{\n private CallSite() : base(default(System.Runtime.CompilerServices.CallSiteBinder)) => throw null;\n private CallSite(System.Runtime.CompilerServices.CallSiteBinder binder) : base(default(System.Runtime.CompilerServices.CallSiteBinder)) => throw null;\n}\n\n// Generated from `System.Runtime.CompilerServices.CallSiteBinder` in `System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic abstract class CallSiteBinder\n{\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n\nnamespace System\n{\nnamespace Linq\n{\n// Generated from `System.Linq.ParallelEnumerable` in `System.Linq.Parallel, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic static class ParallelEnumerable\n{\n public static System.Linq.ParallelQuery AsParallel(this System.Collections.IEnumerable source) => throw null;\n}\n\n// Generated from `System.Linq.ParallelQuery` in `System.Linq.Parallel, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class ParallelQuery : System.Collections.IEnumerable\n{\n System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null;\n internal ParallelQuery(System.Linq.Parallel.QuerySettings specifiedSettings) => throw null;\n}\n\nnamespace Parallel\n{\n// Generated from `System.Linq.Parallel.QuerySettings` in `System.Linq.Parallel, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\ninternal struct QuerySettings\n{\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n\nnamespace System\n{\nnamespace Linq\n{\n// Generated from `System.Linq.Queryable` in `System.Linq.Queryable, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic static class Queryable\n{\n public static System.Linq.IQueryable AsQueryable(this System.Collections.IEnumerable source) => throw null;\n}\n\n}\n}\n\n\n// This file contains auto-generated code.\n\nnamespace System\n{\nnamespace Runtime\n{\nnamespace Serialization\n{\n// Generated from `System.Runtime.Serialization.DataContractAttribute` in `System.Runtime.Serialization.Primitives, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class DataContractAttribute : System.Attribute\n{\n public DataContractAttribute() => throw null;\n}\n\n// Generated from `System.Runtime.Serialization.DataMemberAttribute` in `System.Runtime.Serialization.Primitives, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class DataMemberAttribute : System.Attribute\n{\n public DataMemberAttribute() => throw null;\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n\nnamespace System\n{\nnamespace Text\n{\nnamespace RegularExpressions\n{\n// Generated from `System.Text.RegularExpressions.Capture` in `System.Text.RegularExpressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class Capture\n{\n internal Capture(string text, int index, int length) => throw null;\n public override string ToString() => throw null;\n}\n\n// Generated from `System.Text.RegularExpressions.GeneratedRegexAttribute` in `System.Text.RegularExpressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class GeneratedRegexAttribute : System.Attribute\n{\n public GeneratedRegexAttribute(string pattern) => throw null;\n public GeneratedRegexAttribute(string pattern, System.Text.RegularExpressions.RegexOptions options) => throw null;\n public GeneratedRegexAttribute(string pattern, System.Text.RegularExpressions.RegexOptions options, int matchTimeoutMilliseconds) => throw null;\n public GeneratedRegexAttribute(string pattern, System.Text.RegularExpressions.RegexOptions options, int matchTimeoutMilliseconds, string cultureName) => throw null;\n public GeneratedRegexAttribute(string pattern, System.Text.RegularExpressions.RegexOptions options, string cultureName) => throw null;\n}\n\n// Generated from `System.Text.RegularExpressions.Group` in `System.Text.RegularExpressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class Group : System.Text.RegularExpressions.Capture\n{\n internal Group(string text, int[] caps, int capcount, string name) : base(default(string), default(int), default(int)) => throw null;\n}\n\n// Generated from `System.Text.RegularExpressions.Match` in `System.Text.RegularExpressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class Match : System.Text.RegularExpressions.Group\n{\n internal Match(System.Text.RegularExpressions.Regex regex, int capcount, string text, int textLength) : base(default(string), default(int[]), default(int), default(string)) => throw null;\n}\n\n// Generated from `System.Text.RegularExpressions.Regex` in `System.Text.RegularExpressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\npublic class Regex : System.Runtime.Serialization.ISerializable\n{\n void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo si, System.Runtime.Serialization.StreamingContext context) => throw null;\n public System.Text.RegularExpressions.Match Match(string input) => throw null;\n public static System.Text.RegularExpressions.Match Match(string input, string pattern) => throw null;\n public static System.Text.RegularExpressions.Match Match(string input, string pattern, System.Text.RegularExpressions.RegexOptions options, System.TimeSpan matchTimeout) => throw null;\n public Regex(string pattern) => throw null;\n public Regex(string pattern, System.Text.RegularExpressions.RegexOptions options, System.TimeSpan matchTimeout) => throw null;\n public string Replace(string input, string replacement) => throw null;\n public override string ToString() => throw null;\n}\n\n// Generated from `System.Text.RegularExpressions.RegexOptions` in `System.Text.RegularExpressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`\n[System.Flags]\npublic enum RegexOptions : int\n{\n IgnoreCase = 1,\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n\nnamespace System\n{\nnamespace Web\n{\n// Generated from `System.Web.HtmlString` in `../../../resources/stubs/System.Web.cs:34:18:34:27; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class HtmlString : System.Web.IHtmlString\n{\n}\n\n// Generated from `System.Web.HttpContextBase` in `../../../resources/stubs/System.Web.cs:24:18:24:32; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class HttpContextBase\n{\n public virtual System.Web.HttpRequestBase Request { get => throw null; }\n}\n\n// Generated from `System.Web.HttpCookie` in `../../../resources/stubs/System.Web.cs:174:18:174:27; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class HttpCookie\n{\n}\n\n// Generated from `System.Web.HttpCookieCollection` in `../../../resources/stubs/System.Web.cs:192:27:192:46; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic abstract class HttpCookieCollection : System.Collections.Specialized.NameObjectCollectionBase\n{\n}\n\n// Generated from `System.Web.HttpRequest` in `../../../resources/stubs/System.Web.cs:145:18:145:28; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class HttpRequest\n{\n}\n\n// Generated from `System.Web.HttpRequestBase` in `../../../resources/stubs/System.Web.cs:10:18:10:32; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class HttpRequestBase\n{\n public virtual System.Collections.Specialized.NameValueCollection QueryString { get => throw null; }\n}\n\n// Generated from `System.Web.HttpResponse` in `../../../resources/stubs/System.Web.cs:156:18:156:29; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class HttpResponse\n{\n}\n\n// Generated from `System.Web.HttpResponseBase` in `../../../resources/stubs/System.Web.cs:19:18:19:33; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class HttpResponseBase\n{\n}\n\n// Generated from `System.Web.HttpServerUtility` in `../../../resources/stubs/System.Web.cs:41:18:41:34; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class HttpServerUtility\n{\n}\n\n// Generated from `System.Web.IHtmlString` in `../../../resources/stubs/System.Web.cs:30:22:30:32; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic interface IHtmlString\n{\n}\n\n// Generated from `System.Web.IHttpHandler` in `../../../resources/stubs/System.Web.cs:132:22:132:33; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic interface IHttpHandler\n{\n}\n\n// Generated from `System.Web.IServiceProvider` in `../../../resources/stubs/System.Web.cs:136:22:136:37; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic interface IServiceProvider\n{\n}\n\n// Generated from `System.Web.UnvalidatedRequestValues` in `../../../resources/stubs/System.Web.cs:140:18:140:41; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class UnvalidatedRequestValues\n{\n}\n\n// Generated from `System.Web.UnvalidatedRequestValuesBase` in `../../../resources/stubs/System.Web.cs:5:18:5:45; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class UnvalidatedRequestValuesBase\n{\n}\n\nnamespace Mvc\n{\n// Generated from `System.Web.Mvc.ActionMethodSelectorAttribute` in `../../../resources/stubs/System.Web.cs:241:18:241:46; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class ActionMethodSelectorAttribute : System.Attribute\n{\n}\n\n// Generated from `System.Web.Mvc.ActionResult` in `../../../resources/stubs/System.Web.cs:233:18:233:29; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class ActionResult\n{\n}\n\n// Generated from `System.Web.Mvc.ControllerContext` in `../../../resources/stubs/System.Web.cs:211:18:211:34; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class ControllerContext\n{\n}\n\n// Generated from `System.Web.Mvc.FilterAttribute` in `../../../resources/stubs/System.Web.cs:237:18:237:32; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class FilterAttribute : System.Attribute\n{\n}\n\n// Generated from `System.Web.Mvc.GlobalFilterCollection` in `../../../resources/stubs/System.Web.cs:281:18:281:39; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class GlobalFilterCollection\n{\n}\n\n// Generated from `System.Web.Mvc.IFilterProvider` in `../../../resources/stubs/System.Web.cs:277:15:277:29; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\ninternal interface IFilterProvider\n{\n}\n\n// Generated from `System.Web.Mvc.IViewDataContainer` in `../../../resources/stubs/System.Web.cs:219:22:219:39; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic interface IViewDataContainer\n{\n}\n\n// Generated from `System.Web.Mvc.ViewContext` in `../../../resources/stubs/System.Web.cs:215:18:215:28; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class ViewContext : System.Web.Mvc.ControllerContext\n{\n}\n\n// Generated from `System.Web.Mvc.ViewResult` in `../../../resources/stubs/System.Web.cs:273:18:273:27; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class ViewResult : System.Web.Mvc.ViewResultBase\n{\n}\n\n// Generated from `System.Web.Mvc.ViewResultBase` in `../../../resources/stubs/System.Web.cs:269:18:269:31; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class ViewResultBase : System.Web.Mvc.ActionResult\n{\n}\n\n}\nnamespace Routing\n{\n// Generated from `System.Web.Routing.RequestContext` in `../../../resources/stubs/System.Web.cs:300:18:300:31; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class RequestContext\n{\n}\n\n}\nnamespace Script\n{\nnamespace Serialization\n{\n// Generated from `System.Web.Script.Serialization.JavaScriptTypeResolver` in `../../../resources/stubs/System.Web.cs:365:27:365:48; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic abstract class JavaScriptTypeResolver\n{\n}\n\n}\n}\nnamespace Security\n{\n// Generated from `System.Web.Security.MembershipUser` in `../../../resources/stubs/System.Web.cs:323:18:323:31; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class MembershipUser\n{\n}\n\n}\nnamespace SessionState\n{\n// Generated from `System.Web.SessionState.HttpSessionState` in `../../../resources/stubs/System.Web.cs:201:18:201:33; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class HttpSessionState\n{\n}\n\n}\nnamespace UI\n{\n// Generated from `System.Web.UI.Control` in `../../../resources/stubs/System.Web.cs:76:18:76:24; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class Control\n{\n}\n\nnamespace WebControls\n{\n// Generated from `System.Web.UI.WebControls.WebControl` in `../../../resources/stubs/System.Web.cs:104:18:104:27; System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`\npublic class WebControl : System.Web.UI.Control\n{\n}\n\n}\n}\n}\n}\n\n\n | +| // This file contains auto-generated code.\n// Generated from `System.Collections, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`.\n\nnamespace System\n{\nnamespace Collections\n{\nnamespace Generic\n{\npublic class Stack : System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.ICollection, System.Collections.IEnumerable\n{\n void System.Collections.ICollection.CopyTo(System.Array array, int arrayIndex) => throw null;\n public int Count { get => throw null; }\n System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() => throw null;\n System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null;\n bool System.Collections.ICollection.IsSynchronized { get => throw null; }\n public T Peek() => throw null;\n object System.Collections.ICollection.SyncRoot { get => throw null; }\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n// Generated from `System.Collections.NonGeneric, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`.\n\nnamespace System\n{\nnamespace Collections\n{\npublic class SortedList : System.Collections.ICollection, System.Collections.IDictionary, System.Collections.IEnumerable, System.ICloneable\n{\n public virtual void Add(object key, object value) => throw null;\n public virtual void Clear() => throw null;\n public virtual object Clone() => throw null;\n public virtual bool Contains(object key) => throw null;\n public virtual void CopyTo(System.Array array, int arrayIndex) => throw null;\n public virtual int Count { get => throw null; }\n public virtual object GetByIndex(int index) => throw null;\n public virtual System.Collections.IDictionaryEnumerator GetEnumerator() => throw null;\n System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null;\n public virtual bool IsFixedSize { get => throw null; }\n public virtual bool IsReadOnly { get => throw null; }\n public virtual bool IsSynchronized { get => throw null; }\n public virtual object this[object key] { get => throw null; set => throw null; }\n public virtual System.Collections.ICollection Keys { get => throw null; }\n public virtual void Remove(object key) => throw null;\n public virtual object SyncRoot { get => throw null; }\n public virtual System.Collections.ICollection Values { get => throw null; }\n}\n\n}\n}\n\n\n// This file contains auto-generated code.\n// Generated from `System.Collections.Specialized, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`.\n\nnamespace System\n{\nnamespace Collections\n{\nnamespace Specialized\n{\npublic abstract class NameObjectCollectionBase : System.Collections.ICollection, System.Collections.IEnumerable, System.Runtime.Serialization.IDeserializationCallback, System.Runtime.Serialization.ISerializable\n{\n void System.Collections.ICollection.CopyTo(System.Array array, int index) => throw null;\n public virtual int Count { get => throw null; }\n public virtual System.Collections.IEnumerator GetEnumerator() => throw null;\n public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) => throw null;\n bool System.Collections.ICollection.IsSynchronized { get => throw null; }\n public virtual void OnDeserialization(object sender) => throw null;\n object System.Collections.ICollection.SyncRoot { get => throw null; }\n}\n\npublic class NameValueCollection : System.Collections.Specialized.NameObjectCollectionBase\n{\n public string this[string name] { get => throw null; set => throw null; }\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n// Generated from `System.ComponentModel.TypeConverter, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`.\n\nnamespace System\n{\nnamespace ComponentModel\n{\npublic class ComponentConverter : System.ComponentModel.ReferenceConverter\n{\n}\n\npublic class DefaultEventAttribute : System.Attribute\n{\n public DefaultEventAttribute(string name) => throw null;\n public override bool Equals(object obj) => throw null;\n public override int GetHashCode() => throw null;\n}\n\npublic class DefaultPropertyAttribute : System.Attribute\n{\n public DefaultPropertyAttribute(string name) => throw null;\n public override bool Equals(object obj) => throw null;\n public override int GetHashCode() => throw null;\n}\n\npublic class ReferenceConverter : System.ComponentModel.TypeConverter\n{\n}\n\npublic class TypeConverter\n{\n}\n\n}\nnamespace Timers\n{\npublic class TimersDescriptionAttribute\n{\n public TimersDescriptionAttribute(string description) => throw null;\n internal TimersDescriptionAttribute(string description, string unused) => throw null;\n}\n\n}\n}\n\n\n// This file contains auto-generated code.\n// Generated from `System.Linq, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`.\n\nnamespace System\n{\nnamespace Linq\n{\npublic static class Enumerable\n{\n public static System.Collections.Generic.IEnumerable Select(this System.Collections.Generic.IEnumerable source, System.Func selector) => throw null;\n}\n\n}\n}\n\n\n// This file contains auto-generated code.\n// Generated from `System.Linq.Expressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`.\n\nnamespace System\n{\nnamespace Linq\n{\npublic interface IQueryable : System.Collections.IEnumerable\n{\n}\n\n}\nnamespace Runtime\n{\nnamespace CompilerServices\n{\npublic class CallSite\n{\n internal CallSite(System.Runtime.CompilerServices.CallSiteBinder binder) => throw null;\n}\n\npublic class CallSite : System.Runtime.CompilerServices.CallSite where T: class\n{\n private CallSite() : base(default(System.Runtime.CompilerServices.CallSiteBinder)) => throw null;\n private CallSite(System.Runtime.CompilerServices.CallSiteBinder binder) : base(default(System.Runtime.CompilerServices.CallSiteBinder)) => throw null;\n}\n\npublic abstract class CallSiteBinder\n{\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n// Generated from `System.Linq.Parallel, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`.\n\nnamespace System\n{\nnamespace Linq\n{\npublic static class ParallelEnumerable\n{\n public static System.Linq.ParallelQuery AsParallel(this System.Collections.IEnumerable source) => throw null;\n}\n\npublic class ParallelQuery : System.Collections.IEnumerable\n{\n System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null;\n internal ParallelQuery(System.Linq.Parallel.QuerySettings specifiedSettings) => throw null;\n}\n\nnamespace Parallel\n{\ninternal struct QuerySettings\n{\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n// Generated from `System.Linq.Queryable, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`.\n\nnamespace System\n{\nnamespace Linq\n{\npublic static class Queryable\n{\n public static System.Linq.IQueryable AsQueryable(this System.Collections.IEnumerable source) => throw null;\n}\n\n}\n}\n\n\n// This file contains auto-generated code.\n// Generated from `System.ObjectModel, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`.\n\nnamespace System\n{\nnamespace ComponentModel\n{\npublic class TypeConverterAttribute : System.Attribute\n{\n public override bool Equals(object obj) => throw null;\n public override int GetHashCode() => throw null;\n public TypeConverterAttribute() => throw null;\n public TypeConverterAttribute(System.Type type) => throw null;\n public TypeConverterAttribute(string typeName) => throw null;\n}\n\npublic class TypeDescriptionProviderAttribute : System.Attribute\n{\n public TypeDescriptionProviderAttribute(System.Type type) => throw null;\n public TypeDescriptionProviderAttribute(string typeName) => throw null;\n}\n\n}\nnamespace Windows\n{\nnamespace Markup\n{\npublic class ValueSerializerAttribute : System.Attribute\n{\n public ValueSerializerAttribute(System.Type valueSerializerType) => throw null;\n public ValueSerializerAttribute(string valueSerializerTypeName) => throw null;\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n// Generated from `System.Private.Uri, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`.\n\nnamespace System\n{\npublic class Uri : System.Runtime.Serialization.ISerializable\n{\n public override bool Equals(object comparand) => throw null;\n public override int GetHashCode() => throw null;\n void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) => throw null;\n public override string ToString() => throw null;\n}\n\n}\n\n\n// This file contains auto-generated code.\n// Generated from `System.Runtime.Serialization.Primitives, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`.\n\nnamespace System\n{\nnamespace Runtime\n{\nnamespace Serialization\n{\npublic class DataContractAttribute : System.Attribute\n{\n public DataContractAttribute() => throw null;\n}\n\npublic class DataMemberAttribute : System.Attribute\n{\n public DataMemberAttribute() => throw null;\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n// Generated from `System.Text.RegularExpressions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`.\n\nnamespace System\n{\nnamespace Text\n{\nnamespace RegularExpressions\n{\npublic class Capture\n{\n internal Capture(string text, int index, int length) => throw null;\n public override string ToString() => throw null;\n}\n\npublic class GeneratedRegexAttribute : System.Attribute\n{\n public GeneratedRegexAttribute(string pattern) => throw null;\n public GeneratedRegexAttribute(string pattern, System.Text.RegularExpressions.RegexOptions options) => throw null;\n public GeneratedRegexAttribute(string pattern, System.Text.RegularExpressions.RegexOptions options, int matchTimeoutMilliseconds) => throw null;\n public GeneratedRegexAttribute(string pattern, System.Text.RegularExpressions.RegexOptions options, int matchTimeoutMilliseconds, string cultureName) => throw null;\n public GeneratedRegexAttribute(string pattern, System.Text.RegularExpressions.RegexOptions options, string cultureName) => throw null;\n}\n\npublic class Group : System.Text.RegularExpressions.Capture\n{\n internal Group(string text, int[] caps, int capcount, string name) : base(default(string), default(int), default(int)) => throw null;\n}\n\npublic class Match : System.Text.RegularExpressions.Group\n{\n internal Match(System.Text.RegularExpressions.Regex regex, int capcount, string text, int textLength) : base(default(string), default(int[]), default(int), default(string)) => throw null;\n}\n\npublic class Regex : System.Runtime.Serialization.ISerializable\n{\n void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo si, System.Runtime.Serialization.StreamingContext context) => throw null;\n public System.Text.RegularExpressions.Match Match(string input) => throw null;\n public static System.Text.RegularExpressions.Match Match(string input, string pattern) => throw null;\n public static System.Text.RegularExpressions.Match Match(string input, string pattern, System.Text.RegularExpressions.RegexOptions options, System.TimeSpan matchTimeout) => throw null;\n public Regex(string pattern) => throw null;\n public Regex(string pattern, System.Text.RegularExpressions.RegexOptions options, System.TimeSpan matchTimeout) => throw null;\n public string Replace(string input, string replacement) => throw null;\n public override string ToString() => throw null;\n}\n\n[System.Flags]\npublic enum RegexOptions : int\n{\n IgnoreCase = 1,\n}\n\n}\n}\n}\n\n\n// This file contains auto-generated code.\n// Generated from `System.Web, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null`.\n\nnamespace System\n{\nnamespace Web\n{\npublic class HtmlString : System.Web.IHtmlString\n{\n}\n\npublic class HttpContextBase\n{\n public virtual System.Web.HttpRequestBase Request { get => throw null; }\n}\n\npublic class HttpCookie\n{\n}\n\npublic abstract class HttpCookieCollection : System.Collections.Specialized.NameObjectCollectionBase\n{\n}\n\npublic class HttpRequest\n{\n}\n\npublic class HttpRequestBase\n{\n public virtual System.Collections.Specialized.NameValueCollection QueryString { get => throw null; }\n}\n\npublic class HttpResponse\n{\n}\n\npublic class HttpResponseBase\n{\n}\n\npublic class HttpServerUtility\n{\n}\n\npublic interface IHtmlString\n{\n}\n\npublic interface IHttpHandler\n{\n}\n\npublic interface IServiceProvider\n{\n}\n\npublic class UnvalidatedRequestValues\n{\n}\n\npublic class UnvalidatedRequestValuesBase\n{\n}\n\nnamespace Mvc\n{\npublic class ActionMethodSelectorAttribute : System.Attribute\n{\n}\n\npublic class ActionResult\n{\n}\n\npublic class ControllerContext\n{\n}\n\npublic class FilterAttribute : System.Attribute\n{\n}\n\npublic class GlobalFilterCollection\n{\n}\n\ninternal interface IFilterProvider\n{\n}\n\npublic interface IViewDataContainer\n{\n}\n\npublic class ViewContext : System.Web.Mvc.ControllerContext\n{\n}\n\npublic class ViewResult : System.Web.Mvc.ViewResultBase\n{\n}\n\npublic class ViewResultBase : System.Web.Mvc.ActionResult\n{\n}\n\n}\nnamespace Routing\n{\npublic class RequestContext\n{\n}\n\n}\nnamespace Script\n{\nnamespace Serialization\n{\npublic abstract class JavaScriptTypeResolver\n{\n}\n\n}\n}\nnamespace Security\n{\npublic class MembershipUser\n{\n}\n\n}\nnamespace SessionState\n{\npublic class HttpSessionState\n{\n}\n\n}\nnamespace UI\n{\npublic class Control\n{\n}\n\nnamespace WebControls\n{\npublic class WebControl : System.Web.UI.Control\n{\n}\n\n}\n}\n}\n}\n\n\n | diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll index 0afcba55582..d574f95d181 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl.qll @@ -959,6 +959,17 @@ module Impl { not inBarrier(p) } + /** + * Gets an additional term that is added to `branch` and `join` when deciding whether + * the amount of forward or backward branching is within the limit specified by the + * configuration. + */ + pragma[nomagic] + private int getLanguageSpecificFlowIntoCallNodeCand1(ArgNodeEx arg, ParamNodeEx p) { + flowIntoCallNodeCand1(_, arg, p) and + result = getAdditionalFlowIntoCallNodeTerm(arg.projectToNode(), p.projectToNode()) + } + /** * Gets the amount of forward branching on the origin of a cross-call path * edge in the graph of paths between sources and sinks that ignores call @@ -968,6 +979,7 @@ module Impl { private int branch(NodeEx n1) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + + sum(ParamNodeEx p1 | | getLanguageSpecificFlowIntoCallNodeCand1(n1, p1)) } /** @@ -979,6 +991,7 @@ module Impl { private int join(NodeEx n2) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + + sum(ArgNodeEx arg2 | | getLanguageSpecificFlowIntoCallNodeCand1(arg2, n2)) } /** diff --git a/go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll b/go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll index b65fb3d7d5d..f6cb63c1c75 100644 --- a/go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll +++ b/go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll @@ -390,3 +390,12 @@ class ContentApprox = Unit; /** Gets an approximated value for content `c`. */ pragma[inline] ContentApprox getContentApprox(Content c) { any() } + +/** + * Gets an additional term that is added to the `join` and `branch` computations to reflect + * an additional forward or backwards branching factor that is not taken into account + * when calculating the (virtual) dispatch cost. + * + * Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter. + */ +int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() } diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 0afcba55582..d574f95d181 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -959,6 +959,17 @@ module Impl { not inBarrier(p) } + /** + * Gets an additional term that is added to `branch` and `join` when deciding whether + * the amount of forward or backward branching is within the limit specified by the + * configuration. + */ + pragma[nomagic] + private int getLanguageSpecificFlowIntoCallNodeCand1(ArgNodeEx arg, ParamNodeEx p) { + flowIntoCallNodeCand1(_, arg, p) and + result = getAdditionalFlowIntoCallNodeTerm(arg.projectToNode(), p.projectToNode()) + } + /** * Gets the amount of forward branching on the origin of a cross-call path * edge in the graph of paths between sources and sinks that ignores call @@ -968,6 +979,7 @@ module Impl { private int branch(NodeEx n1) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + + sum(ParamNodeEx p1 | | getLanguageSpecificFlowIntoCallNodeCand1(n1, p1)) } /** @@ -979,6 +991,7 @@ module Impl { private int join(NodeEx n2) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + + sum(ArgNodeEx arg2 | | getLanguageSpecificFlowIntoCallNodeCand1(arg2, n2)) } /** diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index ef89d0f51a2..0194b2609f0 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -478,3 +478,12 @@ predicate containerContent(Content c) { c instanceof MapKeyContent or c instanceof MapValueContent } + +/** + * Gets an additional term that is added to the `join` and `branch` computations to reflect + * an additional forward or backwards branching factor that is not taken into account + * when calculating the (virtual) dispatch cost. + * + * Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter. + */ +int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() } diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll index 0afcba55582..d574f95d181 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll @@ -959,6 +959,17 @@ module Impl { not inBarrier(p) } + /** + * Gets an additional term that is added to `branch` and `join` when deciding whether + * the amount of forward or backward branching is within the limit specified by the + * configuration. + */ + pragma[nomagic] + private int getLanguageSpecificFlowIntoCallNodeCand1(ArgNodeEx arg, ParamNodeEx p) { + flowIntoCallNodeCand1(_, arg, p) and + result = getAdditionalFlowIntoCallNodeTerm(arg.projectToNode(), p.projectToNode()) + } + /** * Gets the amount of forward branching on the origin of a cross-call path * edge in the graph of paths between sources and sinks that ignores call @@ -968,6 +979,7 @@ module Impl { private int branch(NodeEx n1) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + + sum(ParamNodeEx p1 | | getLanguageSpecificFlowIntoCallNodeCand1(n1, p1)) } /** @@ -979,6 +991,7 @@ module Impl { private int join(NodeEx n2) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + + sum(ArgNodeEx arg2 | | getLanguageSpecificFlowIntoCallNodeCand1(arg2, n2)) } /** diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll index c46cd74e3d4..12ced9d4fdd 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll @@ -1008,3 +1008,12 @@ class ContentApprox = Unit; /** Gets an approximated value for content `c`. */ pragma[inline] ContentApprox getContentApprox(Content c) { any() } + +/** + * Gets an additional term that is added to the `join` and `branch` computations to reflect + * an additional forward or backwards branching factor that is not taken into account + * when calculating the (virtual) dispatch cost. + * + * Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter. + */ +int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() } diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll index 0afcba55582..d574f95d181 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowImpl.qll @@ -959,6 +959,17 @@ module Impl { not inBarrier(p) } + /** + * Gets an additional term that is added to `branch` and `join` when deciding whether + * the amount of forward or backward branching is within the limit specified by the + * configuration. + */ + pragma[nomagic] + private int getLanguageSpecificFlowIntoCallNodeCand1(ArgNodeEx arg, ParamNodeEx p) { + flowIntoCallNodeCand1(_, arg, p) and + result = getAdditionalFlowIntoCallNodeTerm(arg.projectToNode(), p.projectToNode()) + } + /** * Gets the amount of forward branching on the origin of a cross-call path * edge in the graph of paths between sources and sinks that ignores call @@ -968,6 +979,7 @@ module Impl { private int branch(NodeEx n1) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + + sum(ParamNodeEx p1 | | getLanguageSpecificFlowIntoCallNodeCand1(n1, p1)) } /** @@ -979,6 +991,7 @@ module Impl { private int join(NodeEx n2) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + + sum(ArgNodeEx arg2 | | getLanguageSpecificFlowIntoCallNodeCand1(arg2, n2)) } /** diff --git a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll index b13d551c837..8c73a7bce4b 100644 --- a/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll +++ b/ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll @@ -1498,3 +1498,12 @@ class AdditionalJumpStep extends Unit { */ abstract predicate step(Node pred, Node succ); } + +/** + * Gets an additional term that is added to the `join` and `branch` computations to reflect + * an additional forward or backwards branching factor that is not taken into account + * when calculating the (virtual) dispatch cost. + * + * Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter. + */ +int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() } diff --git a/ruby/ql/lib/codeql/ruby/security/StoredXSSQuery.qll b/ruby/ql/lib/codeql/ruby/security/StoredXSSQuery.qll index 1a731ae463a..abec1880e65 100644 --- a/ruby/ql/lib/codeql/ruby/security/StoredXSSQuery.qll +++ b/ruby/ql/lib/codeql/ruby/security/StoredXSSQuery.qll @@ -16,9 +16,11 @@ module StoredXss { import XSS::StoredXss /** + * DEPRECATED. + * * A taint-tracking configuration for reasoning about Stored XSS. */ - class Configuration extends TaintTracking::Configuration { + deprecated class Configuration extends TaintTracking::Configuration { Configuration() { this = "StoredXss" } override predicate isSource(DataFlow::Node source) { source instanceof Source } @@ -38,6 +40,23 @@ module StoredXss { isAdditionalXssTaintStep(node1, node2) } } + + /** + * A taint-tracking configuration for reasoning about Stored XSS. + */ + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof Source } + + predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + isAdditionalXssTaintStep(node1, node2) + } + } + + import TaintTracking::Make } /** DEPRECATED: Alias for StoredXss */ diff --git a/ruby/ql/lib/codeql/ruby/security/XSS.qll b/ruby/ql/lib/codeql/ruby/security/XSS.qll index 0589fdeb9af..9371a62bc26 100644 --- a/ruby/ql/lib/codeql/ruby/security/XSS.qll +++ b/ruby/ql/lib/codeql/ruby/security/XSS.qll @@ -4,7 +4,6 @@ private import codeql.ruby.AST private import codeql.ruby.DataFlow -private import codeql.ruby.DataFlow2 private import codeql.ruby.CFG private import codeql.ruby.Concepts private import codeql.ruby.Frameworks @@ -291,20 +290,18 @@ private module OrmTracking { /** * A data flow configuration to track flow from finder calls to field accesses. */ - class Configuration extends DataFlow2::Configuration { - Configuration() { this = "OrmTracking" } - - override predicate isSource(DataFlow2::Node source) { source instanceof OrmInstantiation } + private module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source instanceof OrmInstantiation } // Select any call receiver and narrow down later - override predicate isSink(DataFlow2::Node sink) { - sink = any(DataFlow2::CallNode c).getReceiver() - } + predicate isSink(DataFlow::Node sink) { sink = any(DataFlow::CallNode c).getReceiver() } - override predicate isAdditionalFlowStep(DataFlow2::Node node1, DataFlow2::Node node2) { + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { Shared::isAdditionalXssFlowStep(node1, node2) } } + + import DataFlow::Make } /** Provides default sources, sinks and sanitizers for detecting stored cross-site scripting (XSS) vulnerabilities. */ @@ -333,10 +330,10 @@ module StoredXss { /** DEPRECATED: Alias for isAdditionalXssTaintStep */ deprecated predicate isAdditionalXSSTaintStep = isAdditionalXssTaintStep/2; - private class OrmFieldAsSource extends Source instanceof DataFlow2::CallNode { + private class OrmFieldAsSource extends Source instanceof DataFlow::CallNode { OrmFieldAsSource() { - exists(OrmTracking::Configuration subConfig, DataFlow2::CallNode subSrc | - subConfig.hasFlow(subSrc, this.getReceiver()) and + exists(DataFlow::CallNode subSrc | + OrmTracking::hasFlow(subSrc, this.getReceiver()) and subSrc.(OrmInstantiation).methodCallMayAccessField(this.getMethodName()) ) } diff --git a/ruby/ql/src/queries/security/cwe-079/StoredXSS.ql b/ruby/ql/src/queries/security/cwe-079/StoredXSS.ql index 8630f2eb062..1dc855230a1 100644 --- a/ruby/ql/src/queries/security/cwe-079/StoredXSS.ql +++ b/ruby/ql/src/queries/security/cwe-079/StoredXSS.ql @@ -14,9 +14,9 @@ import codeql.ruby.AST import codeql.ruby.security.StoredXSSQuery -import DataFlow::PathGraph +import StoredXss::PathGraph -from StoredXss::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink -where config.hasFlowPath(source, sink) +from StoredXss::PathNode source, StoredXss::PathNode sink +where StoredXss::hasFlowPath(source, sink) select sink.getNode(), source, sink, "Stored cross-site scripting vulnerability due to $@.", source.getNode(), "stored value" diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll index 0afcba55582..d574f95d181 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowImpl.qll @@ -959,6 +959,17 @@ module Impl { not inBarrier(p) } + /** + * Gets an additional term that is added to `branch` and `join` when deciding whether + * the amount of forward or backward branching is within the limit specified by the + * configuration. + */ + pragma[nomagic] + private int getLanguageSpecificFlowIntoCallNodeCand1(ArgNodeEx arg, ParamNodeEx p) { + flowIntoCallNodeCand1(_, arg, p) and + result = getAdditionalFlowIntoCallNodeTerm(arg.projectToNode(), p.projectToNode()) + } + /** * Gets the amount of forward branching on the origin of a cross-call path * edge in the graph of paths between sources and sinks that ignores call @@ -968,6 +979,7 @@ module Impl { private int branch(NodeEx n1) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n1, _, n) or flowIntoCallNodeCand1(_, n1, n)) + + sum(ParamNodeEx p1 | | getLanguageSpecificFlowIntoCallNodeCand1(n1, p1)) } /** @@ -979,6 +991,7 @@ module Impl { private int join(NodeEx n2) { result = strictcount(NodeEx n | flowOutOfCallNodeCand1(_, n, _, n2) or flowIntoCallNodeCand1(_, n, n2)) + + sum(ArgNodeEx arg2 | | getLanguageSpecificFlowIntoCallNodeCand1(arg2, n2)) } /** diff --git a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll index 059fd4ae26f..d41c1fb00f9 100644 --- a/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll +++ b/swift/ql/lib/codeql/swift/dataflow/internal/DataFlowPrivate.qll @@ -7,6 +7,7 @@ private import codeql.swift.dataflow.Ssa private import codeql.swift.controlflow.BasicBlocks private import codeql.swift.dataflow.FlowSummary as FlowSummary private import codeql.swift.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl +private import codeql.swift.frameworks.StandardLibrary.PointerTypes /** Gets the callable in which this node occurs. */ DataFlowCallable nodeGetEnclosingCallable(NodeImpl n) { result = n.getEnclosingCallable() } @@ -211,6 +212,8 @@ private predicate modifiable(Argument arg) { arg.getExpr() instanceof InOutExpr or arg.getExpr().getType() instanceof NominalType + or + arg.getExpr().getType() instanceof PointerType } predicate modifiableParam(ParamDecl param) { @@ -696,3 +699,12 @@ class ContentApprox = Unit; /** Gets an approximated value for content `c`. */ pragma[inline] ContentApprox getContentApprox(Content c) { any() } + +/** + * Gets an additional term that is added to the `join` and `branch` computations to reflect + * an additional forward or backwards branching factor that is not taken into account + * when calculating the (virtual) dispatch cost. + * + * Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter. + */ +int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() } diff --git a/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/PointerTypes.qll b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/PointerTypes.qll new file mode 100644 index 00000000000..c799e589ffb --- /dev/null +++ b/swift/ql/lib/codeql/swift/frameworks/StandardLibrary/PointerTypes.qll @@ -0,0 +1,75 @@ +/** + * Provides models for Swift pointer types including `UnsafePointer`, + * `UnsafeBufferPointer` and similar types. + */ + +import swift + +/** + * A type that is used as a pointer in Swift, such as `UnsafePointer`, + * `UnsafeBufferPointer` and similar types. + */ +class PointerType extends Type { + PointerType() { + this instanceof UnsafeTypedPointerType or + this instanceof UnsafeRawPointerType or + this instanceof OpaquePointerType or + this instanceof AutoreleasingUnsafeMutablePointerType or + this instanceof UnmanagedType or + this instanceof CVaListPointerType or + this instanceof ManagedBufferPointerType + } +} + +/** + * A Swift unsafe typed pointer type such as `UnsafePointer`, + * `UnsafeMutablePointer` or `UnsafeBufferPointer`. + */ +class UnsafeTypedPointerType extends BoundGenericType { + UnsafeTypedPointerType() { this.getName().regexpMatch("Unsafe(Mutable|)(Buffer|)Pointer<.*") } +} + +/** + * A Swift unsafe raw pointer type such as `UnsafeRawPointer`, + * `UnsafeMutableRawPointer` or `UnsafeRawBufferPointer`. + */ +class UnsafeRawPointerType extends NominalType { + UnsafeRawPointerType() { this.getName().regexpMatch("Unsafe(Mutable|)Raw(Buffer|)Pointer") } +} + +/** + * A Swift `OpaquePointer`. + */ +class OpaquePointerType extends NominalType { + OpaquePointerType() { this.getName() = "OpaquePointer" } +} + +/** + * A Swift `AutoreleasingUnsafeMutablePointer`. + */ +class AutoreleasingUnsafeMutablePointerType extends BoundGenericType { + AutoreleasingUnsafeMutablePointerType() { + this.getName().matches("AutoreleasingUnsafeMutablePointer<%") + } +} + +/** + * A Swift `Unmanaged` object reference. + */ +class UnmanagedType extends BoundGenericType { + UnmanagedType() { this.getName().matches("Unmanaged<%") } +} + +/** + * A Swift `CVaListPointer`. + */ +class CVaListPointerType extends NominalType { + CVaListPointerType() { this.getName() = "CVaListPointer" } +} + +/** + * A Swift `ManagedBufferPointer`. + */ +class ManagedBufferPointerType extends BoundGenericType { + ManagedBufferPointerType() { this.getName().matches("ManagedBufferPointer<%") } +} diff --git a/swift/ql/test/library-tests/dataflow/taint/libraries/data.swift b/swift/ql/test/library-tests/dataflow/taint/libraries/data.swift index df7cf8ad205..60732f704d2 100644 --- a/swift/ql/test/library-tests/dataflow/taint/libraries/data.swift +++ b/swift/ql/test/library-tests/dataflow/taint/libraries/data.swift @@ -158,13 +158,13 @@ func taintThroughData() { let dataTainted19 = source() as! Data let pointerTainted19 = UnsafeMutablePointer.allocate(capacity: 0) dataTainted19.copyBytes(to: pointerTainted19, count: 0) - sink(arg: pointerTainted19) // $ MISSING: tainted=158 + sink(arg: pointerTainted19) // $ tainted=158 // ";Data;true;copyBytes(to:from:);;;Argument[-1];Argument[0];taint", let dataTainted20 = source() as! Data let pointerTainted20 = UnsafeMutablePointer.allocate(capacity: 0) dataTainted20.copyBytes(to: pointerTainted20, from: 0..<1) - sink(arg: pointerTainted20) // $ MISSING: tainted=164 + sink(arg: pointerTainted20) // $ tainted=164 // ";Data;true;flatMap(_:);;;Argument[-1];ReturnValue;taint", let dataTainted21 = source() as! Data diff --git a/swift/ql/test/library-tests/dataflow/taint/libraries/nsstring.swift b/swift/ql/test/library-tests/dataflow/taint/libraries/nsstring.swift index 626e010efda..8f961b35d17 100644 --- a/swift/ql/test/library-tests/dataflow/taint/libraries/nsstring.swift +++ b/swift/ql/test/library-tests/dataflow/taint/libraries/nsstring.swift @@ -318,14 +318,14 @@ func taintThroughInterpolatedStrings() { harmless.getCharacters(ptr1, range: myRange) sink(arg: ptr1) sourceNSString().getCharacters(ptr1, range: myRange) - sink(arg: ptr1) // $ MISSING: tainted= + sink(arg: ptr1) // $ tainted=320 var ptr2 = (nil as UnsafeMutablePointer?)! sink(arg: ptr2) harmless.getCharacters(ptr2) sink(arg: ptr2) sourceNSString().getCharacters(ptr2) - sink(arg: ptr2) // $ MISSING: tainted= + sink(arg: ptr2) // $ tainted=327 var ptr3 = (nil as UnsafeMutableRawPointer?)! sink(arg: ptr3) @@ -339,14 +339,14 @@ func taintThroughInterpolatedStrings() { harmless.getCString(ptr4, maxLength: 128, encoding: 0) sink(arg: ptr4) sourceNSString().getCString(ptr4, maxLength: 128, encoding: 0) - sink(arg: ptr4) // $ MISSING: tainted= + sink(arg: ptr4) // $ tainted=341 var ptr5 = (nil as UnsafeMutablePointer?)! sink(arg: ptr5) harmless.getCString(ptr5) sink(arg: ptr5) sourceNSString().getCString(ptr5) - sink(arg: ptr5) // $ MISSING: tainted= + sink(arg: ptr5) // $ tainted=348 sink(arg: harmless.enumerateLines({ line, stop in @@ -363,10 +363,10 @@ func taintThroughInterpolatedStrings() { var outLongest = (nil as AutoreleasingUnsafeMutablePointer?)! var outArray = (nil as AutoreleasingUnsafeMutablePointer?)! if (str10.completePath(into: outLongest, caseSensitive: false, matchesInto: outArray, filterTypes: nil) > 0) { - sink(arg: outLongest) // $ MISSING: tainted= + sink(arg: outLongest) // $ tainted=362 sink(arg: outLongest.pointee) // $ MISSING: tainted= sink(arg: outLongest.pointee!) // $ MISSING: tainted= - sink(arg: outArray) // $ MISSING: tainted= + sink(arg: outArray) // $ tainted=362 sink(arg: outArray.pointee) // $ MISSING: tainted= sink(arg: outArray.pointee!) // $ MISSING: tainted= } @@ -374,7 +374,7 @@ func taintThroughInterpolatedStrings() { var str11 = sourceNSString() var outBuffer = (nil as UnsafeMutablePointer?)! if (str11.getFileSystemRepresentation(outBuffer, maxLength: 256)) { - sink(arg: outBuffer) // $ MISSING: tainted= + sink(arg: outBuffer) // $ tainted=374 sink(arg: outBuffer.pointee) // $ MISSING: tainted= } diff --git a/swift/ql/test/library-tests/dataflow/taint/libraries/unsafepointer.swift b/swift/ql/test/library-tests/dataflow/taint/libraries/unsafepointer.swift new file mode 100644 index 00000000000..e42bdb30404 --- /dev/null +++ b/swift/ql/test/library-tests/dataflow/taint/libraries/unsafepointer.swift @@ -0,0 +1,101 @@ + +// --- stubs --- + +func sourceString() -> String { return "" } +func sourceUInt8() -> UInt8 { return 0 } +func sink(arg: Any) {} + +// --- tests --- + +func clearPointer1(ptr: UnsafeMutablePointer) { + ptr.pointee = "abc" + + sink(arg: ptr.pointee) + sink(arg: ptr) +} + +func taintPointer(ptr: UnsafeMutablePointer) { + sink(arg: ptr.pointee) + sink(arg: ptr) + + ptr.pointee = sourceString() + + sink(arg: ptr.pointee) // $ tainted=21 + sink(arg: ptr) +} + +func clearPointer2(ptr: UnsafeMutablePointer) { + sink(arg: ptr.pointee) // $ tainted=21 + sink(arg: ptr) + + ptr.pointee = "abc" + + sink(arg: ptr.pointee) + sink(arg: ptr) +} + +func testMutatingPointerInCall(ptr: UnsafeMutablePointer) { + clearPointer1(ptr: ptr) + + sink(arg: ptr.pointee) + sink(arg: ptr) + + taintPointer(ptr: ptr) // mutates `ptr` pointee with a tainted value + + sink(arg: ptr.pointee) // $ tainted=21 + sink(arg: ptr) + + clearPointer2(ptr: ptr) + + sink(arg: ptr.pointee) // $ SPURIOUS: tainted=21 + sink(arg: ptr) +} + +// --- + +func taintBuffer(buffer: UnsafeMutableBufferPointer) { + sink(arg: buffer[0]) + sink(arg: buffer) + + buffer[0] = sourceUInt8() + + sink(arg: buffer[0]) // $ MISSING: tainted=60 + sink(arg: buffer) +} + +func testMutatingBufferInCall(ptr: UnsafeMutablePointer) { + let buffer = UnsafeMutableBufferPointer(start: ptr, count: 1000) + + sink(arg: buffer[0]) + sink(arg: buffer) + + taintBuffer(buffer: buffer) // mutates `buffer` contents with a tainted value + + sink(arg: buffer[0]) // $ MISSING: tainted=60 + sink(arg: buffer) + +} + +// --- + +typealias MyPointer = UnsafeMutablePointer + +func taintMyPointer(ptr: MyPointer) { + sink(arg: ptr.pointee) + sink(arg: ptr) + + ptr.pointee = sourceString() + + sink(arg: ptr.pointee) // $ tainted=87 + sink(arg: ptr) +} + +func testMutatingMyPointerInCall(ptr: MyPointer) { + sink(arg: ptr.pointee) + sink(arg: ptr) + + taintMyPointer(ptr: ptr) // mutates `ptr` pointee with a tainted value + + sink(arg: ptr.pointee) // $ MISSING: tainted=87 + sink(arg: ptr) +} diff --git a/swift/ql/test/library-tests/elements/type/pointertypes/pointers.swift b/swift/ql/test/library-tests/elements/type/pointertypes/pointers.swift new file mode 100644 index 00000000000..81460c1f40d --- /dev/null +++ b/swift/ql/test/library-tests/elements/type/pointertypes/pointers.swift @@ -0,0 +1,29 @@ + +struct AutoreleasingUnsafeMutablePointer { +} + +class MyClass { + init() { + val = 0 + } + + var val: Int +} + +func test() { + var p1: UnsafePointer + var p2: UnsafeMutablePointer + var p3: UnsafeBufferPointer + var p4: UnsafeMutableBufferPointer + var p5: UnsafeRawPointer + var p6: UnsafeMutableRawPointer + var p7: UnsafeRawBufferPointer + var p8: UnsafeMutableRawBufferPointer + + var op: OpaquePointer // C-interop + var aump: AutoreleasingUnsafeMutablePointer // ObjC-interop + var um: Unmanaged // C-interop + var cvlp: CVaListPointer // varargs list pointer + + var mbp: ManagedBufferPointer +} diff --git a/swift/ql/test/library-tests/elements/type/pointertypes/pointertypes.expected b/swift/ql/test/library-tests/elements/type/pointertypes/pointertypes.expected new file mode 100644 index 00000000000..43fd401a491 --- /dev/null +++ b/swift/ql/test/library-tests/elements/type/pointertypes/pointertypes.expected @@ -0,0 +1,14 @@ +| pointers.swift:2:8:2:8 | self | AutoreleasingUnsafeMutablePointer | AutoreleasingUnsafeMutablePointerType, PointerType | +| pointers.swift:14:6:14:6 | p1 | UnsafePointer | PointerType, UnsafeTypedPointerType | +| pointers.swift:15:6:15:6 | p2 | UnsafeMutablePointer | PointerType, UnsafeTypedPointerType | +| pointers.swift:16:6:16:6 | p3 | UnsafeBufferPointer | PointerType, UnsafeTypedPointerType | +| pointers.swift:17:6:17:6 | p4 | UnsafeMutableBufferPointer | PointerType, UnsafeTypedPointerType | +| pointers.swift:18:6:18:6 | p5 | UnsafeRawPointer | PointerType, UnsafeRawPointerType | +| pointers.swift:19:6:19:6 | p6 | UnsafeMutableRawPointer | PointerType, UnsafeRawPointerType | +| pointers.swift:20:6:20:6 | p7 | UnsafeRawBufferPointer | PointerType, UnsafeRawPointerType | +| pointers.swift:21:6:21:6 | p8 | UnsafeMutableRawBufferPointer | PointerType, UnsafeRawPointerType | +| pointers.swift:23:6:23:6 | op | OpaquePointer | OpaquePointerType, PointerType | +| pointers.swift:24:6:24:6 | aump | AutoreleasingUnsafeMutablePointer | AutoreleasingUnsafeMutablePointerType, PointerType | +| pointers.swift:25:6:25:6 | um | Unmanaged | PointerType, UnmanagedType | +| pointers.swift:26:6:26:6 | cvlp | CVaListPointer | CVaListPointerType, PointerType | +| pointers.swift:28:6:28:6 | mbp | ManagedBufferPointer | ManagedBufferPointerType, PointerType | diff --git a/swift/ql/test/library-tests/elements/type/pointertypes/pointertypes.ql b/swift/ql/test/library-tests/elements/type/pointertypes/pointertypes.ql new file mode 100644 index 00000000000..d6316e01d88 --- /dev/null +++ b/swift/ql/test/library-tests/elements/type/pointertypes/pointertypes.ql @@ -0,0 +1,29 @@ +import swift +import codeql.swift.frameworks.StandardLibrary.PointerTypes + +string describe(Type t) { + t instanceof PointerType and result = "PointerType" + or + t instanceof BuiltinRawPointerType and result = "BuiltinRawPointerType" + or + t instanceof UnsafeTypedPointerType and result = "UnsafeTypedPointerType" + or + t instanceof UnsafeRawPointerType and result = "UnsafeRawPointerType" + or + t instanceof OpaquePointerType and result = "OpaquePointerType" + or + t instanceof AutoreleasingUnsafeMutablePointerType and + result = "AutoreleasingUnsafeMutablePointerType" + or + t instanceof UnmanagedType and result = "UnmanagedType" + or + t instanceof CVaListPointerType and result = "CVaListPointerType" + or + t instanceof ManagedBufferPointerType and result = "ManagedBufferPointerType" +} + +from VarDecl v, Type t +where + v.getLocation().getFile().getBaseName() != "" and + t = v.getType() +select v, t.toString(), strictconcat(describe(t), ", ") diff --git a/swift/ql/test/query-tests/Security/CWE-089/SqlInjection.expected b/swift/ql/test/query-tests/Security/CWE-089/SqlInjection.expected index a88b40ccccd..bfe5b10e499 100644 --- a/swift/ql/test/query-tests/Security/CWE-089/SqlInjection.expected +++ b/swift/ql/test/query-tests/Security/CWE-089/SqlInjection.expected @@ -97,12 +97,23 @@ edges | SQLite.swift:62:25:62:79 | call to String.init(contentsOf:) : | SQLite.swift:117:16:117:16 | unsafeQuery1 | | SQLite.swift:62:25:62:79 | call to String.init(contentsOf:) : | SQLite.swift:119:16:119:16 | unsafeQuery1 | | SQLite.swift:62:25:62:79 | call to String.init(contentsOf:) : | SQLite.swift:132:20:132:20 | remoteString | +| sqlite3_c_api.swift:15:2:15:71 | [summary param] this in copyBytes(to:count:) : | file://:0:0:0:0 | [summary] to write: argument 0 in copyBytes(to:count:) : | +| sqlite3_c_api.swift:37:2:37:103 | [summary param] this in data(using:allowLossyConversion:) : | file://:0:0:0:0 | [summary] to write: return (return) in data(using:allowLossyConversion:) : | | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:133:33:133:33 | unsafeQuery1 | | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:134:33:134:33 | unsafeQuery2 | | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:135:33:135:33 | unsafeQuery3 | | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:145:26:145:26 | unsafeQuery3 | | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:175:29:175:29 | unsafeQuery3 | | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:183:29:183:29 | unsafeQuery3 | +| sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:189:13:189:13 | unsafeQuery3 : | +| sqlite3_c_api.swift:189:13:189:13 | unsafeQuery3 : | sqlite3_c_api.swift:37:2:37:103 | [summary param] this in data(using:allowLossyConversion:) : | +| sqlite3_c_api.swift:189:13:189:13 | unsafeQuery3 : | sqlite3_c_api.swift:189:13:189:58 | call to data(using:allowLossyConversion:) : | +| sqlite3_c_api.swift:189:13:189:58 | call to data(using:allowLossyConversion:) : | sqlite3_c_api.swift:190:2:190:2 | data : | +| sqlite3_c_api.swift:190:2:190:2 | data : | sqlite3_c_api.swift:15:2:15:71 | [summary param] this in copyBytes(to:count:) : | +| sqlite3_c_api.swift:190:2:190:2 | data : | sqlite3_c_api.swift:190:21:190:21 | [post] buffer : | +| sqlite3_c_api.swift:190:21:190:21 | [post] buffer : | sqlite3_c_api.swift:194:28:194:28 | buffer | +| sqlite3_c_api.swift:190:21:190:21 | [post] buffer : | sqlite3_c_api.swift:202:31:202:31 | buffer | +| sqlite3_c_api.swift:190:21:190:21 | [post] buffer : | sqlite3_c_api.swift:210:31:210:31 | buffer | nodes | GRDB.swift:104:25:104:79 | call to String.init(contentsOf:) : | semmle.label | call to String.init(contentsOf:) : | | GRDB.swift:106:41:106:41 | remoteString | semmle.label | remoteString | @@ -215,6 +226,10 @@ nodes | SQLite.swift:117:16:117:16 | unsafeQuery1 | semmle.label | unsafeQuery1 | | SQLite.swift:119:16:119:16 | unsafeQuery1 | semmle.label | unsafeQuery1 | | SQLite.swift:132:20:132:20 | remoteString | semmle.label | remoteString | +| file://:0:0:0:0 | [summary] to write: argument 0 in copyBytes(to:count:) : | semmle.label | [summary] to write: argument 0 in copyBytes(to:count:) : | +| file://:0:0:0:0 | [summary] to write: return (return) in data(using:allowLossyConversion:) : | semmle.label | [summary] to write: return (return) in data(using:allowLossyConversion:) : | +| sqlite3_c_api.swift:15:2:15:71 | [summary param] this in copyBytes(to:count:) : | semmle.label | [summary param] this in copyBytes(to:count:) : | +| sqlite3_c_api.swift:37:2:37:103 | [summary param] this in data(using:allowLossyConversion:) : | semmle.label | [summary param] this in data(using:allowLossyConversion:) : | | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | semmle.label | call to String.init(contentsOf:) : | | sqlite3_c_api.swift:133:33:133:33 | unsafeQuery1 | semmle.label | unsafeQuery1 | | sqlite3_c_api.swift:134:33:134:33 | unsafeQuery2 | semmle.label | unsafeQuery2 | @@ -222,7 +237,16 @@ nodes | sqlite3_c_api.swift:145:26:145:26 | unsafeQuery3 | semmle.label | unsafeQuery3 | | sqlite3_c_api.swift:175:29:175:29 | unsafeQuery3 | semmle.label | unsafeQuery3 | | sqlite3_c_api.swift:183:29:183:29 | unsafeQuery3 | semmle.label | unsafeQuery3 | +| sqlite3_c_api.swift:189:13:189:13 | unsafeQuery3 : | semmle.label | unsafeQuery3 : | +| sqlite3_c_api.swift:189:13:189:58 | call to data(using:allowLossyConversion:) : | semmle.label | call to data(using:allowLossyConversion:) : | +| sqlite3_c_api.swift:190:2:190:2 | data : | semmle.label | data : | +| sqlite3_c_api.swift:190:21:190:21 | [post] buffer : | semmle.label | [post] buffer : | +| sqlite3_c_api.swift:194:28:194:28 | buffer | semmle.label | buffer | +| sqlite3_c_api.swift:202:31:202:31 | buffer | semmle.label | buffer | +| sqlite3_c_api.swift:210:31:210:31 | buffer | semmle.label | buffer | subpaths +| sqlite3_c_api.swift:189:13:189:13 | unsafeQuery3 : | sqlite3_c_api.swift:37:2:37:103 | [summary param] this in data(using:allowLossyConversion:) : | file://:0:0:0:0 | [summary] to write: return (return) in data(using:allowLossyConversion:) : | sqlite3_c_api.swift:189:13:189:58 | call to data(using:allowLossyConversion:) : | +| sqlite3_c_api.swift:190:2:190:2 | data : | sqlite3_c_api.swift:15:2:15:71 | [summary param] this in copyBytes(to:count:) : | file://:0:0:0:0 | [summary] to write: argument 0 in copyBytes(to:count:) : | sqlite3_c_api.swift:190:21:190:21 | [post] buffer : | #select | GRDB.swift:106:41:106:41 | remoteString | GRDB.swift:104:25:104:79 | call to String.init(contentsOf:) : | GRDB.swift:106:41:106:41 | remoteString | This query depends on a $@. | GRDB.swift:104:25:104:79 | call to String.init(contentsOf:) | user-provided value | | GRDB.swift:108:41:108:41 | remoteString | GRDB.swift:104:25:104:79 | call to String.init(contentsOf:) : | GRDB.swift:108:41:108:41 | remoteString | This query depends on a $@. | GRDB.swift:104:25:104:79 | call to String.init(contentsOf:) | user-provided value | @@ -328,3 +352,6 @@ subpaths | sqlite3_c_api.swift:145:26:145:26 | unsafeQuery3 | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:145:26:145:26 | unsafeQuery3 | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) | user-provided value | | sqlite3_c_api.swift:175:29:175:29 | unsafeQuery3 | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:175:29:175:29 | unsafeQuery3 | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) | user-provided value | | sqlite3_c_api.swift:183:29:183:29 | unsafeQuery3 | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:183:29:183:29 | unsafeQuery3 | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) | user-provided value | +| sqlite3_c_api.swift:194:28:194:28 | buffer | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:194:28:194:28 | buffer | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) | user-provided value | +| sqlite3_c_api.swift:202:31:202:31 | buffer | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:202:31:202:31 | buffer | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) | user-provided value | +| sqlite3_c_api.swift:210:31:210:31 | buffer | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) : | sqlite3_c_api.swift:210:31:210:31 | buffer | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | call to String.init(contentsOf:) | user-provided value | diff --git a/swift/ql/test/query-tests/Security/CWE-089/sqlite3_c_api.swift b/swift/ql/test/query-tests/Security/CWE-089/sqlite3_c_api.swift index 8a491b82baa..d04dec0debc 100644 --- a/swift/ql/test/query-tests/Security/CWE-089/sqlite3_c_api.swift +++ b/swift/ql/test/query-tests/Security/CWE-089/sqlite3_c_api.swift @@ -191,7 +191,7 @@ func test_sqlite3_c_api(db: OpaquePointer?, buffer: UnsafeMutablePointer) var stmt6: OpaquePointer? - if (sqlite3_prepare16(db, buffer, Int32(data.count), &stmt6, nil) == SQLITE_OK) { // BAD [NOT DETECTED] + if (sqlite3_prepare16(db, buffer, Int32(data.count), &stmt6, nil) == SQLITE_OK) { // BAD let result = sqlite3_step(stmt6) // ... } @@ -199,7 +199,7 @@ func test_sqlite3_c_api(db: OpaquePointer?, buffer: UnsafeMutablePointer) var stmt7: OpaquePointer? - if (sqlite3_prepare16_v2(db, buffer, Int32(data.count), &stmt7, nil) == SQLITE_OK) { // BAD [NOT DETECTED] + if (sqlite3_prepare16_v2(db, buffer, Int32(data.count), &stmt7, nil) == SQLITE_OK) { // BAD let result = sqlite3_step(stmt7) // ... } @@ -207,7 +207,7 @@ func test_sqlite3_c_api(db: OpaquePointer?, buffer: UnsafeMutablePointer) var stmt8: OpaquePointer? - if (sqlite3_prepare16_v3(db, buffer, Int32(data.count), 0, &stmt8, nil) == SQLITE_OK) { // BAD [NOT DETECTED] + if (sqlite3_prepare16_v3(db, buffer, Int32(data.count), 0, &stmt8, nil) == SQLITE_OK) { // BAD let result = sqlite3_step(stmt8) // ... }