[C] Static code analysis

I’m only able to get the “linter” part of SonarCloud to work.

According to C Static Code Analysis Tool & Clean Code Programming Language it should be able to:

precise static code analysis - Deep static analysis of your code through symbolic execution, path sensitive analysis & cross-function/cross file analysis.

But I only get the same issues as when using SonarLint in VS Code.

I’ve created a small project with obvious errors it should be able to find, like divide-by-zero.

How do I set it up to do cross-function/cross file analysis?

Hi,

Welcome to the community!

While it varies by language, I believe all of the C rules run in SonarLint, so you wouldn’t normally see additional issues raised in SonarCloud.

To your specific example, C analysis stops once it hits a point in a file that would halt execution (because subsequent issues/problems will never be encountered at runtime).

Are there other issues further up the file that might trigger this?

 
Ann

No, I can’t see an issue it would stop on.

And it doesn’t even find the internal divide-by-zero (cross function analysis):

printf("i divided is %u\n", i / ReturnZero());

And I would like it to find that the external function also returns zero leading to a divide-by-zero (cross file analysis):

printf("i divived is %u\n", i / ExtReturnZero());

My file:

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "extfunc.h"

#define   BUFFERSIZE          300

uint8_t   buffer[BUFFERSIZE]  = { 0 };
uint16_t  wrIndex             = 0;

void f()
{
    char *p;
    *p = 0;
}


uint8_t ReturnZero(void)
{
  return 0;
}


void PutInBuffer(uint8_t byte)
{
  buffer[wrIndex++] = byte;
}

int intValue = 42;

void readstruct(void *dst)
{
  memcpy(dst, &intValue, sizeof(intValue));
}

void oobwrite(char *c)
{
  readstruct(c);
}


void main(void)
{
  for (uint16_t i = 0; i < 500; i++)
  {
    PutInBuffer(i);
    if (i == 300)
    {
      printf("i divived is %u\n", i / ExtReturnZero());
    }
    if (i == 400)
    {
      printf("i divided is %u\n", i / ReturnZero());
    }
  }

  char charValue = 0;
  oobwrite(&charValue);
}

SonarLint output:

[{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S988",
	"severity": 4,
	"message": "Remove this include of \"stdio.h\".",
	"source": "sonarlint",
	"startLineNumber": 2,
	"startColumn": 1,
	"endLineNumber": 2,
	"endColumn": 19
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:M23_388",
	"severity": 4,
	"message": "Make this global constant with static initialization or remove it by passing the value as a function argument.",
	"source": "sonarlint",
	"startLineNumber": 8,
	"startColumn": 1,
	"endLineNumber": 8,
	"endColumn": 38
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:M23_388",
	"severity": 4,
	"message": "Make this global constant with static initialization or remove it by passing the value as a function argument.",
	"source": "sonarlint",
	"startLineNumber": 9,
	"startColumn": 1,
	"endLineNumber": 9,
	"endColumn": 34
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S929",
	"severity": 4,
	"message": "Set this function's parameter list to \"void\".",
	"source": "sonarlint",
	"startLineNumber": 11,
	"startColumn": 6,
	"endLineNumber": 11,
	"endColumn": 7
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:M23_321",
	"severity": 4,
	"message": "Local variables should be initialized immediately.",
	"source": "sonarlint",
	"startLineNumber": 13,
	"startColumn": 11,
	"endLineNumber": 13,
	"endColumn": 12
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S836",
	"severity": 4,
	"message": "Dereference of undefined pointer value (loaded from variable 'p') [+2 locations]",
	"source": "sonarlint",
	"startLineNumber": 14,
	"startColumn": 8,
	"endLineNumber": 14,
	"endColumn": 9
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:M23_152",
	"severity": 4,
	"message": "Dereference of undefined pointer value (loaded from variable 'p') [+2 locations]",
	"source": "sonarlint",
	"startLineNumber": 14,
	"startColumn": 8,
	"endLineNumber": 14,
	"endColumn": 9
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S100",
	"severity": 4,
	"message": "Rename this function to match the regular expression: ^[a-z][a-zA-Z0-9]*$",
	"source": "sonarlint",
	"startLineNumber": 18,
	"startColumn": 9,
	"endLineNumber": 18,
	"endColumn": 19
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S100",
	"severity": 4,
	"message": "Rename this function to match the regular expression: ^[a-z][a-zA-Z0-9]*$",
	"source": "sonarlint",
	"startLineNumber": 24,
	"startColumn": 6,
	"endLineNumber": 24,
	"endColumn": 17
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:M23_058",
	"severity": 4,
	"message": "Replace this builtin type with an alias that makes the type size explicit",
	"source": "sonarlint",
	"startLineNumber": 29,
	"startColumn": 1,
	"endLineNumber": 29,
	"endColumn": 4
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S813",
	"severity": 4,
	"message": "Replace this use of \"int\" with a typedef.",
	"source": "sonarlint",
	"startLineNumber": 29,
	"startColumn": 1,
	"endLineNumber": 29,
	"endColumn": 4
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:M23_388",
	"severity": 4,
	"message": "Make this global constant with static initialization or remove it by passing the value as a function argument.",
	"source": "sonarlint",
	"startLineNumber": 29,
	"startColumn": 1,
	"endLineNumber": 29,
	"endColumn": 18
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:M23_007",
	"severity": 4,
	"message": "Use the value returned from \"memcpy\".",
	"source": "sonarlint",
	"startLineNumber": 33,
	"startColumn": 3,
	"endLineNumber": 33,
	"endColumn": 9
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S109",
	"severity": 4,
	"message": "Assign this magic number 500 to a well-named variable, and use the variable instead.",
	"source": "sonarlint",
	"startLineNumber": 44,
	"startColumn": 28,
	"endLineNumber": 44,
	"endColumn": 31
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S1705",
	"severity": 4,
	"message": "Use prefix increment.",
	"source": "sonarlint",
	"startLineNumber": 44,
	"startColumn": 34,
	"endLineNumber": 44,
	"endColumn": 36
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S5276",
	"severity": 4,
	"message": "implicit conversion loses integer precision: 'uint16_t' (aka 'unsigned short') to 'uint8_t' (aka 'unsigned char')",
	"source": "sonarlint",
	"startLineNumber": 46,
	"startColumn": 17,
	"endLineNumber": 46,
	"endColumn": 18
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S1772",
	"severity": 4,
	"message": "Flip this test to put the right operand on the left-hand side of \"==\".",
	"source": "sonarlint",
	"startLineNumber": 47,
	"startColumn": 9,
	"endLineNumber": 47,
	"endColumn": 17
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S109",
	"severity": 4,
	"message": "Assign this magic number 300 to a well-named variable, and use the variable instead.",
	"source": "sonarlint",
	"startLineNumber": 47,
	"startColumn": 14,
	"endLineNumber": 47,
	"endColumn": 17
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:M23_007",
	"severity": 4,
	"message": "Use the value returned from \"printf\".",
	"source": "sonarlint",
	"startLineNumber": 49,
	"startColumn": 7,
	"endLineNumber": 49,
	"endColumn": 13
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S1772",
	"severity": 4,
	"message": "Flip this test to put the right operand on the left-hand side of \"==\".",
	"source": "sonarlint",
	"startLineNumber": 51,
	"startColumn": 9,
	"endLineNumber": 51,
	"endColumn": 17
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:S109",
	"severity": 4,
	"message": "Assign this magic number 400 to a well-named variable, and use the variable instead.",
	"source": "sonarlint",
	"startLineNumber": 51,
	"startColumn": 14,
	"endLineNumber": 51,
	"endColumn": 17
},{
	"resource": "/home/tfo/work/static_code_test/src/main.c",
	"owner": "sonarlint",
	"code": "c:M23_007",
	"severity": 4,
	"message": "Use the value returned from \"printf\".",
	"source": "sonarlint",
	"startLineNumber": 53,
	"startColumn": 7,
	"endLineNumber": 53,
	"endColumn": 13
}]

