Repository: ericsson/codechecker
Issue: AttributeError: 'str' object has no attribute 'write'

Hi,

During upload to the 'CodeChecker server' instance, I got the following error (several times):

[WARNING 2020-12-21 15:26] - Check name wasn't found in the plist file '..._template.c_clangsa_6662832d7e50ead8bd22348d2e979cc4.plist'
. 
[WARNING 2020-12-21 15:26] - Check name wasn't found in the plist file '..._template.c_clangsa_6662832d7e50ead8bd22348d2e979cc4.plist'
. 
[WARNING 2020-12-21 15:26] - Check name wasn't found in the plist file '..._template.c_clangsa_6662832d7e50ead8bd22348d2e979cc4.plist'
. 
[WARNING 2020-12-21 15:26] - Check name wasn't found in the plist file '..._template.c_clangsa_6662832d7e50ead8bd22348d2e979cc4.plist'
. 
[WARNING 2020-12-21 15:26] - Error during processing reports from the plist file: ..._template.c_clangsa_6662832d7e50ead8bd22348d2e979cc4.plist
Traceback (most recent call last):
  File "/home/fhe/codechecker/build/CodeChecker/lib/python3/codechecker_common/plist_parser.py", line 216, in parse_plist_file
    plistlib.dump(plist, path)
  File "/usr/lib64/python3.6/plistlib.py", line 1034, in dump
    writer = _FORMATS[fmt]["writer"](fp, sort_keys=sort_keys, skipkeys=skipkeys)
  File "/usr/lib64/python3.6/plistlib.py", line 460, in __init__
    file.write(PLISTHEADER)
AttributeError: 'str' object has no attribute 'write'
[WARNING 2020-12-21 15:26] - <class 'AttributeError'>
[WARNING 2020-12-21 15:26] - 'str' object has no attribute 'write'



Note: also the CodeChecker-server itself logged this error:

[WARNING 2020-12-21 15:26] - Check name wasn't found in the plist file '/tmp/tmpph08v29q/reports/..._clangsa_456aff074dbf28fd35287ef098df8265.plist'.
[WARNING 2020-12-21 15:26] - Check name wasn't found in the plist file '/tmp/tmpph08v29q/reports/..._clangsa_456aff074dbf28fd35287ef098df8265.plist'.
[WARNING 2020-12-21 15:26] - Error during processing reports from the plist file: /tmp/tmpph08v29q/reports/..._clangsa_456aff074dbf28fd35287ef098df8265.plist
Traceback (most recent call last):
  File "/home/fhe/codechecker/build/CodeChecker/lib/python3/codechecker_common/plist_parser.py", line 216, in parse_plist_file
    plistlib.dump(plist, path)
  File "/usr/lib64/python3.6/plistlib.py", line 1034, in dump
    writer = _FORMATS[fmt]["writer"](fp, sort_keys=sort_keys, skipkeys=skipkeys)
  File "/usr/lib64/python3.6/plistlib.py", line 460, in __init__
    file.write(PLISTHEADER)
AttributeError: 'str' object has no attribute 'write'
[WARNING 2020-12-21 15:26] - <class 'AttributeError'>
[WARNING 2020-12-21 15:26] - 'str' object has no attribute 'write'
[INFO 2020-12-21 15:26] - 'Anonymous' stored results (8441 KB) to run 'my-project' in 9.59 seconds.
[INFO 2020-12-21 15:26] - 127.0.0.1:36516 -- [Anonymous] POST /Default/v6.37/CodeCheckerService


Changed files:
web/server/tests/unit/test_store_handler.py, web/client/codechecker_client/cmd_line_client.py, codechecker_common/plist_parser.py, web/client/tests/unit/test_report_converter.py, web/server/tests/unit/test_report_path_hash.py, codechecker_common/report.py, web/client/codechecker_client/report_type_converter.py, codechecker_common/output/codeclimate.py, web/server/tests/unit/test_plist_parser.py, web/server/codechecker_server/api/report_server.py

