Update clang_check_attributes to take into account MT_DISABLED (#4479)
This commit is contained in:
parent
9fe459c820
commit
ec2e3ec0e4
|
|
@ -10,23 +10,37 @@ import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import shlex
|
import shlex
|
||||||
from typing import Callable, Iterable, Optional, Union
|
from typing import Callable, Iterable, Optional, Union, TYPE_CHECKING
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import enum
|
import enum
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import clang.cindex
|
import clang.cindex
|
||||||
from clang.cindex import (
|
from clang.cindex import (
|
||||||
CursorKind,
|
|
||||||
Index,
|
Index,
|
||||||
TranslationUnitSaveError,
|
TranslationUnitSaveError,
|
||||||
TranslationUnitLoadError,
|
TranslationUnitLoadError,
|
||||||
CompilationDatabase,
|
CompilationDatabase,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not TYPE_CHECKING:
|
||||||
|
from clang.cindex import CursorKind
|
||||||
|
else:
|
||||||
|
# Workaround for missing support for members defined out-of-class in Pylance:
|
||||||
|
# https://github.com/microsoft/pylance-release/issues/2365#issuecomment-1035803067
|
||||||
|
|
||||||
|
class CursorKindMeta(type):
|
||||||
|
|
||||||
|
def __getattr__(cls, name: str) -> clang.cindex.CursorKind:
|
||||||
|
return getattr(clang.cindex.CursorKind, name)
|
||||||
|
|
||||||
|
class CursorKind(clang.cindex.CursorKind, metaclass=CursorKindMeta):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def fully_qualified_name(node):
|
def fully_qualified_name(node):
|
||||||
if node is None:
|
if node is None:
|
||||||
|
|
@ -66,6 +80,7 @@ class VlAnnotations:
|
||||||
stable_tree: bool = False
|
stable_tree: bool = False
|
||||||
mt_safe_postinit: bool = False
|
mt_safe_postinit: bool = False
|
||||||
mt_unsafe: bool = False
|
mt_unsafe: bool = False
|
||||||
|
mt_disabled: bool = False
|
||||||
mt_unsafe_one: bool = False
|
mt_unsafe_one: bool = False
|
||||||
pure: bool = False
|
pure: bool = False
|
||||||
guarded: bool = False
|
guarded: bool = False
|
||||||
|
|
@ -87,7 +102,7 @@ class VlAnnotations:
|
||||||
return self.stable_tree or self.mt_start
|
return self.stable_tree or self.mt_start
|
||||||
|
|
||||||
def is_mt_unsafe_call(self):
|
def is_mt_unsafe_call(self):
|
||||||
return self.mt_unsafe or self.mt_unsafe_one
|
return self.mt_unsafe or self.mt_unsafe_one or self.mt_disabled
|
||||||
|
|
||||||
def is_mt_safe_call(self):
|
def is_mt_safe_call(self):
|
||||||
return (not self.is_mt_unsafe_call()
|
return (not self.is_mt_unsafe_call()
|
||||||
|
|
@ -137,6 +152,8 @@ class VlAnnotations:
|
||||||
result.mt_unsafe = True
|
result.mt_unsafe = True
|
||||||
elif node.displayname == "MT_UNSAFE_ONE":
|
elif node.displayname == "MT_UNSAFE_ONE":
|
||||||
result.mt_unsafe_one = True
|
result.mt_unsafe_one = True
|
||||||
|
elif node.displayname == "MT_DISABLED":
|
||||||
|
result.mt_disabled = True
|
||||||
elif node.displayname == "PURE":
|
elif node.displayname == "PURE":
|
||||||
result.pure = True
|
result.pure = True
|
||||||
elif node.displayname in ["ACQUIRE", "ACQUIRE_SHARED"]:
|
elif node.displayname in ["ACQUIRE", "ACQUIRE_SHARED"]:
|
||||||
|
|
@ -209,6 +226,19 @@ class FunctionInfo:
|
||||||
def copy(self, /, **changes):
|
def copy(self, /, **changes):
|
||||||
return dataclasses.replace(self, **changes)
|
return dataclasses.replace(self, **changes)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_decl_file_line_and_refd_node(file: str, line: int,
|
||||||
|
refd: clang.cindex.Cursor,
|
||||||
|
annotations: VlAnnotations):
|
||||||
|
file = os.path.abspath(file)
|
||||||
|
refd = refd.canonical
|
||||||
|
assert refd is not None
|
||||||
|
name_parts = fully_qualified_name(refd)
|
||||||
|
usr = refd.get_usr()
|
||||||
|
ftype = FunctionType.from_node(refd)
|
||||||
|
|
||||||
|
return FunctionInfo(name_parts, usr, file, line, annotations, ftype)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_node(node: clang.cindex.Cursor,
|
def from_node(node: clang.cindex.Cursor,
|
||||||
refd: Optional[clang.cindex.Cursor] = None,
|
refd: Optional[clang.cindex.Cursor] = None,
|
||||||
|
|
@ -234,6 +264,7 @@ class DiagnosticKind(Enum):
|
||||||
NON_PURE_CALL_IN_PURE_CTX = enum.auto()
|
NON_PURE_CALL_IN_PURE_CTX = enum.auto()
|
||||||
NON_MT_SAFE_CALL_IN_MT_SAFE_CTX = enum.auto()
|
NON_MT_SAFE_CALL_IN_MT_SAFE_CTX = enum.auto()
|
||||||
NON_STABLE_TREE_CALL_IN_STABLE_TREE_CTX = enum.auto()
|
NON_STABLE_TREE_CALL_IN_STABLE_TREE_CTX = enum.auto()
|
||||||
|
MISSING_MT_DISABLED_ANNOTATION = enum.auto()
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return self.value < other.value
|
return self.value < other.value
|
||||||
|
|
@ -271,35 +302,142 @@ class CallAnnotationsValidator:
|
||||||
|
|
||||||
self._index = Index.create()
|
self._index = Index.create()
|
||||||
|
|
||||||
self._processed_headers: set[str] = set()
|
# Map key represents translation unit initial defines
|
||||||
|
# (from command line and source's lines before any include)
|
||||||
|
self._processed_headers: dict[str, set[str]] = {}
|
||||||
|
self._external_decls: dict[str, set[tuple[str, int]]] = {}
|
||||||
|
|
||||||
# Current context
|
# Current context
|
||||||
|
self._main_source_file: str = ""
|
||||||
|
self._defines: dict[str, str] = {}
|
||||||
self._call_location: Optional[FunctionInfo] = None
|
self._call_location: Optional[FunctionInfo] = None
|
||||||
self._caller: Optional[FunctionInfo] = None
|
self._caller: Optional[FunctionInfo] = None
|
||||||
self._level: int = 0
|
|
||||||
self._constructor_context: list[clang.cindex.Cursor] = []
|
self._constructor_context: list[clang.cindex.Cursor] = []
|
||||||
|
self._level: int = 0
|
||||||
|
|
||||||
|
def is_mt_disabled_code_unit(self):
|
||||||
|
return "VL_MT_DISABLED_CODE_UNIT" in self._defines
|
||||||
|
|
||||||
def is_constructor_context(self):
|
def is_constructor_context(self):
|
||||||
return len(self._constructor_context) > 0
|
return len(self._constructor_context) > 0
|
||||||
|
|
||||||
|
# Parses all lines in a form: `#define KEY VALUE` located before any `#include` line.
|
||||||
|
# The parsing is very simple, there is no support for line breaks, etc.
|
||||||
|
@staticmethod
|
||||||
|
def parse_initial_defines(source_file: str) -> dict[str, str]:
|
||||||
|
defs: dict[str, str] = {}
|
||||||
|
with open(source_file, "r", encoding="utf-8") as file:
|
||||||
|
for line in file:
|
||||||
|
line = line.strip()
|
||||||
|
match = re.fullmatch(
|
||||||
|
r"^#\s*(define\s+(\w+)(?:\s+(.*))?|include\s+.*)$", line)
|
||||||
|
if match:
|
||||||
|
if match.group(1).startswith("define"):
|
||||||
|
key = match.group(2)
|
||||||
|
value = match.groups("1")[2]
|
||||||
|
defs[key] = value
|
||||||
|
elif match.group(1).startswith("include"):
|
||||||
|
break
|
||||||
|
return defs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def filter_out_unsupported_compiler_args(
|
||||||
|
args: list[str]) -> tuple[list[str], dict[str, str]]:
|
||||||
|
filtered_args = []
|
||||||
|
defines = {}
|
||||||
|
args_iter = iter(args)
|
||||||
|
try:
|
||||||
|
while arg := next(args_iter):
|
||||||
|
# Skip positional arguments (input file name).
|
||||||
|
if not arg.startswith("-") and (arg.endswith(".cpp")
|
||||||
|
or arg.endswith(".c")
|
||||||
|
or arg.endswith(".h")):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skipped options with separate value argument.
|
||||||
|
if arg in ["-o", "-T", "-MT", "-MQ", "-MF"
|
||||||
|
"-L"]:
|
||||||
|
next(args_iter)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skipped options without separate value argument.
|
||||||
|
if arg == "-c" or arg.startswith("-W") or arg.startswith("-L"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Preserved options with separate value argument.
|
||||||
|
if arg in [
|
||||||
|
"-x"
|
||||||
|
"-Xclang", "-I", "-isystem", "-iquote", "-include",
|
||||||
|
"-include-pch"
|
||||||
|
]:
|
||||||
|
filtered_args += [arg, next(args_iter)]
|
||||||
|
continue
|
||||||
|
|
||||||
|
kv_str = None
|
||||||
|
d_or_u = None
|
||||||
|
# Preserve define/undefine with separate value argument.
|
||||||
|
if arg in ["-D", "-U"]:
|
||||||
|
filtered_args.append(arg)
|
||||||
|
d_or_u = arg[1]
|
||||||
|
kv_str = next(args_iter)
|
||||||
|
filtered_args.append(kv_str)
|
||||||
|
# Preserve define/undefine without separate value argument.
|
||||||
|
elif arg[0:2] in ["-D", "-U"]:
|
||||||
|
filtered_args.append(arg)
|
||||||
|
kv_str = arg[2:]
|
||||||
|
d_or_u = arg[1]
|
||||||
|
# Preserve everything else.
|
||||||
|
else:
|
||||||
|
filtered_args.append(arg)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Keep track of defines for class' internal purposes.
|
||||||
|
key_value = kv_str.split("=", 1)
|
||||||
|
key = key_value[0]
|
||||||
|
val = "1" if len(key_value) == 1 else key_value[1]
|
||||||
|
|
||||||
|
if d_or_u == "D":
|
||||||
|
defines[key] = val
|
||||||
|
elif d_or_u == "U" and key in defines:
|
||||||
|
del defines[key]
|
||||||
|
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return (filtered_args, defines)
|
||||||
|
|
||||||
def compile_and_analyze_file(self, source_file: str,
|
def compile_and_analyze_file(self, source_file: str,
|
||||||
compiler_args: list[str],
|
compiler_args: list[str],
|
||||||
build_dir: Optional[str]):
|
build_dir: Optional[str]):
|
||||||
filename = os.path.abspath(source_file)
|
filename = os.path.abspath(source_file)
|
||||||
initial_cwd = "."
|
initial_cwd = "."
|
||||||
|
|
||||||
|
filtered_args, defines = self.filter_out_unsupported_compiler_args(
|
||||||
|
compiler_args)
|
||||||
|
defines.update(self.parse_initial_defines(source_file))
|
||||||
|
|
||||||
if build_dir:
|
if build_dir:
|
||||||
initial_cwd = os.getcwd()
|
initial_cwd = os.getcwd()
|
||||||
os.chdir(build_dir)
|
os.chdir(build_dir)
|
||||||
translation_unit = self._index.parse(filename, compiler_args)
|
try:
|
||||||
has_errors = False
|
translation_unit = self._index.parse(filename, filtered_args)
|
||||||
for diag in translation_unit.diagnostics:
|
except TranslationUnitLoadError:
|
||||||
if diag.severity > clang.cindex.Diagnostic.Error:
|
translation_unit = None
|
||||||
has_errors = True
|
errors = []
|
||||||
if translation_unit and not has_errors:
|
if translation_unit:
|
||||||
|
for diag in translation_unit.diagnostics:
|
||||||
|
if diag.severity >= clang.cindex.Diagnostic.Error:
|
||||||
|
errors.append(str(diag))
|
||||||
|
if translation_unit and len(errors) == 0:
|
||||||
|
self._defines = defines
|
||||||
|
self._main_source_file = filename
|
||||||
self.process_translation_unit(translation_unit)
|
self.process_translation_unit(translation_unit)
|
||||||
|
self._main_source_file = ""
|
||||||
|
self._defines = {}
|
||||||
else:
|
else:
|
||||||
print(f"%Error: parsing failed: {filename}", file=sys.stderr)
|
print(f"%Error: parsing failed: {filename}", file=sys.stderr)
|
||||||
|
for error in errors:
|
||||||
|
print(f" {error}", file=sys.stderr)
|
||||||
if build_dir:
|
if build_dir:
|
||||||
os.chdir(initial_cwd)
|
os.chdir(initial_cwd)
|
||||||
|
|
||||||
|
|
@ -569,6 +707,18 @@ class CallAnnotationsValidator:
|
||||||
f" from: {node.location.file.name}:{node.location.line}")
|
f" from: {node.location.file.name}:{node.location.line}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def process_function_declaration(self, node: clang.cindex.Cursor):
|
||||||
|
# Ignore declarations in main .cpp file
|
||||||
|
if node.location.file.name != self._main_source_file:
|
||||||
|
children = list(node.get_children())
|
||||||
|
annotations = VlAnnotations.from_nodes_list(children)
|
||||||
|
if not annotations.mt_disabled:
|
||||||
|
self._external_decls.setdefault(node.get_usr(), set()).add(
|
||||||
|
(str(node.location.file.name), int(node.location.line)))
|
||||||
|
return self.iterate_children(children, self.dispatch_node)
|
||||||
|
|
||||||
|
return self.iterate_children(node.get_children(), self.dispatch_node)
|
||||||
|
|
||||||
# Definition handling
|
# Definition handling
|
||||||
|
|
||||||
def dispatch_node_inside_definition(self, node: clang.cindex.Cursor):
|
def dispatch_node_inside_definition(self, node: clang.cindex.Cursor):
|
||||||
|
|
@ -599,6 +749,18 @@ class CallAnnotationsValidator:
|
||||||
assert refd is not None
|
assert refd is not None
|
||||||
|
|
||||||
def_annotations = VlAnnotations.from_nodes_list(node_children)
|
def_annotations = VlAnnotations.from_nodes_list(node_children)
|
||||||
|
# Implicitly mark definitions in VL_MT_DISABLED_CODE_UNIT .cpp files as
|
||||||
|
# VL_MT_DISABLED. Existence of the annotation on declarations in .h
|
||||||
|
# files is verified below.
|
||||||
|
# Also sets VL_REQUIRES, as this annotation is added together with
|
||||||
|
# explicit VL_MT_DISABLED.
|
||||||
|
if self.is_mt_disabled_code_unit():
|
||||||
|
if node.location.file.name == self._main_source_file:
|
||||||
|
annotations.mt_disabled = True
|
||||||
|
annotations.requires = True
|
||||||
|
if refd.location.file.name == self._main_source_file:
|
||||||
|
def_annotations.mt_disabled = True
|
||||||
|
def_annotations.requires = True
|
||||||
|
|
||||||
if not (def_annotations.is_empty() or def_annotations == annotations):
|
if not (def_annotations.is_empty() or def_annotations == annotations):
|
||||||
# Use definition's annotations for the diagnostic
|
# Use definition's annotations for the diagnostic
|
||||||
|
|
@ -611,12 +773,26 @@ class CallAnnotationsValidator:
|
||||||
DiagnosticKind.ANNOTATIONS_DEF_DECL_MISMATCH)
|
DiagnosticKind.ANNOTATIONS_DEF_DECL_MISMATCH)
|
||||||
|
|
||||||
# Use concatenation of definition and declaration annotations
|
# Use concatenation of definition and declaration annotations
|
||||||
# for callees validation.
|
# for calls validation.
|
||||||
self._caller = FunctionInfo.from_node(node, refd,
|
self._caller = FunctionInfo.from_node(node, refd,
|
||||||
def_annotations | annotations)
|
def_annotations | annotations)
|
||||||
prev_call_location = self._call_location
|
prev_call_location = self._call_location
|
||||||
self._call_location = self._caller
|
self._call_location = self._caller
|
||||||
|
|
||||||
|
if self.is_mt_disabled_code_unit():
|
||||||
|
# Report declarations of this functions that don't have MT_DISABLED annotation
|
||||||
|
# and are located in headers.
|
||||||
|
if node.location.file.name == self._main_source_file:
|
||||||
|
usr = node.get_usr()
|
||||||
|
declarations = self._external_decls.get(usr, set())
|
||||||
|
for file, line in declarations:
|
||||||
|
self.emit_diagnostic(
|
||||||
|
FunctionInfo.from_decl_file_line_and_refd_node(
|
||||||
|
file, line, refd, def_annotations),
|
||||||
|
DiagnosticKind.MISSING_MT_DISABLED_ANNOTATION)
|
||||||
|
if declarations:
|
||||||
|
del self._external_decls[usr]
|
||||||
|
|
||||||
self.iterate_children(node_children,
|
self.iterate_children(node_children,
|
||||||
self.dispatch_node_inside_definition)
|
self.dispatch_node_inside_definition)
|
||||||
|
|
||||||
|
|
@ -628,34 +804,36 @@ class CallAnnotationsValidator:
|
||||||
# Nodes not located inside definition
|
# Nodes not located inside definition
|
||||||
|
|
||||||
def dispatch_node(self, node: clang.cindex.Cursor):
|
def dispatch_node(self, node: clang.cindex.Cursor):
|
||||||
if node.is_definition() and node.kind in [
|
if node.kind in [
|
||||||
CursorKind.CXX_METHOD, CursorKind.FUNCTION_DECL,
|
CursorKind.CXX_METHOD, CursorKind.FUNCTION_DECL,
|
||||||
CursorKind.CONSTRUCTOR, CursorKind.CONVERSION_FUNCTION
|
CursorKind.CONSTRUCTOR, CursorKind.CONVERSION_FUNCTION
|
||||||
]:
|
]:
|
||||||
return self.process_function_definition(node)
|
if node.is_definition():
|
||||||
if node.is_definition() and node.kind in [
|
return self.process_function_definition(node)
|
||||||
CursorKind.NAMESPACE, CursorKind.STRUCT_DECL,
|
# else:
|
||||||
CursorKind.UNION_DECL, CursorKind.CLASS_DECL
|
return self.process_function_declaration(node)
|
||||||
]:
|
|
||||||
return self.iterate_children(node.get_children(),
|
|
||||||
self.dispatch_node)
|
|
||||||
|
|
||||||
return self.iterate_children(node.get_children(), self.dispatch_node)
|
return self.iterate_children(node.get_children(), self.dispatch_node)
|
||||||
|
|
||||||
def process_translation_unit(
|
def process_translation_unit(
|
||||||
self, translation_unit: clang.cindex.TranslationUnit):
|
self, translation_unit: clang.cindex.TranslationUnit):
|
||||||
self._level += 1
|
self._level += 1
|
||||||
|
kv_defines = sorted([f"{k}={v}" for k, v in self._defines.items()])
|
||||||
|
concat_defines = '\n'.join(kv_defines)
|
||||||
|
# List of headers already processed in a TU with specified set of defines.
|
||||||
|
tu_processed_headers = self._processed_headers.setdefault(
|
||||||
|
concat_defines, set())
|
||||||
for child in translation_unit.cursor.get_children():
|
for child in translation_unit.cursor.get_children():
|
||||||
if self._is_ignored_top_level(child):
|
if self._is_ignored_top_level(child):
|
||||||
continue
|
continue
|
||||||
if self._processed_headers:
|
if tu_processed_headers:
|
||||||
filename = os.path.abspath(child.location.file.name)
|
filename = os.path.abspath(child.location.file.name)
|
||||||
if filename in self._processed_headers:
|
if filename in tu_processed_headers:
|
||||||
continue
|
continue
|
||||||
self.dispatch_node(child)
|
self.dispatch_node(child)
|
||||||
self._level -= 1
|
self._level -= 1
|
||||||
|
|
||||||
self._processed_headers.update([
|
tu_processed_headers.update([
|
||||||
os.path.abspath(str(hdr.source))
|
os.path.abspath(str(hdr.source))
|
||||||
for hdr in translation_unit.get_includes()
|
for hdr in translation_unit.get_includes()
|
||||||
])
|
])
|
||||||
|
|
@ -717,34 +895,40 @@ def get_filter_funcs(verilator_root: str):
|
||||||
|
|
||||||
|
|
||||||
def precompile_header(compile_command: CompileCommand, tmp_dir: str) -> str:
|
def precompile_header(compile_command: CompileCommand, tmp_dir: str) -> str:
|
||||||
|
initial_cwd = os.getcwd()
|
||||||
|
errors = []
|
||||||
try:
|
try:
|
||||||
initial_cwd = os.getcwd()
|
|
||||||
os.chdir(compile_command.directory)
|
os.chdir(compile_command.directory)
|
||||||
|
|
||||||
index = Index.create()
|
index = Index.create()
|
||||||
translation_unit = index.parse(compile_command.filename,
|
translation_unit = index.parse(compile_command.filename,
|
||||||
compile_command.args)
|
compile_command.args)
|
||||||
for diag in translation_unit.diagnostics:
|
for diag in translation_unit.diagnostics:
|
||||||
if diag.severity > clang.cindex.Diagnostic.Error:
|
if diag.severity >= clang.cindex.Diagnostic.Error:
|
||||||
pch_file = None
|
errors.append(str(diag))
|
||||||
break
|
|
||||||
else:
|
if len(errors) == 0:
|
||||||
pch_file = os.path.join(
|
pch_file = os.path.join(
|
||||||
tmp_dir,
|
tmp_dir,
|
||||||
f"{compile_command.refid:02}_{os.path.basename(compile_command.filename)}.pch"
|
f"{compile_command.refid:02}_{os.path.basename(compile_command.filename)}.pch"
|
||||||
)
|
)
|
||||||
translation_unit.save(pch_file)
|
translation_unit.save(pch_file)
|
||||||
|
|
||||||
|
if pch_file:
|
||||||
|
return pch_file
|
||||||
|
|
||||||
|
except (TranslationUnitSaveError, TranslationUnitLoadError,
|
||||||
|
OSError) as exception:
|
||||||
|
print(f"%Warning: {exception}", file=sys.stderr)
|
||||||
|
|
||||||
|
finally:
|
||||||
os.chdir(initial_cwd)
|
os.chdir(initial_cwd)
|
||||||
|
|
||||||
if pch_file:
|
|
||||||
return pch_file
|
|
||||||
|
|
||||||
except (TranslationUnitSaveError, TranslationUnitLoadError, OSError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"%Warning: Precompiling failed, skipping: {compile_command.filename}")
|
f"%Warning: Precompilation failed, skipping: {compile_command.filename}",
|
||||||
|
file=sys.stderr)
|
||||||
|
for error in errors:
|
||||||
|
print(f" {error}", file=sys.stderr)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -887,6 +1071,10 @@ class TopDownSummaryPrinter():
|
||||||
name += "is pure but calls non-pure function(s)"
|
name += "is pure but calls non-pure function(s)"
|
||||||
elif func.reason == DiagnosticKind.NON_STABLE_TREE_CALL_IN_STABLE_TREE_CTX:
|
elif func.reason == DiagnosticKind.NON_STABLE_TREE_CALL_IN_STABLE_TREE_CTX:
|
||||||
name += "is stable_tree but calls non-stable_tree or non-mtsafe"
|
name += "is stable_tree but calls non-stable_tree or non-mtsafe"
|
||||||
|
elif func.reason == DiagnosticKind.MISSING_MT_DISABLED_ANNOTATION:
|
||||||
|
name += ("defined in a file marked as " +
|
||||||
|
"VL_MT_DISABLED_CODE_UNIT has declaration(s) " +
|
||||||
|
"without VL_MT_DISABLED annotation")
|
||||||
else:
|
else:
|
||||||
name += "for unknown reason (please add description)"
|
name += "for unknown reason (please add description)"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,20 +10,20 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||||
|
|
||||||
scenarios(dist => 1);
|
scenarios(dist => 1);
|
||||||
rerunnable(0);
|
rerunnable(0);
|
||||||
|
my $root = "..";
|
||||||
if ($ENV{VERILATOR_TEST_NO_ATTRIBUTES}) {
|
if ($ENV{VERILATOR_TEST_NO_ATTRIBUTES}) {
|
||||||
skip("Skipping due to VERILATOR_TEST_NO_ATTRIBUTES");
|
skip("Skipping due to VERILATOR_TEST_NO_ATTRIBUTES");
|
||||||
|
} elsif (! -e "$root/src/obj_dbg/compile_commands.json") {
|
||||||
|
skip("compile_commands.json not found. Please install 'bear > 3.0' and rebuild Verilator.");
|
||||||
} else {
|
} else {
|
||||||
check();
|
check();
|
||||||
}
|
}
|
||||||
sub check {
|
sub check {
|
||||||
my $root = "..";
|
|
||||||
# some of the files are only used in Verilation
|
# some of the files are only used in Verilation
|
||||||
# and are only in "include" folder
|
# and are only in "include" folder
|
||||||
my @srcfiles = grep { !/\/(V3Const|Vlc\w*|\w*_test|\w*_sc|\w*.yy).cpp$/ }
|
my @srcfiles = grep { !/\/(V3Const|Vlc\w*|\w*_test|\w*_sc|\w*.yy).cpp$/ }
|
||||||
glob("$root/src/*.cpp $root/src/obj_opt/V3Const__gen.cpp");
|
glob("$root/src/*.cpp $root/src/obj_dbg/V3Const__gen.cpp");
|
||||||
my $srcfiles_str = join(" ", @srcfiles);
|
my $srcfiles_str = join(" ", @srcfiles);
|
||||||
my $precompile_args = "-c $root/src/V3Ast.h";
|
|
||||||
my $clang_args = "-I$root/src/ -I$root/include/ -I$root/src/obj_opt/ -fcoroutines-ts";
|
|
||||||
|
|
||||||
sub run_clang_check {
|
sub run_clang_check {
|
||||||
{
|
{
|
||||||
|
|
@ -34,7 +34,12 @@ sub check {
|
||||||
}
|
}
|
||||||
run(logfile => $Self->{run_log_filename},
|
run(logfile => $Self->{run_log_filename},
|
||||||
tee => 1,
|
tee => 1,
|
||||||
cmd => ["python3", "$root/nodist/clang_check_attributes --verilator-root=$root --cxxflags='$clang_args' $precompile_args $srcfiles_str"]);
|
cmd => ["python3",
|
||||||
|
"$root/nodist/clang_check_attributes",
|
||||||
|
"--verilator-root=$root",
|
||||||
|
"--compilation-root=$root/src/obj_dbg",
|
||||||
|
"--compile-commands-dir=$root/src/obj_dbg",
|
||||||
|
"$srcfiles_str"]);
|
||||||
|
|
||||||
file_grep($Self->{run_log_filename}, "Number of functions reported unsafe: 0");
|
file_grep($Self->{run_log_filename}, "Number of functions reported unsafe: 0");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||||
|
//
|
||||||
|
//*************************************************************************
|
||||||
|
//
|
||||||
|
// Code available from: https://verilator.org
|
||||||
|
//
|
||||||
|
// Copyright 2022-2023 by Wilson Snyder. This program is free software; you can
|
||||||
|
// redistribute it and/or modify it under the terms of either the GNU
|
||||||
|
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||||
|
// Version 2.0.
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||||
|
//
|
||||||
|
//*************************************************************************
|
||||||
|
|
||||||
|
#define VL_MT_DISABLED_CODE_UNIT 1
|
||||||
|
|
||||||
|
#include "mt_disabled.h"
|
||||||
|
#include "mt_enabled.h"
|
||||||
|
|
||||||
|
void unannotatedMtDisabledFunctionBad() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnannotatedMtDisabledClass::unannotatedMtDisabledMethodBad() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnannotatedMtDisabledClass::unannotatedMtDisabledStaticMethodBad() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declarations in .cpp don't have to be annotated with VL_MT_DISABLED.
|
||||||
|
void annotatedMtDisabledFunctionOK();
|
||||||
|
|
||||||
|
void annotatedMtDisabledFunctionOK() {
|
||||||
|
VerilatedMutex m;
|
||||||
|
// REQUIRES should be ignored and mutex locking not needed.
|
||||||
|
nsf_aa_VL_REQUIRES(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnnotatedMtDisabledClass::annotatedMtDisabledMethodOK() {
|
||||||
|
annotatedMtDisabledFunctionOK();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnnotatedMtDisabledClass::annotatedMtDisabledStaticMethodOK() {
|
||||||
|
annotatedMtDisabledFunctionOK();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||||
|
//
|
||||||
|
//*************************************************************************
|
||||||
|
//
|
||||||
|
// Code available from: https://verilator.org
|
||||||
|
//
|
||||||
|
// Copyright 2022-2023 by Wilson Snyder. This program is free software; you can
|
||||||
|
// redistribute it and/or modify it under the terms of either the GNU
|
||||||
|
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||||
|
// Version 2.0.
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||||
|
//
|
||||||
|
//*************************************************************************
|
||||||
|
|
||||||
|
#ifndef T_DIST_ATTRIBUTES_MT_DISABLED_H_
|
||||||
|
#define T_DIST_ATTRIBUTES_MT_DISABLED_H_
|
||||||
|
|
||||||
|
#include "verilatedos.h"
|
||||||
|
|
||||||
|
#include "V3ThreadSafety.h"
|
||||||
|
|
||||||
|
void unannotatedMtDisabledFunctionBad();
|
||||||
|
|
||||||
|
// Duplicate to check that every declaration is reported
|
||||||
|
void unannotatedMtDisabledFunctionBad();
|
||||||
|
|
||||||
|
class UnannotatedMtDisabledClass final {
|
||||||
|
public:
|
||||||
|
void unannotatedMtDisabledMethodBad();
|
||||||
|
static void unannotatedMtDisabledStaticMethodBad();
|
||||||
|
|
||||||
|
int unannotatedInlineMethodOK() const { return 42; }
|
||||||
|
static int unannotatedInlineStaticMethodOK() { return -42; }
|
||||||
|
};
|
||||||
|
|
||||||
|
void annotatedMtDisabledFunctionOK() VL_MT_DISABLED;
|
||||||
|
|
||||||
|
// Duplicate
|
||||||
|
void annotatedMtDisabledFunctionOK() VL_MT_DISABLED;
|
||||||
|
|
||||||
|
class AnnotatedMtDisabledClass final {
|
||||||
|
public:
|
||||||
|
void annotatedMtDisabledMethodOK() VL_MT_DISABLED;
|
||||||
|
static void annotatedMtDisabledStaticMethodOK() VL_MT_DISABLED;
|
||||||
|
|
||||||
|
int annotatedInlineMethodOK() const VL_MT_DISABLED { return 42; }
|
||||||
|
static int annotatedInlineStaticMethodOK() VL_MT_DISABLED { return -42; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // T_DIST_ATTRIBUTES_MT_DISABLED_H_
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
#include "verilatedos.h"
|
#include "verilatedos.h"
|
||||||
|
|
||||||
#include "t_dist_attributes_bad.h"
|
#include "mt_enabled.h"
|
||||||
|
|
||||||
// Non-Static Functions, Annotated declaration, Unannotated definition.
|
// Non-Static Functions, Annotated declaration, Unannotated definition.
|
||||||
// (definitions)
|
// (definitions)
|
||||||
|
|
@ -12,8 +12,8 @@
|
||||||
//
|
//
|
||||||
//*************************************************************************
|
//*************************************************************************
|
||||||
|
|
||||||
#ifndef T_DIST_ATTRIBUTES_BAD_H_
|
#ifndef T_DIST_ATTRIBUTES_MT_ENABLED_H_
|
||||||
#define T_DIST_ATTRIBUTES_BAD_H_
|
#define T_DIST_ATTRIBUTES_MT_ENABLED_H_
|
||||||
|
|
||||||
#include "verilatedos.h"
|
#include "verilatedos.h"
|
||||||
|
|
||||||
|
|
@ -410,4 +410,4 @@ class TestClassConstructor {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // T_DIST_ATTRIBUTES_BAD_H_
|
#endif // T_DIST_ATTRIBUTES_MT_ENABLED_H_
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -8,17 +8,65 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||||
# Version 2.0.
|
# Version 2.0.
|
||||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||||
|
|
||||||
|
use Cwd qw(abs_path);
|
||||||
|
use JSON::PP;
|
||||||
|
use IO::File;
|
||||||
|
|
||||||
scenarios(dist => 1);
|
scenarios(dist => 1);
|
||||||
if ($ENV{VERILATOR_TEST_NO_ATTRIBUTES}) {
|
if ($ENV{VERILATOR_TEST_NO_ATTRIBUTES}) {
|
||||||
skip("Skipping due to VERILATOR_TEST_NO_ATTRIBUTES");
|
skip("Skipping due to VERILATOR_TEST_NO_ATTRIBUTES");
|
||||||
} else {
|
} else {
|
||||||
check();
|
check();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub gen_compile_commands_json {
|
||||||
|
my $json = JSON::PP->new->utf8->pretty;
|
||||||
|
|
||||||
|
my $root_dir = abs_path("..");
|
||||||
|
my $srcs_dir = abs_path("./t/t_dist_attributes");
|
||||||
|
my @common_args = ("clang++",
|
||||||
|
"-std=c++14",
|
||||||
|
"-I$root_dir/include",
|
||||||
|
"-I$root_dir/src",
|
||||||
|
"-c");
|
||||||
|
|
||||||
|
my $ccjson = [
|
||||||
|
{"directory" => "$srcs_dir",
|
||||||
|
"file" => "$srcs_dir/mt_enabled.cpp",
|
||||||
|
"output" => undef,
|
||||||
|
"arguments" => [@common_args]},
|
||||||
|
{"directory" => "$srcs_dir",
|
||||||
|
"file" => "$srcs_dir/mt_disabled.cpp",
|
||||||
|
"output" => undef,
|
||||||
|
"arguments" => [@common_args]},
|
||||||
|
];
|
||||||
|
|
||||||
|
my @srcfiles;
|
||||||
|
foreach my $entry (@$ccjson) {
|
||||||
|
# Add "output" key
|
||||||
|
($entry->{"output"} = $entry->{"file"}) =~ s/\.cpp$/.o/;
|
||||||
|
# Add "-o src.o src.cpp" arguments
|
||||||
|
push @{$entry->{"arguments"}}, ("-o", $entry->{"output"}, $entry->{"file"});
|
||||||
|
|
||||||
|
push @srcfiles, $entry->{"file"};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
\@srcfiles,
|
||||||
|
$json->encode($ccjson)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
sub check {
|
sub check {
|
||||||
my $root = "..";
|
my $root = abs_path("..");
|
||||||
my @srcfiles = glob("$root/test_regress/t/t_dist_attributes_bad.cpp");
|
my $ccjson_file = "$Self->{obj_dir}/compile_commands.json";
|
||||||
my $srcfiles_str = join(" ", @srcfiles);
|
my ($srcfiles, $ccjson) = gen_compile_commands_json();
|
||||||
my $clang_args = "-I$root/include";
|
my $srcfiles_str = join(" ", @$srcfiles);
|
||||||
|
{
|
||||||
|
my $fh = IO::File->new(">$ccjson_file") or die "%Error: $! $ccjson_file";
|
||||||
|
print $fh $ccjson;
|
||||||
|
$fh->close();
|
||||||
|
}
|
||||||
|
|
||||||
sub run_clang_check {
|
sub run_clang_check {
|
||||||
{
|
{
|
||||||
|
|
@ -32,7 +80,11 @@ sub check {
|
||||||
# With `--verilator-root` set to the current directory
|
# With `--verilator-root` set to the current directory
|
||||||
# (i.e. `test_regress`) the script will skip annotation issues in
|
# (i.e. `test_regress`) the script will skip annotation issues in
|
||||||
# headers from the `../include` directory.
|
# headers from the `../include` directory.
|
||||||
cmd => ["python3", "$root/nodist/clang_check_attributes --verilator-root=. --cxxflags='$clang_args' $srcfiles_str"]);
|
cmd => ["python3",
|
||||||
|
"$root/nodist/clang_check_attributes",
|
||||||
|
"--verilator-root=.",
|
||||||
|
"--compile-commands-dir=$Self->{obj_dir}",
|
||||||
|
"$srcfiles_str"]);
|
||||||
|
|
||||||
files_identical($Self->{run_log_filename}, $Self->{golden_filename});
|
files_identical($Self->{run_log_filename}, $Self->{golden_filename});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue