blob: 61e9befcf83ec71ed1458c378554c02885ddde96 [file] [log] [blame]
// 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/analysis/checkers/create_object_name_match_rule.h"
#include <cstddef>
#include <memory>
#include <set>
#include <string>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "common/analysis/citation.h"
#include "common/analysis/lint_rule_status.h"
#include "common/analysis/matcher/bound_symbol_manager.h"
#include "common/text/concrete_syntax_leaf.h"
#include "common/text/concrete_syntax_tree.h"
#include "common/text/symbol.h"
#include "common/text/syntax_tree_context.h"
#include "common/text/token_info.h"
#include "common/util/casts.h"
#include "verilog/CST/verilog_nonterminals.h"
#include "verilog/analysis/descriptions.h"
#include "verilog/analysis/lint_rule_registry.h"
#include "verilog/parser/verilog_token_enum.h"
namespace verilog {
namespace analysis {
using verible::down_cast;
using verible::GetVerificationCitation;
using verible::LintRuleStatus;
using verible::LintViolation;
using verible::SyntaxTreeContext;
using verible::SyntaxTreeLeaf;
using verible::SyntaxTreeNode;
using verible::TokenInfo;
// Register CreateObjectNameMatchRule
VERILOG_REGISTER_LINT_RULE(CreateObjectNameMatchRule);
absl::string_view CreateObjectNameMatchRule::Name() {
return "create-object-name-match";
}
const char CreateObjectNameMatchRule::kTopic[] = "naming";
std::string CreateObjectNameMatchRule::GetDescription(
DescriptionType description_type) {
return absl::StrCat(
"Checks that the \'name\' argument of ",
Codify("type_id::create()", description_type),
" matches the name of the variable to which it is assigned. See ",
GetVerificationCitation(kTopic), ".");
}
// Returns true if the underyling unqualified identifiers matches `name`,
// and returns false if the necessary conditions are not met.
// TODO(fangism): This function will be useful to many other analyses.
// Make public and refactor.
static bool UnqualifiedIdEquals(const SyntaxTreeNode& node,
absl::string_view name) {
if (node.MatchesTag(NodeEnum::kUnqualifiedId)) {
if (!node.children().empty()) {
// The one-and-only child is the SymbolIdentifier token
const auto& leaf_ptr =
down_cast<const SyntaxTreeLeaf*>(node.children().front().get());
if (leaf_ptr != nullptr) {
const TokenInfo& token = leaf_ptr->get();
return token.token_enum() == SymbolIdentifier && token.text() == name;
}
}
}
return false;
}
// Returns true if the qualified call is in the form "<any>::type_id::create".
// TODO(fangism): Refactor into QualifiedCallEndsWith().
static bool QualifiedCallIsTypeIdCreate(
const SyntaxTreeNode& qualified_id_node) {
const auto& children = qualified_id_node.children();
const size_t num_children = children.size();
// Allow for more than 3 segments, in case of package qualification, e.g.
// my_pkg::class_type::type_id::create.
// 5: 3 segments + 2 separators (in alternation), e.g. A::B::C
if (qualified_id_node.children().size() >= 5) {
const auto* create_leaf_ptr =
down_cast<const SyntaxTreeNode*>(children.back().get());
const auto* type_id_leaf_ptr =
down_cast<const SyntaxTreeNode*>(children[num_children - 3].get());
if (create_leaf_ptr != nullptr && type_id_leaf_ptr != nullptr) {
return UnqualifiedIdEquals(*create_leaf_ptr, "create") &&
UnqualifiedIdEquals(*type_id_leaf_ptr, "type_id");
}
}
return false;
}
// Returns string_view of `text` with outermost double-quotes removed.
// If `text` is not wrapped in quotes, return it as-is.
static absl::string_view StripOuterQuotes(absl::string_view text) {
if (!text.empty() && text[0] == '\"') {
return text.substr(1, text.length() - 2);
}
return text;
}
// Returns token information for a single string literal expression, or nullptr
// if the expression is not a string literal.
// `expr_node` should be a SyntaxTreeNode tagged as an expression.
static const TokenInfo* ExtractStringLiteralToken(
const SyntaxTreeNode& expr_node) {
if (!expr_node.MatchesTag(NodeEnum::kExpression)) return nullptr;
// this check is limited to only checking string literal leaf tokens
if (expr_node.children().front().get()->Kind() !=
verible::SymbolKind::kLeaf) {
return nullptr;
}
const auto* leaf_ptr =
down_cast<const SyntaxTreeLeaf*>(expr_node.children().front().get());
if (leaf_ptr != nullptr) {
const TokenInfo& token = leaf_ptr->get();
if (token.token_enum() == TK_StringLiteral) {
return &token;
}
}
return nullptr;
}
// Returns the first expression from an argument list, if it exists.
static const SyntaxTreeNode* GetFirstExpressionFromArgs(
const SyntaxTreeNode& args_node) {
if (!args_node.children().empty()) {
const auto& first_arg = args_node.children().front();
if (const auto* first_expr =
down_cast<const SyntaxTreeNode*>(first_arg.get())) {
return first_expr;
}
}
return nullptr;
}
// Returns a diagnostic message for this lint violation.
static std::string FormatReason(absl::string_view decl_name,
absl::string_view name_text) {
return absl::StrCat(
"The \'name\' argument of type_id::create() must match the name of "
"the variable to which it is assigned: ",
decl_name, ", got: ", name_text, ". ");
}
void CreateObjectNameMatchRule::HandleSymbol(const verible::Symbol& symbol,
const SyntaxTreeContext& context) {
// Check for assignments that match the pattern.
verible::matcher::BoundSymbolManager manager;
if (!create_assignment_matcher_.Matches(symbol, &manager)) return;
// Extract named bindings for matched nodes within this match.
if (const auto* lval = manager.GetAs<SyntaxTreeLeaf>("lval")) {
const TokenInfo& lval_token = lval->get();
if (lval_token.token_enum() != SymbolIdentifier) return;
const auto* call = manager.GetAs<SyntaxTreeNode>("func");
const auto* args = manager.GetAs<SyntaxTreeNode>("args");
if (call == nullptr && args == nullptr) return;
if (!QualifiedCallIsTypeIdCreate(*call)) return;
// The first argument is a string that must match the variable name, lval.
if (const auto* expr = GetFirstExpressionFromArgs(*args)) {
if (const TokenInfo* name_token = ExtractStringLiteralToken(*expr)) {
if (StripOuterQuotes(name_token->text()) != lval_token.text()) {
violations_.insert(LintViolation(
*name_token,
FormatReason(lval_token.text(), name_token->text())));
}
}
}
}
}
LintRuleStatus CreateObjectNameMatchRule::Report() const {
return LintRuleStatus(violations_, Name(), GetVerificationCitation(kTopic));
}
} // namespace analysis
} // namespace verilog