Skip to content

Commit

Permalink
Merge pull request #2968 from jay24rajput/smatch_parser
Browse files Browse the repository at this point in the history
[report-converter] Smatch Parser
  • Loading branch information
csordasmarton authored Oct 19, 2020
2 parents 9798bd2 + 5538d6d commit 69b9f14
Show file tree
Hide file tree
Showing 11 changed files with 293 additions and 1 deletion.
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
26 changes: 26 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)
* [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 @@ -412,7 +415,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

# 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
5 changes: 4 additions & 1 deletion tools/report-converter/codechecker_report_converter/cli.py
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,64 @@
# -------------------------------------------------------------------------
#
# 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'
# Function name followed by a whitespace.
r'(?P<function_name>\S+)\s'
# Checker name followed by a whitespace
r'\[smatch\.(?P<checker_name>\S+)\]\s'
# Message.
r'(?P<message>[\S \t]+)\s*')

def parse_message(self, it, line):
"""
Actual Parsing function for the given line
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')))

column = 0

message = Message(
file_path,
int(match.group('line')),
column,
match.group('message').strip(),
match.group('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>check_zero_to_err_ptr</string>
<key>description</key>
<string>warn: passing zero to 'ERR_PTR'</string>
<key>issue_hash_content_of_line_in_context</key>
<string>2d8b9711e5b334b518d201a8a6799fdb</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>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() [smatch.check_zero_to_err_ptr] 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"

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)

0 comments on commit 69b9f14

Please sign in to comment.