diff --git a/amendments.csv b/amendments.csv index abe31e7815..1e774bc0c3 100644 --- a/amendments.csv +++ b/amendments.csv @@ -1,7 +1,7 @@ language,standard,amendment,rule_id,supportable,implementation_category,implemented,difficulty c,MISRA-C-2012,Amendment3,DIR-4-6,Yes,Expand,Yes,Easy c,MISRA-C-2012,Amendment3,DIR-4-9,Yes,Refine,Yes,Easy -c,MISRA-C-2012,Amendment3,DIR-4-11,Yes,Refine,No,Import +c,MISRA-C-2012,Amendment3,DIR-4-11,Yes,Refine,Yes,Import c,MISRA-C-2012,Amendment3,RULE-1-4,Yes,Replace,Yes,Easy c,MISRA-C-2012,Amendment3,RULE-10-1,Yes,Replace,Yes,Easy c,MISRA-C-2012,Amendment3,RULE-10-3,Yes,Refine,Yes,Easy diff --git a/c/misra/src/rules/DIR-4-11/LowPrecisionPeriodicTrigonometricFunctionCall.ql b/c/misra/src/rules/DIR-4-11/LowPrecisionPeriodicTrigonometricFunctionCall.ql new file mode 100644 index 0000000000..6a910a1a71 --- /dev/null +++ b/c/misra/src/rules/DIR-4-11/LowPrecisionPeriodicTrigonometricFunctionCall.ql @@ -0,0 +1,43 @@ +/** + * @id c/misra/low-precision-periodic-trigonometric-function-call + * @name DIR-4-11: The validity of values passed to trigonometric functions shall be checked + * @description Trigonometric periodic functions have significantly less precision when called with + * large floating-point values. + * @kind problem + * @precision high + * @problem.severity warning + * @tags external/misra/id/dir-4-11 + * correctness + * external/misra/c/2012/third-edition-first-revision + * external/misra/c/2012/amendment3 + * external/misra/obligation/required + */ + +import cpp +import codingstandards.c.misra +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis + +float getMaxAllowedAbsoluteValue(FloatingPointType t, string description) { + if t.getSize() <= 4 + then ( + // Per MISRA, assume k=1 for float types. + result = 3.15 and description = "pi" + ) else ( + // Allow k=10 for doubles, as the standard allows for a larger range depending on the + // implementation, application, and precision goals. + result = 10 * 3.15 and description = "10 * pi" + ) +} + +from FunctionCall fc, Expr argument, float maxValue, float maxAllowedValue, string maxAllowedStr +where + not isExcluded(fc, ContractsPackage::lowPrecisionPeriodicTrigonometricFunctionCallQuery()) and + fc.getTarget().getName() = ["sin", "cos", "tan"] and + argument = fc.getArgument(0) and + maxValue = rank[1](float bound | bound = [lowerBound(argument), upperBound(argument)].abs()) and + maxAllowedValue = getMaxAllowedAbsoluteValue(argument.getType(), maxAllowedStr) and + maxValue > maxAllowedValue +select fc, + "Call to periodic trigonometric function " + fc.getTarget().getName() + + " with maximum argument absolute value of " + maxValue.toString() + + ", which exceeds the recommended " + "maximum of " + maxAllowedStr + "." diff --git a/c/misra/test/rules/DIR-4-11/LowPrecisionPeriodicTrigonometricFunctionCall.expected b/c/misra/test/rules/DIR-4-11/LowPrecisionPeriodicTrigonometricFunctionCall.expected new file mode 100644 index 0000000000..d5d5892975 --- /dev/null +++ b/c/misra/test/rules/DIR-4-11/LowPrecisionPeriodicTrigonometricFunctionCall.expected @@ -0,0 +1,18 @@ +| test.c:32:5:32:7 | call to sin | Call to periodic trigonometric function sin with maximum argument absolute value of 31.4, which exceeds the recommended maximum of pi. | +| test.c:33:5:33:7 | call to cos | Call to periodic trigonometric function cos with maximum argument absolute value of 31.4, which exceeds the recommended maximum of pi. | +| test.c:34:5:34:7 | call to tan | Call to periodic trigonometric function tan with maximum argument absolute value of 31.4, which exceeds the recommended maximum of pi. | +| test.c:38:5:38:7 | call to sin | Call to periodic trigonometric function sin with maximum argument absolute value of 31.4, which exceeds the recommended maximum of pi. | +| test.c:39:5:39:7 | call to cos | Call to periodic trigonometric function cos with maximum argument absolute value of 31.4, which exceeds the recommended maximum of pi. | +| test.c:40:5:40:7 | call to tan | Call to periodic trigonometric function tan with maximum argument absolute value of 31.4, which exceeds the recommended maximum of pi. | +| test.c:49:5:49:7 | call to sin | Call to periodic trigonometric function sin with maximum argument absolute value of 314, which exceeds the recommended maximum of pi. | +| test.c:50:5:50:7 | call to cos | Call to periodic trigonometric function cos with maximum argument absolute value of 314, which exceeds the recommended maximum of pi. | +| test.c:51:5:51:7 | call to tan | Call to periodic trigonometric function tan with maximum argument absolute value of 314, which exceeds the recommended maximum of pi. | +| test.c:52:5:52:7 | call to sin | Call to periodic trigonometric function sin with maximum argument absolute value of 314, which exceeds the recommended maximum of 10 * pi. | +| test.c:53:5:53:7 | call to cos | Call to periodic trigonometric function cos with maximum argument absolute value of 314, which exceeds the recommended maximum of 10 * pi. | +| test.c:54:5:54:7 | call to tan | Call to periodic trigonometric function tan with maximum argument absolute value of 314, which exceeds the recommended maximum of 10 * pi. | +| test.c:55:5:55:7 | call to sin | Call to periodic trigonometric function sin with maximum argument absolute value of 314, which exceeds the recommended maximum of pi. | +| test.c:56:5:56:7 | call to cos | Call to periodic trigonometric function cos with maximum argument absolute value of 314, which exceeds the recommended maximum of pi. | +| test.c:57:5:57:7 | call to tan | Call to periodic trigonometric function tan with maximum argument absolute value of 314, which exceeds the recommended maximum of pi. | +| test.c:58:5:58:7 | call to sin | Call to periodic trigonometric function sin with maximum argument absolute value of 314, which exceeds the recommended maximum of 10 * pi. | +| test.c:59:5:59:7 | call to cos | Call to periodic trigonometric function cos with maximum argument absolute value of 314, which exceeds the recommended maximum of 10 * pi. | +| test.c:60:5:60:7 | call to tan | Call to periodic trigonometric function tan with maximum argument absolute value of 314, which exceeds the recommended maximum of 10 * pi. | diff --git a/c/misra/test/rules/DIR-4-11/LowPrecisionPeriodicTrigonometricFunctionCall.qlref b/c/misra/test/rules/DIR-4-11/LowPrecisionPeriodicTrigonometricFunctionCall.qlref new file mode 100644 index 0000000000..f7bd11b44d --- /dev/null +++ b/c/misra/test/rules/DIR-4-11/LowPrecisionPeriodicTrigonometricFunctionCall.qlref @@ -0,0 +1 @@ +rules/DIR-4-11/LowPrecisionPeriodicTrigonometricFunctionCall.ql \ No newline at end of file diff --git a/c/misra/test/rules/DIR-4-11/test.c b/c/misra/test/rules/DIR-4-11/test.c new file mode 100644 index 0000000000..dac34860ff --- /dev/null +++ b/c/misra/test/rules/DIR-4-11/test.c @@ -0,0 +1,62 @@ +#include +void f(int x) { + float f1 = 0.0f; + double d1 = 0.0f; + sin(f1); // COMPLIANT + cos(f1); // COMPLIANT + tan(f1); // COMPLIANT + sin(d1); // COMPLIANT + cos(d1); // COMPLIANT + tan(d1); // COMPLIANT + + if (x < 10) { + f1 += 3.14; + d1 += 3.14; + sin(f1); // COMPLIANT + cos(f1); // COMPLIANT + tan(f1); // COMPLIANT + sin(d1); // COMPLIANT + cos(d1); // COMPLIANT + tan(d1); // COMPLIANT + sin(-f1); // COMPLIANT + cos(-f1); // COMPLIANT + tan(-f1); // COMPLIANT + sin(-d1); // COMPLIANT + cos(-d1); // COMPLIANT + tan(-d1); // COMPLIANT + } + + if (x < 20) { + f1 = 3.14 * 10; + d1 = 3.14 * 10; + sin(f1); // NON-COMPLIANT + cos(f1); // NON-COMPLIANT + tan(f1); // NON-COMPLIANT + sin(d1); // COMPLIANT + cos(d1); // COMPLIANT + tan(d1); // COMPLIANT + sin(-f1); // NON-COMPLIANT + cos(-f1); // NON-COMPLIANT + tan(-f1); // NON-COMPLIANT + sin(-d1); // COMPLIANT + cos(-d1); // COMPLIANT + tan(-d1); // COMPLIANT + } + + if (x < 30) { + f1 = 3.14 * 100; + d1 = 3.14 * 100; + sin(f1); // NON-COMPLIANT + cos(f1); // NON-COMPLIANT + tan(f1); // NON-COMPLIANT + sin(d1); // NON-COMPLIANT + cos(d1); // NON-COMPLIANT + tan(d1); // NON-COMPLIANT + sin(-f1); // NON-COMPLIANT + cos(-f1); // NON-COMPLIANT + tan(-f1); // NON-COMPLIANT + sin(-d1); // NON-COMPLIANT + cos(-d1); // NON-COMPLIANT + tan(-d1); // NON-COMPLIANT + } +} \ No newline at end of file diff --git a/change_notes/2025-03-27-detect-precision-limit-in-trig-functions.md b/change_notes/2025-03-27-detect-precision-limit-in-trig-functions.md new file mode 100644 index 0000000000..914b25a2a2 --- /dev/null +++ b/change_notes/2025-03-27-detect-precision-limit-in-trig-functions.md @@ -0,0 +1,2 @@ + - `DIR-4-11` - `LowPrecisionPeriodicTrigonometricFunctionCall.ql`: + - New query within rule added to detect calls to periodic trigonometric functions with values outside of pi*k for k that depends on implementation and application precision goals, assuming k=1 for 32 bit floating types and k=10 for 64 bit floating types. diff --git a/cpp/common/src/codingstandards/cpp/exclusions/c/Contracts.qll b/cpp/common/src/codingstandards/cpp/exclusions/c/Contracts.qll index 32a44a4355..174e7769b7 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/c/Contracts.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/c/Contracts.qll @@ -6,6 +6,7 @@ import codingstandards.cpp.exclusions.RuleMetadata newtype ContractsQuery = TDoNotViolateInLineLinkageConstraintsQuery() or TCheckMathLibraryFunctionParametersQuery() or + TLowPrecisionPeriodicTrigonometricFunctionCallQuery() or TFunctionErrorInformationUntestedQuery() predicate isContractsQueryMetadata(Query query, string queryId, string ruleId, string category) { @@ -27,6 +28,15 @@ predicate isContractsQueryMetadata(Query query, string queryId, string ruleId, s ruleId = "DIR-4-11" and category = "required" or + query = + // `Query` instance for the `lowPrecisionPeriodicTrigonometricFunctionCall` query + ContractsPackage::lowPrecisionPeriodicTrigonometricFunctionCallQuery() and + queryId = + // `@id` for the `lowPrecisionPeriodicTrigonometricFunctionCall` query + "c/misra/low-precision-periodic-trigonometric-function-call" and + ruleId = "DIR-4-11" and + category = "required" + or query = // `Query` instance for the `functionErrorInformationUntested` query ContractsPackage::functionErrorInformationUntestedQuery() and @@ -52,6 +62,13 @@ module ContractsPackage { TQueryC(TContractsPackageQuery(TCheckMathLibraryFunctionParametersQuery())) } + Query lowPrecisionPeriodicTrigonometricFunctionCallQuery() { + //autogenerate `Query` type + result = + // `Query` type for `lowPrecisionPeriodicTrigonometricFunctionCall` query + TQueryC(TContractsPackageQuery(TLowPrecisionPeriodicTrigonometricFunctionCallQuery())) + } + Query functionErrorInformationUntestedQuery() { //autogenerate `Query` type result = diff --git a/rule_packages/c/Contracts.json b/rule_packages/c/Contracts.json index 40bf3d8b0b..e7db6fff86 100644 --- a/rule_packages/c/Contracts.json +++ b/rule_packages/c/Contracts.json @@ -44,6 +44,19 @@ "implementation_scope": { "description": "This query identifies possible domain, pole and range errors on a selection of C standard library fuctions from math.h." } + }, + { + "description": "Trigonometric periodic functions have significantly less precision when called with large floating-point values.", + "kind": "problem", + "name": "The validity of values passed to trigonometric functions shall be checked", + "precision": "high", + "severity": "warning", + "short_name": "LowPrecisionPeriodicTrigonometricFunctionCall", + "tags": [ + "correctness", + "external/misra/c/2012/third-edition-first-revision", + "external/misra/c/2012/amendment3" + ] } ], "title": "The validity of values passed to library functions shall be checked"