Update clang_check_attributes to take into account MT_DISABLED (#4479)

This commit is contained in:
Kamil Rakoczy 2023-09-13 22:32:18 +02:00 committed by GitHub
parent 9fe459c820
commit ec2e3ec0e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1338 additions and 986 deletions

View File

@ -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)"

View File

@ -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");
} }

View File

@ -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();
}

View File

@ -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_

View File

@ -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)

View File

@ -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

View File

@ -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});
} }