Detect Semantic Issues Using Polyspace Query Language Semantic Classes
Semantic issues arise when your code is syntactically correct but behaves in an unexpected manner. Examples of semantic issues include:
Discarding the returned value of a specific function
Unused variables
Multiple definitions of identical macros
Unexpected filenames or file extensions
Polyspace Query Language (PQL) has classes that model semantic objects in C/C++ code such as call sites, fields, variables, functions, macros, and namespaces. These classes have predicates that queries the code to find specific semantic objects. You can construct user-defined defects that look for semantic issues by using logical combination of these predicates. For a list of semantic classes, see Create Your Own Coding Rules and Coding Standard.
This example shows how to define a semantic issue and create a user-defined to detect the issue using PQL. The example defect detects whether variable names follow a specific naming convention.
Define Issue
Define the issue using plain language in as much details as possible. For this example,define the naming convention explicitly:
Variable names must start with a prefix.
The first letter of the prefix must indicate storage duration of the variable:
g(global),s(static), ort(local nonstatic).The next part of the prefix must indicate the data type of the variable:
u(unsigned),s(signed), orst(structured).Finally, if the variable is of an integer type, the prefix must contain number of bytes used for storage.
From this informal definition, you can derive what the defect needs to accomplish. At a high level, the defect needs to detect all variables in your code and extract their names. It also needs to identify the storage duration, data type, and size of the variables. Finally, it needs to calculate the expected prefix and compare it to the actual prefix of the variable name.
Define Defect Implementation Architecture
In this step, build on the issue definition to define the defect architecture. It is helpful to think of the defect as a filter that starts with all the semantic objects in your code. Each condition you add to the defect narrows the search until the desired objects are identified. Break the defect into smaller parts that performs intermediate steps. For example, this defect can contain these parts:
Identify all variables in your code.
Extract the names of variables as strings.
Find the storage duration of a variable. Determine the first letter of the expected prefix from the storage duration.
Find the data type of a variable. Determine the second part of the expected prefix from the data type.
If the variable is of an integer type, find its size in bytes. Determine the number at the end of the expected prefix from the size.
Combine the expected prefix into a string.
Compare the actual prefix of the variable name with the calculates prefix.
Report a defect on variables when the actual prefix does not match the expected prefix.
Outline Implementation of Components
Once you know the parts of the defects, review the predicates associated with the semantic PQL classes and identify how you can implement each part using their supported predicates. There can be multiple ways to implement a defect.
The implementation of a semantic defect uses these PQL classes:
Additionally, the helper class String provides
predicates for string manipulation.
| Component | Relevant predicates | User-defined predicate for the component |
|---|---|---|
Identify all variables in your code and extract their names |
| This predicate queries variables, finds their names and returns the names as string objects. predicate getVarName(Cpp.Variable.Variable variable, Lang.String &varname){
return variable.name(varname)
} |
Find the storage duration of variables. Determine the first letter of the expected prefix using the storage duration. |
| This predicate queries the variables and returns predicate getFirstLetter(Cpp.Variable.Variable variable, Lang.String &letter) {
return
(variable.isGloballyVisible() and Lang.createString( "g",&letter))
or
((variable.isFileStatic() or variable.isFunctionStaticLocal() or variable.isClassStatic()) and Lang.createString( "s", &letter))
or
(variable.isFunctionNonStaticLocal() and Lang.createString("t", &letter))
} |
Find the data type of the variable. Determine the second part of the expected prefix using the data type. |
| This predicate queries variables and checks their types. If the type is
unsigned, the predicate returns predicate getSecondPart(Cpp.Variable.Variable variable, Lang.String &letter) {
return
variable.type(&type) and (
(type.isUnsigned() and Lang.createString("u", &letter))
or
(type.isSigned() and Lang.createString("s", &letter))
or
((type.isStruct() or type.isClass()) and Lang.createString("st", &letter))
)
} |
For an integer variable, find the size in bytes. Determine the number at the end of the expected prefix using the size. |
| This predicates queries the type of a variable. If the variable is of an integral type, the predicate returns a string representing the size of the type in bytes. predicate getThirdPart(Cpp.Variable.Variable variable, Lang.String &letter) {
return
variable.type(&type) and type.isIntegral()
and type.sizeInBytes(&size) and Lang.toString(size, &letter)
} |
Combine the expected prefix into a string. and check if the actual prefix of the variable name matches the prefix. |
| This predicate queries a variable and determines the expected prefix
for the variable name. The predicate returns predicate isNotCorrectPrefix(Cpp.Variable.Variable variable, Lang.String &prefix) {
return
variable.name(&varname)
and getFirstLetter(variable, &F)
and getSecondPart(variable, &S)
and getThirdPart(variable, &T)
and Lang.catString(F, S, &prefixA)
and Lang.catString( prefixA, T, &prefix)
and not varname.startsWith(prefix)
} |
Implement Defect
Implementing defects can be an iterative process. Execute the outlined predicates from
the preceding step on sample C/C++ code iteratively until the defect behaves as expected. To
facilitate this process, construct a small coding standard containing your queries. In a
folder SemanticRule:
Initialize a new user-defined coding standard by running this command:
polyspace-query-language init
Create a new folder
Naming_Conventionfor the user-defined defect.Copy the predicates rule definition to individual
pqlfiles:In the file
main.pql, add this code to insert the ruletestrulein a section:package main catalog TestStandard = { #[Description("Test Section")] section TestSection = { Naming_Convention.testrule } }Add this code to a source file
example.cpp. You can run and test your defect using this file:This code is annotated with expected rule violations.
Build the defect in steps. As a sanity check, update the defect
TestDefectinso that it reports the name of all variables:Naming_Convention/test_defect.pqlpackage Naming_Convention defect TestDefect = when Cpp.Variable.is(&variable) and variable.name(&varname) raise "Variable detected \"{varname}\"" on variablePackage the standard into the file
TestStandard.pschkand run a Polyspace® Bug Finder™ analysis using it:Open the results in Polyspace Platform user interface and verify that all variable names are correctly reported. Note that unused variables are ignored by Bug Finder.polyspace-query-language package polyspace-bug-finder -sources example.cpp -checkers-activation-file TestStandard.pschk
Update the defect in
to check if the predicateNaming_Convention/test_defect.pqlgetFirstLetterbehaves correctly.Package the standard and run the Bug Finder analysis. Open the results in Polyspace Platform user interface and verify that the first letter of the variable names are correctly reported. No result is reported for the variablepackage Naming_Convention defect TestDefect = when Cpp.Variable.is(&variable) and getFirstLetter(variable, &letter) raise "First expected letter \"{letter}\"" on variablecount. This is a class static variable. The predicategetFirstLetterdoes not account for this case. Add the predicateisClassStatic()to address this case. Update the predicate into this:Naming_Convention/getFirstLetter.pqlAfter packaging the coding standard and running the analysis, Bug Finder reports that the expected first letter ofpackage Naming_Convention predicate getFirstLetter(Cpp.Variable.Variable variable, Lang.String &letter) { return (variable.isGloballyVisible() and Lang.createString( "g",&letter)) or ((variable.isFileStatic() or variable.isFunctionStaticLocal() or variable.isClassStatic()) and Lang.createString( "s", &letter)) // Now detecting class static variables or (variable.isFunctionNonStaticLocal() and Lang.createString("t", &letter)) }myclass::countiss, since this is a class static variable.Update the defect to check if the predicate
getSecondPartbehaves correctly. Update the defectTestDefet:Package the standard and run the Bug Finder analysis. In the Polyspace Platform user interface, verify that the reported second parts are correct.package Naming_Convention defect TestDefect = when Cpp.Variable.is(&variable) and getSecondPart(variable, &letter) raise "Second expected part \"{letter}\"" on variableUpdate the defect to check if the predicate
getThirdpartbehaves correctly.Package the standard and run the Bug Finder analysis. In the Polyspace Platform user interface, verify that the reported third parts are correct.package Naming_Convention defect TestDefect = when Cpp.Variable.is(&variable) and getThirdPart(variable, &letter) raise "Third expected part \"{letter}\"" on variableComplete the user-defined defect:
To contextualize the message, this defect extracts the actual prefix in variable names and reports it in the message.package Naming_Convention defect TestDefect = when Cpp.Variable.is(&variable) and isNotCorrectPrefix(variable, &prefix) and variable.name(&name) and Lang.String.substr(name, 0, 3, &actual) raise "Expected prefix \"{prefix}\", actual prefix \"{actual}\"" on variable }After packaging the coding standard and running a Bug Finder analysis, the variable names that are not compliant are reported as user-defined defects.
Test the coding standard to verify the expected violations are present.
The test passes with this output:polyspace-query-language test example.cpp
number of actual defects: 6 number of expected annotations: 13 (including 7 expected absence of defects). _______________________________________________ Checking expected defects with actuals... ----------------------------------------- _______________________________________________ Looking for unexpected defects... --------------------------------- _______________________________________________ Tests passed
