Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[report-converter] Smatch Parser #2968

Merged
merged 5 commits into from
Oct 19, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ The following tools are supported:
| | [Cppcheck](/tools/report-converter/README.md#cppcheck) |
| | [Facebook Infer](/tools/report-converter/README.md#facebook-infer) |
| | [Coccinelle](/tools/report-converter/README.md#coccinelle) |
| | [Smatch](/tools/report-converter/README.md#smatch) |
| **Java** | [SpotBugs](/tools/report-converter/README.md#spotbugs) |
| | [Facebook Infer](/tools/report-converter/README.md#fbinfer) |
| **Python** | [Pylint](/tools/report-converter/README.md#pylint) |
Expand Down
1 change: 1 addition & 0 deletions docs/supported_code_analyzers.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ CodeChecker result directory which can be stored to a CodeChecker server.
| | [Cppcheck](/tools/report-converter/README.md#cppcheck) | ✓ |
| | [Facebook Infer](/tools/report-converter/README.md#fbinfer) | ✓ |
| | [Coccinelle](/tools/report-converter/README.md#coccinelle) | ✓ |
| | [Smatch](/tools/report-converter/README.md#smatch) | ✓ |
| **Java** | [FindBugs](http://findbugs.sourceforge.net/) | ✗ |
| | [SpotBugs](/tools/report-converter/README.md#spotbugs) | ✓ |
| | [Facebook Infer](/tools/report-converter/README.md#fbinfer) | ✓ |
Expand Down
27 changes: 27 additions & 0 deletions tools/report-converter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ a CodeChecker server.
* [Pyflakes](#pyflakes)
* [Markdownlint](#markdownlint)
* [Coccinelle](#coccinelle)
* [Smatch](#smatch)
jay24rajput marked this conversation as resolved.
Show resolved Hide resolved
* [License](#license)

## Install guide
Expand Down Expand Up @@ -80,6 +81,7 @@ optional arguments:
Supported analyzers:
asan - AddressSanitizer, https://clang.llvm.org/docs/AddressSanitizer.html
clang-tidy - Clang Tidy, https://clang.llvm.org/extra/clang-tidy
coccinelle - Coccinelle, https://github.com/coccinelle/coccinelle
cppcheck - Cppcheck, http://cppcheck.sourceforge.net
eslint - ESLint, https://eslint.org/
fbinfer - Facebook Infer, https://fbinfer.com
Expand All @@ -88,6 +90,7 @@ Supported analyzers:
msan - MemorySanitizer, https://clang.llvm.org/docs/MemorySanitizer.html
pyflakes - Pyflakes, https://github.com/PyCQA/pyflakes
pylint - Pylint, https://www.pylint.org
smatch - smatch, https://repo.or.cz/w/smatch.git
spotbugs - spotbugs, https://spotbugs.github.io
tsan - ThreadSanitizer, https://clang.llvm.org/docs/ThreadSanitizer.html
tslint - TSLint, https://palantir.github.io/tslint
Expand Down Expand Up @@ -385,6 +388,7 @@ report-converter -t mdl -o ./codechecker_mdl_reports ./mdl_reports.out

# Store Markdownlint reports with CodeChecker.
CodeChecker store ./codechecker_mdl_reports -n mdl
```

## [Coccinelle](https://github.com/coccinelle/coccinelle)
[Coccinelle](https://github.com/coccinelle/coccinelle) allows programmers to easily
Expand All @@ -409,7 +413,30 @@ report-converter -t coccinelle -o ./codechecker_coccinelle_reports ./coccinelle_

# Store the Cocccinelle reports with CodeChecker.
CodeChecker store ./codechecker_coccinelle_reports -n coccinelle
```

## [Smatch](https://repo.or.cz/w/smatch.git)
[Smatch](https://repo.or.cz/w/smatch.git) is a static analysis tool for C that is used on the kernel.

The recommended way of running Smatch is to redirect the output to a file and
give this file to the report converter tool.

The following example shows you how to run Smatch on kernel sources
and store the results found by Smatch to the CodeChecker database.
```sh
# Change Directory to your project
cd path/to/linux/kernel/repository

# Run Smatch
# Note: The warnings will be stored by default into smatch_warns.txt after executing the following command
path/to/smatch/smatch_scripts/test_kernel.sh
jay24rajput marked this conversation as resolved.
Show resolved Hide resolved

# Use 'report-converter' to create a CodeChecker report directory from the
# analyzer result of Smatch
report-converter -t smatch -o ./codechecker_smatch_reports ./smatch_warns.txt

# Store the Smatch reports with CodeChecker.
CodeChecker store ./codechecker_smatch_reports -n smatch
```

## License
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
MarkdownlintAnalyzerResult # noqa
from codechecker_report_converter.coccinelle.analyzer_result import \
CoccinelleAnalyzerResult # noqa
from codechecker_report_converter.smatch.analyzer_result import \
SmatchAnalyzerResult # noqa


LOG = logging.getLogger('ReportConverter')
Expand Down Expand Up @@ -89,7 +91,8 @@ class RawDescriptionDefaultHelpFormatter(
UBSANAnalyzerResult.TOOL_NAME: UBSANAnalyzerResult,
SpotBugsAnalyzerResult.TOOL_NAME: SpotBugsAnalyzerResult,
MarkdownlintAnalyzerResult.TOOL_NAME: MarkdownlintAnalyzerResult,
CoccinelleAnalyzerResult.TOOL_NAME: CoccinelleAnalyzerResult
CoccinelleAnalyzerResult.TOOL_NAME: CoccinelleAnalyzerResult,
SmatchAnalyzerResult.TOOL_NAME: SmatchAnalyzerResult
}

supported_metadata_keys = ["analyzer_command", "analyzer_version"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------

from codechecker_report_converter.analyzer_result import AnalyzerResult

from .output_parser import SmatchParser
from ..plist_converter import PlistConverter


class SmatchAnalyzerResult(AnalyzerResult):
""" Transform analyzer result of Smatch. """

TOOL_NAME = 'smatch'
NAME = 'Smatch'
URL = 'https://repo.or.cz/w/smatch.git'

def parse(self, analyzer_result):
""" Creates plist files from the given analyzer result to the given
output directory.
"""
parser = SmatchParser(analyzer_result)

content = self._get_analyzer_result_file_content(analyzer_result)
if not content:
return

messages = parser.parse_messages(content)

plist_converter = PlistConverter(self.TOOL_NAME)
plist_converter.add_messages(messages)
return plist_converter.get_plist_results()
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------

import logging
import os
import re

from ..output_parser import BaseParser, Message
LOG = logging.getLogger('ReportConverter')


class SmatchParser(BaseParser):
"""
Parser for Smatch Output
"""

def __init__(self, analyzer_result):
super(SmatchParser, self).__init__()

self.analyzer_result = analyzer_result

self.message_line_re = re.compile(
# File path followed by a ':'.
r'^(?P<path>[\S ]+?):'
# Line number followed by a whitespace.
r'(?P<line>\d+?)\s'
# Message.
r'(?P<message>[\S \t]+)\s*')

def parse_message(self, it, line):
"""
Actual Parsing function for the given line
jay24rajput marked this conversation as resolved.
Show resolved Hide resolved
It is expected that each line contains a seperate report
"""
match = self.message_line_re.match(line)
if match is None:
return None, next(it)

file_path = os.path.normpath(
os.path.join(os.path.dirname(self.analyzer_result),
match.group('path')))

checker_name = None
column = 0

message = Message(
file_path,
int(match.group('line')),
column,
match.group('message').strip(),
checker_name)

try:
return message, next(it)
except StopIteration:
return message, ''
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
static struct parse_smatch *check_sample() {
int res;
res = 0;
return ERR_PTR(res);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>diagnostics</key>
<array>
<dict>
<key>category</key>
<string>unknown</string>
<key>check_name</key>
<string>smatch</string>
jay24rajput marked this conversation as resolved.
Show resolved Hide resolved
<key>description</key>
<string>check_sample() warn: passing zero to 'ERR_PTR'</string>
<key>issue_hash_content_of_line_in_context</key>
<string>4b7ca471f46a9a22d294b3e6e3676131</string>
<key>location</key>
<dict>
<key>col</key>
<integer>0</integer>
<key>file</key>
<integer>0</integer>
<key>line</key>
<integer>4</integer>
</dict>
<key>path</key>
<array>
<dict>
<key>depth</key>
<integer>0</integer>
<key>kind</key>
<string>event</string>
<key>location</key>
<dict>
<key>col</key>
<integer>0</integer>
<key>file</key>
<integer>0</integer>
<key>line</key>
<integer>4</integer>
</dict>
<key>message</key>
<string>check_sample() warn: passing zero to 'ERR_PTR'</string>
</dict>
</array>
<key>type</key>
<string>smatch</string>
</dict>
</array>
<key>files</key>
<array>
<string>files/sample.c</string>
</array>
<key>metadata</key>
<dict>
<key>analyzer</key>
<dict>
<key>name</key>
<string>smatch</string>
</dict>
<key>generated_by</key>
<dict>
<key>name</key>
<string>report-converter</string>
<key>version</key>
<string>x.y.z</string>
</dict>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
files/sample.c:4 check_sample() warn: passing zero to 'ERR_PTR'
79 changes: 79 additions & 0 deletions tools/report-converter/tests/unit/test_smatch_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# -------------------------------------------------------------------------
#
# Part of the CodeChecker project, under the Apache License v2.0 with
# LLVM Exceptions. See LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# -------------------------------------------------------------------------

"""
This module tests the correctness of the SmatchAnalyzerResult, which
used in sequence transform Smatch output to a plist file.
"""


import os
import plistlib
import shutil
import tempfile
import unittest


from codechecker_report_converter.smatch.analyzer_result import \
SmatchAnalyzerResult


class SmatchAnalyzerResultTestCase(unittest.TestCase):
""" Test the output of the SmatchAnalyzerResult. """

def setUp(self):
""" Setup the test. """
self.analyzer_result = SmatchAnalyzerResult()
self.cc_result_dir = tempfile.mkdtemp()
self.test_files = os.path.join(os.path.dirname(__file__),
'smatch_output_test_files')

def tearDown(self):
""" Clean temporary directory. """
shutil.rmtree(self.cc_result_dir)

def test_no_smatch_output_file(self):
""" Test transforming single C file. """
analyzer_result = os.path.join(self.test_files, 'files',
'sample.c')

ret = self.analyzer_result.transform(analyzer_result,
self.cc_result_dir)
self.assertFalse(ret)

def test_transform_dir(self):
""" Test transforming a directory. """
analyzer_result = os.path.join(self.test_files)

ret = self.analyzer_result.transform(analyzer_result,
self.cc_result_dir)
self.assertFalse(ret)

def test_transform_single_file(self):
""" Test transforming single output file. """
analyzer_result = os.path.join(self.test_files, 'sample.out')
self.analyzer_result.transform(analyzer_result, self.cc_result_dir)

plist_file = os.path.join(self.cc_result_dir,
'sample.c_smatch.plist')

with open(plist_file, mode='rb') as pfile:
res = plistlib.load(pfile)

# Use relative path for this test.
res['files'][0] = os.path.join('files', 'sample.c')

self.assertTrue(res['metadata']['generated_by']['version'])
res['metadata']['generated_by']['version'] = "x.y.z"
jay24rajput marked this conversation as resolved.
Show resolved Hide resolved

plist_file = os.path.join(self.test_files,
'sample.expected.plist')
with open(plist_file, mode='rb') as pfile:
exp = plistlib.load(pfile)

self.assertEqual(res, exp)