From d9b41d220f4b4dc142b8a99556419b9c94b5c0bc Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Tue, 30 Jan 2024 15:34:36 -0800 Subject: [PATCH 01/10] Include non-member assignment operators --- cpp/common/src/codingstandards/cpp/Operator.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/common/src/codingstandards/cpp/Operator.qll b/cpp/common/src/codingstandards/cpp/Operator.qll index 72ee04b68f..e53adb0255 100644 --- a/cpp/common/src/codingstandards/cpp/Operator.qll +++ b/cpp/common/src/codingstandards/cpp/Operator.qll @@ -119,7 +119,7 @@ class UserAssignmentOperator extends AssignmentOperator { } /** An assignment operator of any sort */ -class AssignmentOperator extends MemberFunction { +class AssignmentOperator extends Function { AssignmentOperator() { // operator op, where op is =, +=, -=, *=, /=, %=, ^=, &=, |=, >>=, <<= exists(string op | From c5fe049a882e0fc9799b32fcd9fbd246f4744420 Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Tue, 30 Jan 2024 15:35:45 -0800 Subject: [PATCH 02/10] Add stream insertion and extraction operators --- .../src/codingstandards/cpp/Operator.qll | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/cpp/common/src/codingstandards/cpp/Operator.qll b/cpp/common/src/codingstandards/cpp/Operator.qll index e53adb0255..e57989c5c5 100644 --- a/cpp/common/src/codingstandards/cpp/Operator.qll +++ b/cpp/common/src/codingstandards/cpp/Operator.qll @@ -1,5 +1,6 @@ import cpp import Expr +private import semmle.code.cpp.security.FileWrite /** * any assignment operator that also reads from the access @@ -264,3 +265,51 @@ class UserOverloadedOperator extends Function { not this.isCompilerGenerated() } } + +/** + * A `std::basic_istream` class, or something that can be used + * as one. Based on the BasicOStreamClass. + */ +private class BasicIStreamClass extends Type { + BasicIStreamClass() { + this.(Class).getName().matches("basic\\_istream%") + or + this.getUnspecifiedType() instanceof BasicIStreamClass + or + this.(Class).getABaseClass() instanceof BasicIStreamClass + or + this.(ReferenceType).getBaseType() instanceof BasicIStreamClass + } +} + +/** An implementation of a stream insertion operator. */ +class StreamInsertionOperator extends Function { + StreamInsertionOperator() { + this.hasName("operator<<") and + ( + if this.isMember() + then this.getNumberOfParameters() = 1 + else ( + this.getNumberOfParameters() = 2 and + this.getParameter(0).getType() instanceof BasicOStreamClass + ) + ) and + this.getType() instanceof BasicOStreamClass + } +} + +/** An implementation of a stream extraction operator. */ +class StreamExtractionOperator extends Function { + StreamExtractionOperator() { + this.hasName("operator>>") and + ( + if this.isMember() + then this.getNumberOfParameters() = 1 + else ( + this.getNumberOfParameters() = 2 and + this.getParameter(0).getType() instanceof BasicIStreamClass + ) + ) and + this.getType() instanceof BasicIStreamClass + } +} From 00fa7604b5b3af21e3997456cfd7f1b259ac75c3 Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Tue, 30 Jan 2024 15:36:40 -0800 Subject: [PATCH 03/10] Change definition of out parameter and add exclusions An out parameter is a non-const reference or pointer that is modified. We removed assignment operators and crement operators as exclusions to the modified property, because they should still be used to qualify an out parameter. Additionally we exclude user-defined non-member assignment operator and stream related operator parameters that are required to be passed by non-const reference. --- .../src/rules/A8-4-8/OutputParametersUsed.ql | 69 +++++++++++++------ .../A8-4-8/OutputParametersUsed.expected | 11 +-- cpp/autosar/test/rules/A8-4-8/test.cpp | 37 +++++++++- 3 files changed, 91 insertions(+), 26 deletions(-) diff --git a/cpp/autosar/src/rules/A8-4-8/OutputParametersUsed.ql b/cpp/autosar/src/rules/A8-4-8/OutputParametersUsed.ql index aa480a95f7..c2fc51fcdf 100644 --- a/cpp/autosar/src/rules/A8-4-8/OutputParametersUsed.ql +++ b/cpp/autosar/src/rules/A8-4-8/OutputParametersUsed.ql @@ -23,31 +23,60 @@ import codingstandards.cpp.ConstHelpers import codingstandards.cpp.Operator /** - * Non-const T& and T* `Parameter`s to `Function`s + * Holds if p is passed as a non-const reference or pointer and is modified. + * This holds for in-out or out-only parameters. */ -class NonConstReferenceOrPointerParameterCandidate extends FunctionParameter { - NonConstReferenceOrPointerParameterCandidate() { - this instanceof NonConstReferenceParameter - or - this instanceof NonConstPointerParameter - } +predicate isOutParameter(NonConstPointerorReferenceParameter p) { + any(VariableEffect ve).getTarget() = p +} + +/** + * Holds if parameter `p` is a parameter to a user defined assignment operator that + * is defined outside of a class body. + * These require an in-out parameter as the first argument. + */ +predicate isNonMemberUserAssignmentParameter(NonConstPointerorReferenceParameter p) { + p.getFunction() instanceof UserAssignmentOperator and + not p.isMember() +} + +/** + * Holds if parameter `p` is a parameter to a stream insertion operator that + * is defined outside of a class body. + * These require an in-out parameter as the first argument. + * + * e.g., `std::ostream& operator<<(std::ostream& os, const T& obj)` + */ +predicate isStreamInsertionStreamParameter(NonConstPointerorReferenceParameter p) { + exists(StreamInsertionOperator op | not op.isMember() | op.getParameter(0) = p) } -pragma[inline] -predicate isFirstAccess(VariableAccess va) { - not exists(VariableAccess otherVa | - otherVa.getTarget() = va.getTarget() or - otherVa.getQualifier().(VariableAccess).getTarget() = va.getTarget() - | - otherVa.getASuccessor() = va +/** + * Holds if parameter `p` is a parameter to a stream insertion operator that + * is defined outside of a class body. + * These require an in-out parameter as the first argument and an out parameter for the second. + * + * e.g., `std::istream& operator>>(std::istream& is, T& obj)` + */ +predicate isStreamExtractionParameter(NonConstPointerorReferenceParameter p) { + exists(StreamExtractionOperator op | not op.isMember() | + op.getParameter(0) = p + or + op.getParameter(1) = p ) } -from NonConstReferenceOrPointerParameterCandidate p, VariableEffect ve +predicate isException(NonConstPointerorReferenceParameter p) { + isNonMemberUserAssignmentParameter(p) and p.getIndex() = 0 + or + isStreamInsertionStreamParameter(p) + or + isStreamExtractionParameter(p) +} + +from NonConstPointerorReferenceParameter p where not isExcluded(p, ConstPackage::outputParametersUsedQuery()) and - ve.getTarget() = p and - isFirstAccess(ve.getAnAccess()) and - not ve instanceof AnyAssignOperation and - not ve instanceof CrementOperation -select p, "Out parameter " + p.getName() + " that is modified before being read." + isOutParameter(p) and + not isException(p) +select p, "Out parameter '" + p.getName() + "' used." diff --git a/cpp/autosar/test/rules/A8-4-8/OutputParametersUsed.expected b/cpp/autosar/test/rules/A8-4-8/OutputParametersUsed.expected index 6b0df8d0dd..221def5a42 100644 --- a/cpp/autosar/test/rules/A8-4-8/OutputParametersUsed.expected +++ b/cpp/autosar/test/rules/A8-4-8/OutputParametersUsed.expected @@ -1,5 +1,6 @@ -| test.cpp:5:22:5:24 | str | Out parameter str that is modified before being read. | -| test.cpp:16:14:16:14 | i | Out parameter i that is modified before being read. | -| test.cpp:21:14:21:14 | i | Out parameter i that is modified before being read. | -| test.cpp:29:12:29:12 | a | Out parameter a that is modified before being read. | -| test.cpp:33:12:33:12 | a | Out parameter a that is modified before being read. | +| test.cpp:5:22:5:24 | str | Out parameter 'str' used. | +| test.cpp:8:22:8:24 | str | Out parameter 'str' used. | +| test.cpp:16:14:16:14 | i | Out parameter 'i' used. | +| test.cpp:21:14:21:14 | i | Out parameter 'i' used. | +| test.cpp:29:12:29:12 | a | Out parameter 'a' used. | +| test.cpp:33:12:33:12 | a | Out parameter 'a' used. | diff --git a/cpp/autosar/test/rules/A8-4-8/test.cpp b/cpp/autosar/test/rules/A8-4-8/test.cpp index e41a61704b..baf0255291 100644 --- a/cpp/autosar/test/rules/A8-4-8/test.cpp +++ b/cpp/autosar/test/rules/A8-4-8/test.cpp @@ -5,7 +5,7 @@ void f(int &i) { // COMPLIANT void f1(std::string &str) { // NON_COMPLIANT str = "replacement"; } -void f2(std::string &str) { // COMPLIANT +void f2(std::string &str) { // NON_COMPLIANT str += "suffix"; } @@ -37,3 +37,38 @@ void f7(A &a) { // NON_COMPLIANT void f8(int i) { // COMPLIANT i += 1; } + +constexpr A &operator|=( + A &lhs, + const A &rhs) noexcept { // COMPLIANT - non-member user defined assignment + // operators are considered an exception. + return lhs; +} + +enum class byte : unsigned char {}; +constexpr byte &operator|(const byte &lhs, const byte &rhs) noexcept { + return lhs | rhs; +} +constexpr byte &operator|=( + byte &lhs, + const byte rhs) noexcept { // COMPLIANT - non-member user defined assignment + // operators are considered an exception. + lhs = (lhs | rhs); + return lhs; +} + +#include +std::ostream &operator<<(std::ostream &os, + const byte &obj) { // COMPLIANT - insertion operators + // are considered an exception. + std::ostream other; + os = other; // simulate modification + return os; +} + +std::istream &operator>>(std::istream &is, + byte &obj) { // COMPLIANT - extraction operators are + // considered an exception. + obj = static_cast('a'); // simulate modification + return is; +} \ No newline at end of file From 0b04a75fa6a0f2a2507db7c05c105fed4fd91ec0 Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Tue, 30 Jan 2024 15:48:33 -0800 Subject: [PATCH 04/10] Add changenote --- change_notes/2024-01-30-fix-fp-for-a8-4-8.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 change_notes/2024-01-30-fix-fp-for-a8-4-8.md diff --git a/change_notes/2024-01-30-fix-fp-for-a8-4-8.md b/change_notes/2024-01-30-fix-fp-for-a8-4-8.md new file mode 100644 index 0000000000..3a9bcc5968 --- /dev/null +++ b/change_notes/2024-01-30-fix-fp-for-a8-4-8.md @@ -0,0 +1,3 @@ +- `A8-4-8` - `OutParametersUsed.ql` + - Fixes #370 - Non-member user-defined assignment operator and stream insertion/extraction parameters that are required to be out parameters are excluded. + - Broadens the definition of out parameter by considering assignment and crement operators as modifications to an out parameter candidate. From e54b4c5c16e86e5eedd5ecb1cdb4d323ef1e1e6e Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Wed, 31 Jan 2024 17:22:02 -0800 Subject: [PATCH 05/10] Add missing predicate `isLValueRefQualified` --- cpp/common/src/codingstandards/cpp/Operator.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp/common/src/codingstandards/cpp/Operator.qll b/cpp/common/src/codingstandards/cpp/Operator.qll index e57989c5c5..3a5ed9d1bc 100644 --- a/cpp/common/src/codingstandards/cpp/Operator.qll +++ b/cpp/common/src/codingstandards/cpp/Operator.qll @@ -128,6 +128,8 @@ class AssignmentOperator extends Function { op in ["=", "+=", "-=", "*=", "/=", "%=", "^=", "&=", "|=", ">>=", "<<="] ) } + + predicate isLValueRefQualified() { this.(MemberFunction).isLValueRefQualified() } } class UserComparisonOperator extends Function { From a10476f2c8311c554e7b37d13827b8e0cc1b78df Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Fri, 2 Feb 2024 17:31:48 -0800 Subject: [PATCH 06/10] Annotate compliant case --- cpp/autosar/test/rules/A8-4-8/test.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cpp/autosar/test/rules/A8-4-8/test.cpp b/cpp/autosar/test/rules/A8-4-8/test.cpp index baf0255291..fd2e5e8763 100644 --- a/cpp/autosar/test/rules/A8-4-8/test.cpp +++ b/cpp/autosar/test/rules/A8-4-8/test.cpp @@ -46,7 +46,10 @@ constexpr A &operator|=( } enum class byte : unsigned char {}; -constexpr byte &operator|(const byte &lhs, const byte &rhs) noexcept { +constexpr byte & +operator|(const byte &lhs, + const byte &rhs) noexcept { // COMPLIANT - parameters are const + // qualified references return lhs | rhs; } constexpr byte &operator|=( From ef8c9be1bc161f3affe98a347a1713993ae8dcf6 Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Fri, 2 Feb 2024 17:32:58 -0800 Subject: [PATCH 07/10] Reuse and extend existing modelling --- .../CloseFilesWhenTheyAreNoLongerNeeded.ql | 5 ++-- .../src/codingstandards/cpp/Operator.qll | 21 ++------------ .../cpp/standardlibrary/FileStreams.qll | 29 +++++++++++++++---- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/cpp/cert/src/rules/FIO51-CPP/CloseFilesWhenTheyAreNoLongerNeeded.ql b/cpp/cert/src/rules/FIO51-CPP/CloseFilesWhenTheyAreNoLongerNeeded.ql index 8736348682..343cef26e8 100644 --- a/cpp/cert/src/rules/FIO51-CPP/CloseFilesWhenTheyAreNoLongerNeeded.ql +++ b/cpp/cert/src/rules/FIO51-CPP/CloseFilesWhenTheyAreNoLongerNeeded.ql @@ -38,8 +38,9 @@ predicate filebufAccess(ControlFlowNode node, FileStreamSource fss) { //insertion or extraction operator calls node.(InsertionOperatorCall).getFStream() = fss.getAUse() or node.(ExtractionOperatorCall).getFStream() = fss.getAUse() or - //methods inherited from istream or ostream - node.(IOStreamFunctionCall).getFStream() = fss.getAUse() + // Methods inherited from istream or ostream that access the file stream. + // Exclude is_open as it is not a filebuf access + any(IOStreamFunctionCall call | node = call and not call.getTarget().hasName("is_open")).getFStream() = fss.getAUse() } /** diff --git a/cpp/common/src/codingstandards/cpp/Operator.qll b/cpp/common/src/codingstandards/cpp/Operator.qll index 3a5ed9d1bc..5838a043e9 100644 --- a/cpp/common/src/codingstandards/cpp/Operator.qll +++ b/cpp/common/src/codingstandards/cpp/Operator.qll @@ -1,6 +1,7 @@ import cpp import Expr private import semmle.code.cpp.security.FileWrite +private import codingstandards.cpp.standardlibrary.FileStreams /** * any assignment operator that also reads from the access @@ -268,22 +269,6 @@ class UserOverloadedOperator extends Function { } } -/** - * A `std::basic_istream` class, or something that can be used - * as one. Based on the BasicOStreamClass. - */ -private class BasicIStreamClass extends Type { - BasicIStreamClass() { - this.(Class).getName().matches("basic\\_istream%") - or - this.getUnspecifiedType() instanceof BasicIStreamClass - or - this.(Class).getABaseClass() instanceof BasicIStreamClass - or - this.(ReferenceType).getBaseType() instanceof BasicIStreamClass - } -} - /** An implementation of a stream insertion operator. */ class StreamInsertionOperator extends Function { StreamInsertionOperator() { @@ -309,9 +294,9 @@ class StreamExtractionOperator extends Function { then this.getNumberOfParameters() = 1 else ( this.getNumberOfParameters() = 2 and - this.getParameter(0).getType() instanceof BasicIStreamClass + this.getParameter(0).getType() instanceof IStream ) ) and - this.getType() instanceof BasicIStreamClass + this.getType() instanceof IStream } } diff --git a/cpp/common/src/codingstandards/cpp/standardlibrary/FileStreams.qll b/cpp/common/src/codingstandards/cpp/standardlibrary/FileStreams.qll index 4d495fce3e..f680f99fc4 100644 --- a/cpp/common/src/codingstandards/cpp/standardlibrary/FileStreams.qll +++ b/cpp/common/src/codingstandards/cpp/standardlibrary/FileStreams.qll @@ -12,6 +12,7 @@ import cpp import codingstandards.cpp.dataflow.DataFlow import codingstandards.cpp.dataflow.TaintTracking +private import codingstandards.cpp.Operator /** * A `basic_fstream` like `std::fstream` @@ -23,15 +24,31 @@ class FileStream extends ClassTemplateInstantiation { /** * A `basic_istream` like `std::istream` */ -class IStream extends ClassTemplateInstantiation { - IStream() { this.getTemplate().hasQualifiedName("std", "basic_istream") } +class IStream extends Type { + IStream() { + this.(Class).getQualifiedName().matches("std::basic\\_istream%") + or + this.getUnspecifiedType() instanceof IStream + or + this.(Class).getABaseClass() instanceof IStream + or + this.(ReferenceType).getBaseType() instanceof IStream + } } /** * A `basic_ostream` like `std::ostream` */ -class OStream extends ClassTemplateInstantiation { - OStream() { this.getTemplate().hasQualifiedName("std", "basic_ostream") } +class OStream extends Type { + OStream() { + this.(Class).getQualifiedName().matches("std::basic\\_ostream%") + or + this.getUnspecifiedType() instanceof OStream + or + this.(Class).getABaseClass() instanceof OStream + or + this.(ReferenceType).getBaseType() instanceof OStream + } } /** @@ -53,7 +70,7 @@ predicate sameStreamSource(FileStreamFunctionCall a, FileStreamFunctionCall b) { * Insertion `operator<<` and Extraction `operator>>` operators. */ class InsertionOperatorCall extends FileStreamFunctionCall { - InsertionOperatorCall() { this.getTarget().(Operator).hasQualifiedName("std", "operator<<") } + InsertionOperatorCall() { this.getTarget() instanceof StreamInsertionOperator } override Expr getFStream() { result = this.getQualifier() @@ -63,7 +80,7 @@ class InsertionOperatorCall extends FileStreamFunctionCall { } class ExtractionOperatorCall extends FileStreamFunctionCall { - ExtractionOperatorCall() { this.getTarget().(Operator).hasQualifiedName("std", "operator>>") } + ExtractionOperatorCall() { this.getTarget() instanceof StreamExtractionOperator } override Expr getFStream() { result = this.getQualifier() From 9ca33f397536cd61834f0a22c05873788bf6ed9f Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Mon, 5 Feb 2024 17:13:57 -0800 Subject: [PATCH 08/10] Apply correct query format --- .../src/rules/FIO51-CPP/CloseFilesWhenTheyAreNoLongerNeeded.ql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/cert/src/rules/FIO51-CPP/CloseFilesWhenTheyAreNoLongerNeeded.ql b/cpp/cert/src/rules/FIO51-CPP/CloseFilesWhenTheyAreNoLongerNeeded.ql index 343cef26e8..383fb9db1f 100644 --- a/cpp/cert/src/rules/FIO51-CPP/CloseFilesWhenTheyAreNoLongerNeeded.ql +++ b/cpp/cert/src/rules/FIO51-CPP/CloseFilesWhenTheyAreNoLongerNeeded.ql @@ -40,7 +40,8 @@ predicate filebufAccess(ControlFlowNode node, FileStreamSource fss) { node.(ExtractionOperatorCall).getFStream() = fss.getAUse() or // Methods inherited from istream or ostream that access the file stream. // Exclude is_open as it is not a filebuf access - any(IOStreamFunctionCall call | node = call and not call.getTarget().hasName("is_open")).getFStream() = fss.getAUse() + any(IOStreamFunctionCall call | node = call and not call.getTarget().hasName("is_open")) + .getFStream() = fss.getAUse() } /** From 2cbb15273477e7483aa685523817fd4b8b482601 Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Wed, 7 Feb 2024 15:20:11 -0800 Subject: [PATCH 09/10] Apply query formatting --- cpp/common/src/codingstandards/cpp/Operator.qll | 4 ++-- .../src/codingstandards/cpp/standardlibrary/FileStreams.qll | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/common/src/codingstandards/cpp/Operator.qll b/cpp/common/src/codingstandards/cpp/Operator.qll index 5838a043e9..065e92064a 100644 --- a/cpp/common/src/codingstandards/cpp/Operator.qll +++ b/cpp/common/src/codingstandards/cpp/Operator.qll @@ -294,9 +294,9 @@ class StreamExtractionOperator extends Function { then this.getNumberOfParameters() = 1 else ( this.getNumberOfParameters() = 2 and - this.getParameter(0).getType() instanceof IStream + this.getParameter(0).getType() instanceof IStream ) ) and - this.getType() instanceof IStream + this.getType() instanceof IStream } } diff --git a/cpp/common/src/codingstandards/cpp/standardlibrary/FileStreams.qll b/cpp/common/src/codingstandards/cpp/standardlibrary/FileStreams.qll index f680f99fc4..c4724d36c2 100644 --- a/cpp/common/src/codingstandards/cpp/standardlibrary/FileStreams.qll +++ b/cpp/common/src/codingstandards/cpp/standardlibrary/FileStreams.qll @@ -25,7 +25,7 @@ class FileStream extends ClassTemplateInstantiation { * A `basic_istream` like `std::istream` */ class IStream extends Type { - IStream() { + IStream() { this.(Class).getQualifiedName().matches("std::basic\\_istream%") or this.getUnspecifiedType() instanceof IStream @@ -40,7 +40,7 @@ class IStream extends Type { * A `basic_ostream` like `std::ostream` */ class OStream extends Type { - OStream() { + OStream() { this.(Class).getQualifiedName().matches("std::basic\\_ostream%") or this.getUnspecifiedType() instanceof OStream From ad26759fae9faed5d34a7fbeee636855b98111d7 Mon Sep 17 00:00:00 2001 From: Remco Vermeulen Date: Tue, 27 Feb 2024 10:50:55 -0800 Subject: [PATCH 10/10] Add queries impacted by broadened definitions of `IStream` type. The new definition identifies new types that are subclasses of `IStream` which allows for more accurate finding of file stream operations and logging operations. --- change_notes/2024-01-30-fix-fp-for-a8-4-8.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/change_notes/2024-01-30-fix-fp-for-a8-4-8.md b/change_notes/2024-01-30-fix-fp-for-a8-4-8.md index 3a9bcc5968..a71f810b24 100644 --- a/change_notes/2024-01-30-fix-fp-for-a8-4-8.md +++ b/change_notes/2024-01-30-fix-fp-for-a8-4-8.md @@ -1,3 +1,7 @@ - `A8-4-8` - `OutParametersUsed.ql` - Fixes #370 - Non-member user-defined assignment operator and stream insertion/extraction parameters that are required to be out parameters are excluded. - Broadens the definition of out parameter by considering assignment and crement operators as modifications to an out parameter candidate. +- `FIO51-CPP` - `CloseFilesWhenTheyAreNoLongerNeeded.ql`: + - Broadened definition of `IStream` and `OStream` types may result in reduced false negatives. +- `A5-1-1` - `LiteralValueUsedOutsideTypeInit.ql`: + - Broadened definition of `IStream` types may result in reduced false positives because more file stream function calls may be detected as logging operations that will be excluded from the results.