diff --git a/csharp/change-notes/2021-04-23-model-error-extraction.md b/csharp/change-notes/2021-04-23-model-error-extraction.md new file mode 100644 index 000000000000..7ac878442984 --- /dev/null +++ b/csharp/change-notes/2021-04-23-model-error-extraction.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Program model errors are now extracted in standalone extraction. \ No newline at end of file diff --git a/csharp/extractor/Semmle.Extraction/Context.cs b/csharp/extractor/Semmle.Extraction/Context.cs index c0580db96a7d..a60d82c6952a 100644 --- a/csharp/extractor/Semmle.Extraction/Context.cs +++ b/csharp/extractor/Semmle.Extraction/Context.cs @@ -376,6 +376,19 @@ private void ExtractionError(Message msg) Extractor.Message(msg); } + private void ExtractionError(InternalError error) + { + ExtractionError(new Message(error.Message, error.EntityText, CreateLocation(error.Location), error.StackTrace, Severity.Error)); + } + + private void ReportError(InternalError error) + { + if (!Extractor.Standalone) + throw error; + + ExtractionError(error); + } + /// /// Signal an error in the program model. /// @@ -383,8 +396,7 @@ private void ExtractionError(Message msg) /// The error message. public void ModelError(SyntaxNode node, string msg) { - if (!Extractor.Standalone) - throw new InternalError(node, msg); + ReportError(new InternalError(node, msg)); } /// @@ -394,8 +406,7 @@ public void ModelError(SyntaxNode node, string msg) /// The error message. public void ModelError(ISymbol symbol, string msg) { - if (!Extractor.Standalone) - throw new InternalError(symbol, msg); + ReportError(new InternalError(symbol, msg)); } /// @@ -404,8 +415,7 @@ public void ModelError(ISymbol symbol, string msg) /// The error message. public void ModelError(string msg) { - if (!Extractor.Standalone) - throw new InternalError(msg); + ReportError(new InternalError(msg)); } /// diff --git a/csharp/ql/src/Diagnostics/DiagnosticExtractionErrors.ql b/csharp/ql/src/Diagnostics/DiagnosticExtractionErrors.ql new file mode 100644 index 000000000000..23943d8491fb --- /dev/null +++ b/csharp/ql/src/Diagnostics/DiagnosticExtractionErrors.ql @@ -0,0 +1,63 @@ +/** + * @name Extraction errors + * @description List all errors reported by the extractor or the compiler. Extractor errors are + * limited to those files where there are no compilation errors. + * @kind diagnostic + * @id cs/diagnostics/extraction-errors + */ + +import csharp +import semmle.code.csharp.commons.Diagnostics + +private newtype TDiagnosticError = + TCompilerError(CompilerError c) or + TExtractorError(ExtractorError e) + +abstract private class DiagnosticError extends TDiagnosticError { + abstract string getMessage(); + + abstract string toString(); + + abstract Location getLocation(); + + string getLocationMessage() { + if getLocation().getFile().fromSource() + then result = " in " + getLocation().getFile() + else result = "" + } +} + +private class DiagnosticCompilerError extends DiagnosticError { + CompilerError c; + + DiagnosticCompilerError() { this = TCompilerError(c) } + + override string getMessage() { + result = "Compiler error" + this.getLocationMessage() + ": " + c.getMessage() + } + + override string toString() { result = c.toString() } + + override Location getLocation() { result = c.getLocation() } +} + +private class DiagnosticExtractorError extends DiagnosticError { + ExtractorError e; + + DiagnosticExtractorError() { + this = TExtractorError(e) and + not exists(CompilerError ce | ce.getLocation().getFile() = e.getLocation().getFile()) + } + + override string getMessage() { + result = + "Unexpected " + e.getOrigin() + " error" + this.getLocationMessage() + ": " + e.getText() + } + + override string toString() { result = e.toString() } + + override Location getLocation() { result = e.getLocation() } +} + +from DiagnosticError error +select error.getMessage(), 2 diff --git a/csharp/ql/src/Diagnostics/DiagnosticNoExtractionErrors.ql b/csharp/ql/src/Diagnostics/DiagnosticNoExtractionErrors.ql new file mode 100644 index 000000000000..cb86e293efcb --- /dev/null +++ b/csharp/ql/src/Diagnostics/DiagnosticNoExtractionErrors.ql @@ -0,0 +1,17 @@ +/** + * @name Successfully extracted files + * @description A list of all files in the source code directory that were extracted + * without encountering an extraction or compiler error in the file. + * @kind diagnostic + * @id cs/diagnostics/successfully-extracted-files + */ + +import csharp +import semmle.code.csharp.commons.Diagnostics + +from File file +where + file.fromSource() and + not exists(ExtractorError e | e.getLocation().getFile() = file) and + not exists(CompilerError e | e.getLocation().getFile() = file) +select file, "" diff --git a/csharp/ql/test/library-tests/diagnostics/A.cs b/csharp/ql/test/library-tests/diagnostics/A.cs new file mode 100644 index 000000000000..21a5b7f52616 --- /dev/null +++ b/csharp/ql/test/library-tests/diagnostics/A.cs @@ -0,0 +1,4 @@ +public class A +{ + public void M() { } +} \ No newline at end of file diff --git a/csharp/ql/test/library-tests/diagnostics/DiagnosticExtractorErrors.expected b/csharp/ql/test/library-tests/diagnostics/DiagnosticExtractorErrors.expected new file mode 100644 index 000000000000..e4ae5ec1ef29 --- /dev/null +++ b/csharp/ql/test/library-tests/diagnostics/DiagnosticExtractorErrors.expected @@ -0,0 +1 @@ +| Unexpected C# extractor error in Program.cs: Unable to resolve target for call. (Compilation error?) | 2 | diff --git a/csharp/ql/test/library-tests/diagnostics/DiagnosticExtractorErrors.qlref b/csharp/ql/test/library-tests/diagnostics/DiagnosticExtractorErrors.qlref new file mode 100644 index 000000000000..7068705cc1be --- /dev/null +++ b/csharp/ql/test/library-tests/diagnostics/DiagnosticExtractorErrors.qlref @@ -0,0 +1 @@ +Diagnostics/DiagnosticExtractionErrors.ql diff --git a/csharp/ql/test/library-tests/diagnostics/DiagnosticNoExtractorErrors.expected b/csharp/ql/test/library-tests/diagnostics/DiagnosticNoExtractorErrors.expected new file mode 100644 index 000000000000..17e4dc54b7dd --- /dev/null +++ b/csharp/ql/test/library-tests/diagnostics/DiagnosticNoExtractorErrors.expected @@ -0,0 +1 @@ +| A.cs:0:0:0:0 | A.cs | | diff --git a/csharp/ql/test/library-tests/diagnostics/DiagnosticNoExtractorErrors.qlref b/csharp/ql/test/library-tests/diagnostics/DiagnosticNoExtractorErrors.qlref new file mode 100644 index 000000000000..7994e050699f --- /dev/null +++ b/csharp/ql/test/library-tests/diagnostics/DiagnosticNoExtractorErrors.qlref @@ -0,0 +1 @@ +Diagnostics/DiagnosticNoExtractionErrors.ql diff --git a/csharp/ql/test/library-tests/diagnostics/Program.cs b/csharp/ql/test/library-tests/diagnostics/Program.cs new file mode 100644 index 000000000000..7f0a7e57d1cd --- /dev/null +++ b/csharp/ql/test/library-tests/diagnostics/Program.cs @@ -0,0 +1,17 @@ +// semmle-extractor-options: --standalone + +using System; + +class Class +{ + static void Main(string[] args) + { + int z = GetParamLength(__arglist(1, 2)); + } + + public static int GetParamLength(__arglist) + { + ArgIterator iterator = new ArgIterator(__arglist); + return iterator.GetRemainingCount(); + } +} diff --git a/csharp/ql/test/library-tests/standalone/errorrecovery/DiagnosticsAndErrors.expected b/csharp/ql/test/library-tests/standalone/errorrecovery/DiagnosticsAndErrors.expected index 13bdd30b1c40..917d625c10c3 100644 --- a/csharp/ql/test/library-tests/standalone/errorrecovery/DiagnosticsAndErrors.expected +++ b/csharp/ql/test/library-tests/standalone/errorrecovery/DiagnosticsAndErrors.expected @@ -1,3 +1,13 @@ compilationMessages extractorMessages +| errors.cs:8:1:8:22 | Namespace not found | +| errors.cs:24:31:24:32 | Failed to determine type | +| errors.cs:24:31:24:40 | Failed to determine type | +| errors.cs:24:31:24:40 | Unable to resolve target for call. (Compilation error?) | +| errors.cs:24:38:24:39 | Failed to determine type | +| errors.cs:57:20:57:20 | Failed to determine type | +| errors.cs:93:45:93:45 | Failed to determine type | +| errors.cs:93:45:93:45 | Failed to resolve name | +| errors.cs:94:45:94:45 | Failed to determine type | +| errors.cs:94:45:94:45 | Failed to resolve name | | file://:0:0:0:0 | Extracting default argument value 'object RecordNumber = default' instead of 'object RecordNumber = -1'. The latter is not supported in C#. |