Hello @thomasfogh

You will not see any bug on SonarCloud either.

You do not see any bug pop-up here because of what we call loop unrolling.
It is a common shortcoming of many (if not all) analyzers. Loops are unrolled up to a certain point and then, the analysis moves on.
I adapted your example to show you how such problems can be detected for small values of i.
I removed the ExtReturnZero. Trust my word, with the same adaptation, it would work.

  • On this first adaptation, the example first hit the buffer overflow, and the analysis stops (as an execution would most likely stop or would be in an undefined state).
    Compiler Explorer

  • In this second example (just changed an i ==1 to i == 10), the division by 0 is hit first, and then, the analysis stops.
    Compiler Explorer

I hope it helps.

1 Like

Thanks, Geoffray

I guess that makes sense (when you know it).
However I still can’t get it to detect the external divide-by-zero (cross file analysis).

void main(void)
{
  uint16_t n = 500 / ExtReturnZero();

  for (uint16_t i = 0; i < n; i++)
  {
    if(i == 1) {
      wrIndex = i+299;
      PutInBuffer(0);
    }
    if (i == 2)
    {
      printf("i divided is %u\n", i / ReturnZero());
    }
  }
}

extfunc.c:

#include <stdint.h>

__attribute__ ((noinline)) uint8_t ExtReturnZero(void)
{
  return 0;
}

Hi @thomasfogh

It will work if your function is defined in the extfunc.h file.

This is a limitation we currently have. We are investigating fixing it.
We analyze translation units (.c, .cpp, .cc, etc …) one by one. For each of these analyses, we take into account, the content found in the header files involved with this translation unit (recursively included files).
In your case the analysis of main.c cannot see the body of your function ExtReturnZero defined in extfunc.c.
It would see it if you put the body inside extfunc.h

I hope it clarifies.

The function is declared in extfunc.h:

#include <stdint.h>

uint8_t ExtReturnZero(void);

Is this not enough?

No, unfortunately, it is not enough.
The return value (0) occurs in the function’s body. Without using the information in the extfunc.c file, you cannot detect the division by 0.

Then I will assert that you currently don’t support cross-file analysis for C…

A more accurate version of this statement:
Our symbolic execution engine does not currently support cross-translation unit analysis. Still, it can cross file boundaries as it goes through all the headers that are used in a single translation unit.

It affects all the C (and C++) rules that are labeled as symbolic-execution.

We also have rules that support cross-translation unit detection with other engines. For example, Unused functions and Methods should be removed.

As you see, the situation is a bit complex, and summarizing it in one sentence on the website you mentioned is difficult and doomed to be inaccurate. Still, we are open about its details, as you can see here and on other posts about it.

1 Like