diff --git a/codechecker_common/output/codeclimate.py b/codechecker_common/output/codeclimate.py
index 6abcb6df..14b0b5f3 100644
--- a/codechecker_common/output/codeclimate.py
+++ b/codechecker_common/output/codeclimate.py
@@ -29,8 +29,7 @@ def convert(reports: List[Report]) -> Dict:
 
 def __to_codeclimate(report: Report) -> Dict:
     """Convert a Report to Code Climate format."""
-    location = report.main['location']
-    _, file_name = os.path.split(location['file'])
+    _, file_name = os.path.split(report.file_path)
 
     return {
         "type": "issue",
@@ -41,7 +40,7 @@ def __to_codeclimate(report: Report) -> Dict:
         "location": {
             "path": file_name,
             "lines": {
-                "begin": location['line']
+                "begin": report.main['location']['line']
             }
         }
     }
diff --git a/codechecker_common/plist_parser.py b/codechecker_common/plist_parser.py
index 2aa3b030..0956296c 100644
--- a/codechecker_common/plist_parser.py
+++ b/codechecker_common/plist_parser.py
@@ -32,11 +32,10 @@ With the newer clang releases more information is available in the plist files.
 
 """
 import importlib
-import os
 import sys
 import traceback
 import plistlib
-from typing import List, Dict, Union, Tuple
+from typing import List, Dict, Tuple
 from xml.parsers.expat import ExpatError
 
 from codechecker_common.logger import get_logger
@@ -142,13 +141,13 @@ def get_checker_name(diagnostic, path=""):
     """
     checker_name = diagnostic.get('check_name')
     if not checker_name:
-        LOG.warning("Check name wasn't found in the plist file '%s'. ", path)
+        LOG.warning("Check name wasn't found in the plist file '%s'. It is "
+                    "available since 'Clang v3.7'.", path)
         checker_name = "unknown"
     return checker_name
 
 
 def parse_plist_file(path: str,
-                     source_root: Union[str, None] = None,
                      allow_plist_update=True) \\
                              -> Tuple[Dict[int, str], List[Report]]:
     """
@@ -191,15 +190,11 @@ def parse_plist_file(path: str,
             # by older clang version (before 3.7).
             main_section['check_name'] = get_checker_name(diag, path)
 
-            # We need to extend information for plist files generated
-            # by older clang version (before 3.8).
-            file_path = mentioned_files[diag['location']['file']]
-            if source_root:
-                file_path = os.path.join(source_root, file_path.lstrip('/'))
-            main_section['location']['file'] = file_path
             report_hash = diag.get('issue_hash_content_of_line_in_context')
 
             if not report_hash:
+                file_path = mentioned_files[diag['location']['file']]
+
                 # Generate hash value if it is missing from the report.
                 report_hash = get_report_hash(diag, file_path,
                                               HashType.PATH_SENSITIVE)
@@ -223,7 +218,8 @@ def parse_plist_file(path: str,
             # If the diagnostic section has changed we update the plist file.
             # This way the client will always send a plist file where the
             # report hash field is filled.
-            plistlib.dump(plist, path)
+            with open(path, 'wb') as plist_file:
+                plistlib.dump(plist, plist_file)
     except IndexError as iex:
         LOG.warning('Indexing error during processing plist file %s', path)
         LOG.warning(type(iex))
diff --git a/codechecker_common/report.py b/codechecker_common/report.py
index 83623679..7dde9a10 100644
--- a/codechecker_common/report.py
+++ b/codechecker_common/report.py
@@ -93,7 +93,7 @@ class Report(object):
     @property
     def file_path(self) -> str:
         """ Get the filepath for the main report location. """
-        return self.__main['location']['file']
+        return self.files[self.__main['location']['file']]
 
     @property
     def source_line(self) -> str:
@@ -103,8 +103,8 @@ class Report(object):
         if not tries to read it from the disk.
         """
         if not self.__source_line:
-            self.__source_line = \\
-                util.get_line(self.__main['location']['file'], self.line)
+            self.__source_line = util.get_line(self.file_path, self.line)
+
         return self.__source_line
 
     @source_line.setter
