| // Copyright 2017-2020 The Verible Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "verilog/CST/functions.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "absl/strings/string_view.h" |
| #include "common/analysis/matcher/matcher_builders.h" |
| #include "common/analysis/syntax_tree_search.h" |
| #include "common/analysis/syntax_tree_search_test_utils.h" |
| #include "common/text/concrete_syntax_leaf.h" |
| #include "common/text/concrete_syntax_tree.h" |
| #include "common/text/symbol.h" |
| #include "common/text/text_structure.h" |
| #include "common/text/token_info.h" |
| #include "common/text/token_info_test_util.h" |
| #include "common/text/tree_utils.h" |
| #include "common/util/casts.h" |
| #include "common/util/logging.h" |
| #include "common/util/range.h" |
| #include "verilog/CST/identifier.h" |
| #include "verilog/analysis/verilog_analyzer.h" |
| #include "verilog/parser/verilog_token_enum.h" |
| |
| #undef ASSERT_OK |
| #define ASSERT_OK(value) ASSERT_TRUE((value).ok()) |
| |
| namespace verilog { |
| namespace { |
| |
| using verible::CheckSymbolAsLeaf; |
| using verible::SymbolCastToNode; |
| using verible::SyntaxTreeSearchTestCase; |
| using verible::TreeSearchMatch; |
| |
| TEST(FindAllFunctionDeclarationsTest, EmptySource) { |
| VerilogAnalyzer analyzer("", ""); |
| ASSERT_OK(analyzer.Analyze()); |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = |
| FindAllFunctionDeclarations(*ABSL_DIE_IF_NULL(root)); |
| EXPECT_TRUE(function_declarations.empty()); |
| } |
| |
| TEST(FindAllFunctionDeclarationsTest, OnlyClass) { |
| VerilogAnalyzer analyzer("class foo; endclass", ""); |
| ASSERT_OK(analyzer.Analyze()); |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = |
| FindAllFunctionDeclarations(*ABSL_DIE_IF_NULL(root)); |
| EXPECT_TRUE(function_declarations.empty()); |
| } |
| |
| TEST(FindAllFunctionDeclarationsTest, OnlyModule) { |
| VerilogAnalyzer analyzer("module foo; endmodule", ""); |
| ASSERT_OK(analyzer.Analyze()); |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = |
| FindAllFunctionDeclarations(*ABSL_DIE_IF_NULL(root)); |
| EXPECT_TRUE(function_declarations.empty()); |
| } |
| |
| TEST(FindAllFunctionDeclarationsTest, OneFunction) { |
| VerilogAnalyzer analyzer("function foo(); endfunction", ""); |
| ASSERT_OK(analyzer.Analyze()); |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = |
| FindAllFunctionDeclarations(*ABSL_DIE_IF_NULL(root)); |
| ASSERT_EQ(function_declarations.size(), 1); |
| } |
| |
| TEST(FindAllFunctionDeclarationsTest, TwoFunctions) { |
| VerilogAnalyzer analyzer(R"( |
| function foo(); endfunction |
| function foo2(); endfunction |
| )", |
| ""); |
| ASSERT_OK(analyzer.Analyze()); |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = |
| FindAllFunctionDeclarations(*ABSL_DIE_IF_NULL(root)); |
| EXPECT_EQ(function_declarations.size(), 2); |
| } |
| |
| TEST(FindAllFunctionDeclarationsTest, FunctionInsideClass) { |
| VerilogAnalyzer analyzer("class bar; function foo(); endfunction endclass", |
| ""); |
| ASSERT_OK(analyzer.Analyze()); |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = |
| FindAllFunctionDeclarations(*ABSL_DIE_IF_NULL(root)); |
| EXPECT_EQ(function_declarations.size(), 1); |
| } |
| |
| TEST(FindAllFunctionDeclarationsTest, FunctionInsideModule) { |
| VerilogAnalyzer analyzer("module bar; function foo(); endfunction endmodule", |
| ""); |
| ASSERT_OK(analyzer.Analyze()); |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = |
| FindAllFunctionDeclarations(*ABSL_DIE_IF_NULL(root)); |
| EXPECT_EQ(function_declarations.size(), 1); |
| } |
| |
| // TODO(kathuriac): Add test case for function inside cross_body_item see |
| // (verilog.y) |
| |
| TEST(GetFunctionHeaderTest, Header) { |
| const char* kTestCases[] = { |
| "function foo(); endfunction", |
| "class c; function foo(); endfunction endclass", |
| "module m; function foo(); endfunction endmodule", |
| }; |
| for (const auto test : kTestCases) { |
| VerilogAnalyzer analyzer(test, ""); |
| ASSERT_OK(analyzer.Analyze()); |
| // Root node is a description list, not a function. |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = FindAllFunctionDeclarations(*root); |
| ASSERT_EQ(function_declarations.size(), 1); |
| const auto& function_node = |
| SymbolCastToNode(*function_declarations.front().match); |
| GetFunctionHeader(function_node); |
| // Reaching here is success, function includes internal checks already. |
| // TODO(b/151371397): verify substring range |
| } |
| } |
| |
| TEST(GetFunctionLifetimeTest, NoLifetimeDeclared) { |
| VerilogAnalyzer analyzer("function foo(); endfunction", ""); |
| ASSERT_OK(analyzer.Analyze()); |
| // Root node is a description list, not a function. |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = FindAllFunctionDeclarations(*root); |
| ASSERT_EQ(function_declarations.size(), 1); |
| const auto& function_node = |
| SymbolCastToNode(*function_declarations.front().match); |
| const auto* lifetime = GetFunctionLifetime(function_node); |
| EXPECT_EQ(lifetime, nullptr); |
| } |
| |
| TEST(GetFunctionLifetimeTest, StaticLifetimeDeclared) { |
| VerilogAnalyzer analyzer("function static foo(); endfunction", ""); |
| ASSERT_OK(analyzer.Analyze()); |
| // Root node is a description list, not a function. |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = FindAllFunctionDeclarations(*root); |
| ASSERT_EQ(function_declarations.size(), 1); |
| const auto& function_node = |
| SymbolCastToNode(*function_declarations.front().match); |
| const auto* lifetime = GetFunctionLifetime(function_node); |
| CheckSymbolAsLeaf(*ABSL_DIE_IF_NULL(lifetime), TK_static); |
| // TODO(b/151371397): verify substring range |
| } |
| |
| TEST(GetFunctionLifetimeTest, AutomaticLifetimeDeclared) { |
| VerilogAnalyzer analyzer("function automatic foo(); endfunction", ""); |
| ASSERT_OK(analyzer.Analyze()); |
| // Root node is a description list, not a function.e |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = FindAllFunctionDeclarations(*root); |
| ASSERT_EQ(function_declarations.size(), 1); |
| const auto& function_node = |
| SymbolCastToNode(*function_declarations.front().match); |
| const auto* lifetime = GetFunctionLifetime(function_node); |
| CheckSymbolAsLeaf(*ABSL_DIE_IF_NULL(lifetime), TK_automatic); |
| // TODO(b/151371397): verify substring range |
| } |
| |
| TEST(GetFunctionIdTest, UnqualifiedIds) { |
| const std::pair<std::string, std::vector<absl::string_view>> kTestCases[] = { |
| {"function foo(); endfunction", {"foo"}}, |
| {"function automatic bar(); endfunction", {"bar"}}, |
| {"function static baz(); endfunction", {"baz"}}, |
| {"package p; function foo(); endfunction endpackage", {"foo"}}, |
| {"class c; function zoo(); endfunction endclass", {"zoo"}}, |
| {"function myclass::foo(); endfunction", {"myclass", "foo"}}, |
| }; |
| for (const auto test : kTestCases) { |
| VerilogAnalyzer analyzer(test.first, ""); |
| ASSERT_OK(analyzer.Analyze()); |
| // Root node is a description list, not a function. |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = FindAllFunctionDeclarations(*root); |
| ASSERT_EQ(function_declarations.size(), 1); |
| const auto& function_node = |
| SymbolCastToNode(*function_declarations.front().match); |
| const auto* function_id = GetFunctionId(function_node); |
| const auto ids = FindAllUnqualifiedIds(*function_id); |
| std::vector<absl::string_view> got_ids; |
| for (const auto& id : ids) { |
| const verible::SyntaxTreeLeaf* base = GetIdentifier(*id.match); |
| got_ids.push_back(ABSL_DIE_IF_NULL(base)->get().text()); |
| } |
| EXPECT_EQ(got_ids, test.second); |
| // TODO(b/151371397): verify substring range |
| } |
| } |
| |
| TEST(GetFunctionIdTest, QualifiedIds) { |
| const std::pair<std::string, int> kTestCases[] = { |
| {"function foo(); endfunction", 0}, |
| {"function myclass::foo(); endfunction", 1}, |
| }; |
| for (const auto test : kTestCases) { |
| VerilogAnalyzer analyzer(test.first, ""); |
| ASSERT_OK(analyzer.Analyze()); |
| // Root node is a description list, not a function. |
| const auto& root = analyzer.Data().SyntaxTree(); |
| const auto function_declarations = FindAllFunctionDeclarations(*root); |
| ASSERT_EQ(function_declarations.size(), 1); |
| const auto& function_node = |
| SymbolCastToNode(*function_declarations.front().match); |
| const auto* function_id = GetFunctionId(function_node); |
| const auto ids = FindAllQualifiedIds(*function_id); |
| EXPECT_EQ(ids.size(), test.second); |
| // TODO(b/151371397): verify substring range |
| } |
| } |
| |
| struct SubtreeTestData { |
| NodeEnum expected_construct; |
| verible::TokenInfoTestData token_data; |
| }; |
| |
| TEST(GetFunctionReturnTypeTest, NoReturnType) { |
| const SubtreeTestData kTestCases[] = { |
| {NodeEnum::kFunctionDeclaration, {"function f;endfunction\n"}}, |
| {NodeEnum::kFunctionDeclaration, |
| {"package p;\nfunction f;\nendfunction\nendpackage\n"}}, |
| {NodeEnum::kFunctionDeclaration, |
| {"class c;\nfunction f;\nendfunction\nendclass\n"}}, |
| {NodeEnum::kFunctionDeclaration, |
| {"module m;\nfunction f;\nendfunction\nendmodule\n"}}, |
| }; |
| for (const auto& test : kTestCases) { |
| const absl::string_view code(test.token_data.code); |
| VerilogAnalyzer analyzer(code, "test-file"); |
| ASSERT_OK(analyzer.Analyze()) << "failed on:\n" << code; |
| const auto& root = analyzer.Data().SyntaxTree(); |
| |
| const auto statements = FindAllFunctionDeclarations(*root); |
| ASSERT_EQ(statements.size(), 1); |
| const auto& statement = *statements.front().match; |
| const auto* return_type = GetFunctionReturnType(statement); |
| // Expect a type node, even when type is implicit or empty. |
| ASSERT_NE(return_type, nullptr); |
| const auto return_type_span = verible::StringSpanOfSymbol(*return_type); |
| EXPECT_TRUE(return_type_span.empty()); |
| } |
| } |
| |
| TEST(GetFunctionReturnTypeTest, WithReturnType) { |
| constexpr int kTag = 1; // value not important |
| const SubtreeTestData kTestCases[] = { |
| {NodeEnum::kFunctionDeclaration, |
| {"function ", {kTag, "void"}, " f;endfunction\n"}}, |
| {NodeEnum::kFunctionDeclaration, |
| {"package p;\nfunction ", |
| {kTag, "int"}, |
| " f;\nendfunction\nendpackage\n"}}, |
| {NodeEnum::kFunctionDeclaration, |
| {"class c;\nfunction ", |
| {kTag, "foo_pkg::bar_t"}, |
| " f;\nendfunction\nendclass\n"}}, |
| {NodeEnum::kFunctionDeclaration, |
| {"module m;\nfunction ", |
| {kTag, "foo#(bar)"}, |
| " f;\nendfunction\nendmodule\n"}}, |
| }; |
| for (const auto& test : kTestCases) { |
| const absl::string_view code(test.token_data.code); |
| VerilogAnalyzer analyzer(code, "test-file"); |
| const absl::string_view code_copy(analyzer.Data().Contents()); |
| ASSERT_OK(analyzer.Analyze()) << "failed on:\n" << code; |
| const auto& root = analyzer.Data().SyntaxTree(); |
| |
| const auto statements = FindAllFunctionDeclarations(*root); |
| ASSERT_EQ(statements.size(), 1); |
| const auto& statement = *statements.front().match; |
| const auto* return_type = GetFunctionReturnType(statement); |
| ASSERT_NE(return_type, nullptr); |
| const auto return_type_span = verible::StringSpanOfSymbol(*return_type); |
| EXPECT_FALSE(return_type_span.empty()); |
| |
| // TODO(b/151371397): Refactor this test code along with |
| // common/analysis/linter_test_util.h to be able to compare set-symmetric |
| // differences in 'findings'. |
| |
| // Find the tokens, rebased into the other buffer. |
| const auto expected_excerpts = |
| test.token_data.FindImportantTokens(code_copy); |
| ASSERT_EQ(expected_excerpts.size(), 1); |
| // Compare the string_views and their exact spans. |
| const auto expected_span = expected_excerpts.front().text(); |
| ASSERT_TRUE(verible::IsSubRange(return_type_span, code_copy)); |
| ASSERT_TRUE(verible::IsSubRange(expected_span, code_copy)); |
| EXPECT_EQ(return_type_span, expected_span); |
| EXPECT_TRUE(verible::BoundsEqual(return_type_span, expected_span)); |
| } |
| } |
| |
| TEST(GetFunctionFormalPortsGroupTest, NoFormalPorts) { |
| const SubtreeTestData kTestCases[] = { |
| {NodeEnum::kFunctionDeclaration, {"function f;endfunction\n"}}, |
| {NodeEnum::kFunctionDeclaration, |
| {"package p;\nfunction f;\nendfunction\nendpackage\n"}}, |
| {NodeEnum::kFunctionDeclaration, |
| {"class c;\nfunction f;\nendfunction\nendclass\n"}}, |
| {NodeEnum::kFunctionDeclaration, |
| {"module m;\nfunction f;\nendfunction\nendmodule\n"}}, |
| }; |
| for (const auto& test : kTestCases) { |
| const absl::string_view code(test.token_data.code); |
| VerilogAnalyzer analyzer(code, "test-file"); |
| ASSERT_OK(analyzer.Analyze()) << "failed on:\n" << code; |
| const auto& root = analyzer.Data().SyntaxTree(); |
| |
| const auto statements = FindAllFunctionDeclarations(*root); |
| ASSERT_EQ(statements.size(), 1); |
| const auto& statement = *statements.front().match; |
| const auto* return_type = GetFunctionFormalPortsGroup(statement); |
| EXPECT_EQ(return_type, nullptr); |
| } |
| } |
| |
| TEST(GetFunctionFormalPortsGroupTest, WithFormalPorts) { |
| constexpr int kTag = 1; // value not important |
| const SubtreeTestData kTestCases[] = { |
| {NodeEnum::kFunctionDeclaration, |
| {"function f", {kTag, "()"}, ";endfunction\n"}}, |
| {NodeEnum::kFunctionDeclaration, |
| {"package p;\nfunction f", |
| {kTag, "(string s)"}, |
| ";\nendfunction\nendpackage\n"}}, |
| {NodeEnum::kFunctionDeclaration, |
| {"class c;\nfunction f", |
| {kTag, "(int i, string s)"}, |
| ";\nendfunction\nendclass\n"}}, |
| {NodeEnum::kFunctionDeclaration, |
| {"module m;\nfunction f", |
| {kTag, "(input logic foo, bar)"}, |
| ";\nendfunction\nendmodule\n"}}, |
| }; |
| for (const auto& test : kTestCases) { |
| const absl::string_view code(test.token_data.code); |
| VerilogAnalyzer analyzer(code, "test-file"); |
| const absl::string_view code_copy(analyzer.Data().Contents()); |
| ASSERT_OK(analyzer.Analyze()) << "failed on:\n" << code; |
| const auto& root = analyzer.Data().SyntaxTree(); |
| |
| const auto statements = FindAllFunctionDeclarations(*root); |
| ASSERT_EQ(statements.size(), 1); |
| const auto& statement = *statements.front().match; |
| const auto* port_formals = GetFunctionFormalPortsGroup(statement); |
| ASSERT_NE(port_formals, nullptr); |
| const auto port_formals_span = verible::StringSpanOfSymbol(*port_formals); |
| EXPECT_FALSE(port_formals_span.empty()); |
| |
| // TODO(b/151371397): Refactor this test code along with |
| // common/analysis/linter_test_util.h to be able to compare set-symmetric |
| // differences in 'findings'. |
| |
| // Find the tokens, rebased into the other buffer. |
| const auto expected_excerpts = |
| test.token_data.FindImportantTokens(code_copy); |
| ASSERT_EQ(expected_excerpts.size(), 1); |
| // Compare the string_views and their exact spans. |
| const auto expected_span = expected_excerpts.front().text(); |
| ASSERT_TRUE(verible::IsSubRange(port_formals_span, code_copy)); |
| ASSERT_TRUE(verible::IsSubRange(expected_span, code_copy)); |
| EXPECT_EQ(port_formals_span, expected_span); |
| EXPECT_TRUE(verible::BoundsEqual(port_formals_span, expected_span)); |
| } |
| } |
| |
| TEST(GetFunctionHeaderTest, GetFunctionName) { |
| constexpr int kTag = 1; // value doesn't matter |
| const SyntaxTreeSearchTestCase kTestCases[] = { |
| {""}, |
| {"module m(); endmodule: m"}, |
| {"function ", |
| {kTag, "foo"}, |
| "();\n endfunction\n function ", |
| {kTag, "bar"}, |
| "()\n; endfunction"}, |
| {"function int ", |
| {kTag, "foo"}, |
| "();\n endfunction\n function ", |
| {kTag, "bar"}, |
| "()\n; endfunction"}, |
| {"function int ", |
| {kTag, "foo"}, |
| "(int a, bit x);\nreturn a;\n endfunction\n function ", |
| {kTag, "bar"}, |
| "()\n; endfunction"}, |
| |
| {"module my_module;\nfunction int ", |
| {kTag, "inner_function"}, |
| "(int my_args);\nreturn my_args;\nendfunction\nendmodule"}, |
| {"class function_class;\nfunction int ", |
| {kTag, "my_function"}, |
| "(input int a, b, output int c);\nc = a + b;\nreturn " |
| "c;\nendfunction\nendclass"}, |
| {"package my_pkg;\nfunction automatic int ", |
| {kTag, "my_function"}, |
| "(input int a, b, output int c);\nc = a + b;\nreturn " |
| "c;\nendfunction\nendpackage"}, |
| {"class m;\n virtual function int ", |
| {kTag, "my_fun"}, |
| "();\n return 10;\n endfunction\n endclass"}, |
| {"class m;\n static function int ", |
| {kTag, "my_fun"}, |
| "();\n return 10;\n endfunction\n endclass"}, |
| }; |
| for (const auto& test : kTestCases) { |
| const absl::string_view code(test.code); |
| VerilogAnalyzer analyzer(code, "test-file"); |
| const auto code_copy = analyzer.Data().Contents(); |
| ASSERT_OK(analyzer.Analyze()) << "failed on:\n" << code; |
| const auto& root = analyzer.Data().SyntaxTree(); |
| |
| const auto decls = FindAllFunctionDeclarations(*ABSL_DIE_IF_NULL(root)); |
| |
| std::vector<TreeSearchMatch> types; |
| for (const auto& decl : decls) { |
| const auto* type = GetFunctionName(*decl.match); |
| types.push_back(TreeSearchMatch{type, {/* ignored context */}}); |
| } |
| |
| std::ostringstream diffs; |
| EXPECT_TRUE(test.ExactMatchFindings(types, code_copy, &diffs)) |
| << "failed on:\n" |
| << code << "\ndiffs:\n" |
| << diffs.str(); |
| } |
| } |
| |
| TEST(GetFunctionBlockStatement, GetFunctionBody) { |
| constexpr int kTag = 1; // value doesn't matter |
| const SyntaxTreeSearchTestCase kTestCases[] = { |
| {""}, |
| {"module m(); endmodule: m"}, |
| {"function int foo(int a, bit x);\n", |
| {kTag, "return a;"}, |
| " endfunction\n"}, |
| {"module my_module;\nfunction int inner_function(int my_args);\n", |
| {kTag, "return my_args;"}, |
| "\nendfunction\nendmodule"}, |
| {"class function_class;\nfunction int my_function(input int a, b, output " |
| "int c);\n", |
| {kTag, "c = a + b;\nreturn c;"}, |
| "\nendfunction\nendclass"}, |
| {"package my_pkg;\nfunction automatic int my_function(input int a, b, " |
| "output int c);\n", |
| {kTag, "c = a + b;\nreturn c;"}, |
| "\nendfunction\nendpackage"}, |
| {"class m;\n virtual function int my_fun();\n ", |
| {kTag, "return 10;"}, |
| "\n endfunction\n endclass"}, |
| {"class m;\n static function int my_fun();\n ", |
| {kTag, "return 10;"}, |
| "\n endfunction\n endclass"}, |
| }; |
| for (const auto& test : kTestCases) { |
| const absl::string_view code(test.code); |
| VerilogAnalyzer analyzer(code, "test-file"); |
| const auto code_copy = analyzer.Data().Contents(); |
| ASSERT_OK(analyzer.Analyze()) << "failed on:\n" << code; |
| const auto& root = analyzer.Data().SyntaxTree(); |
| |
| const auto decls = FindAllFunctionDeclarations(*ABSL_DIE_IF_NULL(root)); |
| |
| std::vector<TreeSearchMatch> functions_body; |
| for (const auto& decl : decls) { |
| const auto& body = GetFunctionBlockStatementList(*decl.match); |
| functions_body.push_back(TreeSearchMatch{&body, {/* ignored context */}}); |
| } |
| |
| std::ostringstream diffs; |
| EXPECT_TRUE(test.ExactMatchFindings(functions_body, code_copy, &diffs)) |
| << "failed on:\n" |
| << code << "\ndiffs:\n" |
| << diffs.str(); |
| } |
| } |
| |
| } // namespace |
| } // namespace verilog |