@@ -124,9 +124,6 @@ class Report(object):
         """ Removes the longest matching leading path from the file paths. """
         self.__files = {i: util.trim_path_prefixes(file_path, path_prefixes)
                         for i, file_path in self.__files.items()}
-        self.__main['location']['file'] = \\
-            util.trim_path_prefixes(self.__main['location']['file'],
-                                    path_prefixes)
 
     def to_json(self):
         """Converts to a special json format.
diff --git a/web/client/codechecker_client/cmd_line_client.py b/web/client/codechecker_client/cmd_line_client.py
index 604324ad..eb2a186f 100644
--- a/web/client/codechecker_client/cmd_line_client.py
+++ b/web/client/codechecker_client/cmd_line_client.py
@@ -163,7 +163,7 @@ def reports_to_html_report_data(reports: List[Report]) -> Dict:
             'events': events,
             'macros': macros,
             'notes': notes,
-            'path': report.main['location']['file'],
+            'path': report.file_path,
             'reportHash': report_hash,
             'checkerName': report.main['check_name']})
 
@@ -225,7 +225,7 @@ def get_suppressed_reports(reports: List[Report]) -> List[str]:
 
     for rep in reports:
         bughash = rep.report_hash
-        source_file = rep.main['location']['file']
+        source_file = rep.file_path
         bug_line = rep.main['location']['line']
         checker_name = rep.main['check_name']
         src_comment_data = []
@@ -1094,7 +1094,7 @@ def handle_diff_results(args):
         file_report_map = defaultdict(list)
         for report in reports:
             if isinstance(report, Report):
-                file_path = report.main['location']['file']
+                file_path = report.file_path
 
                 check_name = report.main['check_name']
                 sev = context.severity_map.get(check_name)
diff --git a/web/client/codechecker_client/report_type_converter.py b/web/client/codechecker_client/report_type_converter.py
index 1f33e58b..0f841851 100644
--- a/web/client/codechecker_client/report_type_converter.py
+++ b/web/client/codechecker_client/report_type_converter.py
@@ -23,7 +23,7 @@ def reportData_to_report(report_data: ReportData) -> Report:
         "location": {
             "line": report_data.line,
             "col": report_data.column,
-            "file": report_data.checkedFile,
+            "file": 0,
         },
     }
     bug_path = None
@@ -49,7 +49,7 @@ def report_to_reportData(report: Report,
     return ReportData(
         checkerId=checker_name,
         bugHash=report_hash,
-        checkedFile=report.main["location"]["file"],
+        checkedFile=report.file_path,
         checkerMsg=report.main["description"],
         line=report.main["location"]["line"],
         column=report.main["location"]["col"],
diff --git a/web/client/tests/unit/test_report_converter.py b/web/client/tests/unit/test_report_converter.py
index 6582c16f..abde1a25 100644
--- a/web/client/tests/unit/test_report_converter.py
+++ b/web/client/tests/unit/test_report_converter.py
@@ -33,10 +33,14 @@ class ReportTypeConverterTest(unittest.TestCase):
             "description": description,
             "check_name": check_name,
             "issue_hash_content_of_line_in_context": report_hash,
-            "location": {"line": line, "col": column, "file": source_file},
+            "location": {"line": line, "col": column, "file": 0},
         }
 
-        rep = report.Report(main=main, bugpath=[], files={}, metadata=None)
+        rep = report.Report(main=main,
+                            bugpath=[],
+                            files={0: source_file},
+                            metadata=None)
+
         severity_map = {check_name: "LOW"}
         rep_data = report_type_converter.report_to_reportData(rep, severity_map)
 
diff --git a/web/server/codechecker_server/api/report_server.py b/web/server/codechecker_server/api/report_server.py
index 5f092d46..58e12711 100644
--- a/web/server/codechecker_server/api/report_server.py
+++ b/web/server/codechecker_server/api/report_server.py
@@ -2724,7 +2724,7 @@ class ThriftRequestHandler(object):
 
             try:
                 files, reports = plist_parser.parse_plist_file(
-                    os.path.join(report_dir, f), None)
+                    os.path.join(report_dir, f))
             except Exception as ex:
                 LOG.error('Parsing the plist failed: %s', str(ex))
                 continue
diff --git a/web/server/tests/unit/test_plist_parser.py b/web/server/tests/unit/test_plist_parser.py
index 02660e6e..a58dc627 100644
--- a/web/server/tests/unit/test_plist_parser.py
+++ b/web/server/tests/unit/test_plist_parser.py
@@ -237,7 +237,6 @@ class PlistParserTestCaseNose(unittest.TestCase):
         """Plist file is empty."""
         empty_plist = os.path.join(self.__plist_test_files, 'empty_file')
         files, reports = plist_parser.parse_plist_file(empty_plist,
-                                                       None,
                                                        False)
         self.assertEqual(files, [])
         self.assertEqual(reports, [])
@@ -247,7 +246,6 @@ class PlistParserTestCaseNose(unittest.TestCase):
         no_bug_plist = os.path.join(
             self.__plist_test_files, 'clang-3.7-noerror.plist')
         files, reports = plist_parser.parse_plist_file(no_bug_plist,
-                                                       None,
                                                        False)
         self.assertEqual(files, [])
         self.assertEqual(reports, [])
@@ -260,7 +258,6 @@ class PlistParserTestCaseNose(unittest.TestCase):
         clang37_plist = os.path.join(
             self.__plist_test_files, 'clang-3.7.plist')
         files, reports = plist_parser.parse_plist_file(clang37_plist,
-                                                       None,
                                                        False)
 
         self.assertEqual(files, self.__found_file_names)
@@ -276,7 +273,6 @@ class PlistParserTestCaseNose(unittest.TestCase):
         clang38_plist = os.path.join(
             self.__plist_test_files, 'clang-3.8-trunk.plist')
         files, reports = plist_parser.parse_plist_file(clang38_plist,
-                                                       None,
                                                        False)
 
         self.assertEqual(files, self.__found_file_names)
@@ -306,7 +302,6 @@ class PlistParserTestCaseNose(unittest.TestCase):
         clang40_plist = os.path.join(
             self.__plist_test_files, 'clang-4.0.plist')
         files, reports = plist_parser.parse_plist_file(clang40_plist,
-                                                       None,
                                                        False)
 
         self.assertEqual(files, self.__found_file_names)
@@ -338,7 +333,6 @@ class PlistParserTestCaseNose(unittest.TestCase):
         clang50_trunk_plist = os.path.join(
             self.__plist_test_files, 'clang-5.0-trunk.plist')
         files, reports = plist_parser.parse_plist_file(clang50_trunk_plist,
-                                                       None,
                                                        False)
         self.assertEqual(files, self.__found_file_names)
         self.assertEqual(len(reports), 3)
diff --git a/web/server/tests/unit/test_report_path_hash.py b/web/server/tests/unit/test_report_path_hash.py
index ae71cea3..d518ff53 100644
--- a/web/server/tests/unit/test_report_path_hash.py
+++ b/web/server/tests/unit/test_report_path_hash.py
@@ -34,7 +34,6 @@ class ReportPathHashHandler(unittest.TestCase):
         clang50_trunk_plist = os.path.join(
             self.__plist_test_files, 'clang-5.0-trunk.plist')
         files, reports = plist_parser.parse_plist_file(clang50_trunk_plist,
-                                                       None,
                                                        False)
         self.assertEqual(len(reports), 3)
 
diff --git a/web/server/tests/unit/test_store_handler.py b/web/server/tests/unit/test_store_handler.py
index da0441be..7703b266 100644
--- a/web/server/tests/unit/test_store_handler.py
+++ b/web/server/tests/unit/test_store_handler.py
@@ -37,7 +37,6 @@ class StoreHandler(unittest.TestCase):
         clang50_trunk_plist = os.path.join(
             self.__plist_test_files, 'clang-5.0-trunk.plist')
         files, reports = plist_parser.parse_plist_file(clang50_trunk_plist,
-                                                       None,
                                                        False)
         self.assertEqual(len(reports), 3)