blob: 67cd4599cbb83dfb50ad681e0fe3fc3b3ddbd3cb [file] [log] [blame]
// Copyright 2017-2021 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/truncated_numeric_literal_rule.h"
#include <cmath>
#include <cstddef>
#include <set>
#include <string>
#include "absl/numeric/int128.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "common/analysis/lint_rule_status.h"
#include "common/analysis/matcher/bound_symbol_manager.h"
#include "common/analysis/matcher/matcher.h"
#include "common/text/concrete_syntax_leaf.h"
#include "common/text/config_utils.h"
#include "common/text/symbol.h"
#include "common/text/syntax_tree_context.h"
#include "common/text/token_info.h"
#include "common/util/logging.h"
#include "verilog/CST/numbers.h"
#include "verilog/CST/verilog_matchers.h"
#include "verilog/analysis/descriptions.h"
#include "verilog/analysis/lint_rule_registry.h"
namespace verilog {
namespace analysis {
using verible::down_cast;
using verible::LintRuleStatus;
using verible::LintViolation;
using verible::SyntaxTreeContext;
using verible::SyntaxTreeLeaf;
using verible::SyntaxTreeNode;
using verible::matcher::Matcher;
VERILOG_REGISTER_LINT_RULE(TruncatedNumericLiteralRule);
const LintRuleDescriptor& TruncatedNumericLiteralRule::GetDescriptor() {
static const LintRuleDescriptor d{
.name = "truncated-numeric-literal",
.topic = "number-literals",
.desc =
"Checks that numeric literals are not longer than their stated "
"bit-width to avoid undesired accidental truncation.",
};
return d;
}
static const Matcher& NumberMatcher() {
static const Matcher matcher(
NodekNumber(NumberHasConstantWidth().Bind("width"),
NumberHasBasedLiteral().Bind("literal")));
return matcher;
}
// Given a binary/oct/hex digit, return how many bits it occupies
static int digitBits(char digit, bool* is_lower_bound) {
if (digit == 'z' || digit == 'x' || digit == '?') {
*is_lower_bound = true;
return 1; // Minimum number of bits assumed
}
*is_lower_bound = false;
if (digit > '7')
return 4;
else if (digit > '3')
return 3;
else if (digit > '1')
return 2;
return 1;
}
static absl::string_view StripLeadingZeroes(absl::string_view str) {
const absl::string_view::const_iterator it =
std::find_if_not(str.begin(), str.end(), [](char c) { return c == '0'; });
return str.substr(it - str.begin());
}
// Return count of bits the given number occupies. Sometims we can only make
// a lower bound estimate, return that in "is_lower_bound".
static size_t GetBitWidthOfNumber(const BasedNumber& n, bool* is_lower_bound) {
const absl::string_view literal = StripLeadingZeroes(n.literal);
*is_lower_bound = true; // Can only estimate for the following two
if (literal.empty()) return 1; // all zeroes
if (literal[0] == '`') return 1; // Not dealing with macros.
*is_lower_bound = false; // Now, we strive for exact bits
switch (n.base) {
case 'h':
return digitBits(literal[0], is_lower_bound) + 4 * (literal.length() - 1);
case 'o':
return digitBits(literal[0], is_lower_bound) + 3 * (literal.length() - 1);
case 'b':
return literal.length();
case 'd': {
if (!isdigit(literal[0])) {
*is_lower_bound = true;
return 1; // Not dealing with ? or z
}
// Let's first try if we can parse it with regular means. Luckily,
// absl provides compiler-independent abstraction of 128 bit numbers,
// so we can parse most commonly used values accurately.
absl::uint128 number;
if (absl::SimpleAtoi(literal, &number)) {
int bits;
for (bits = 0; bits < 128 && number; ++bits) {
number >>= 1;
}
return bits;
}
*is_lower_bound = true; // Heuristic below only gives us a lower bound
// More than 128 bits. Best effort to establish at least a lower bound.
// We parse the number as double, so that parsing can keep track of
// pretty long numbers and we get log2 for 15-ish significant dec digits.
// This will create false negatives, i.e. undercounting required bits,
// TODO(hzeller): is there a cheap way to accurately determine bits used
// without fully parsing the decimal number ?
double v;
if (absl::SimpleAtod(literal, &v) && !std::isinf(v)) {
return std::max(129, static_cast<int>(ceil(log(v) / log(2))));
}
// Uh, more than 300-ish decimal digits ? ... rough estimation it is.
return ceil((literal.length() - 1) * log(10) / log(2));
} break;
}
return 0; // not reached.
}
void TruncatedNumericLiteralRule::HandleSymbol(
const verible::Symbol& symbol, const SyntaxTreeContext& context) {
verible::matcher::BoundSymbolManager manager;
if (!NumberMatcher().Matches(symbol, &manager)) return;
const auto* width_leaf = manager.GetAs<SyntaxTreeLeaf>("width");
const auto* literal_node = manager.GetAs<SyntaxTreeNode>("literal");
if (!width_leaf || !literal_node) return;
const auto width_text = width_leaf->get().text();
size_t width;
if (!absl::SimpleAtoi(width_text, &width)) return;
const auto& base_digit_part = literal_node->children();
const auto* base_leaf =
down_cast<const SyntaxTreeLeaf*>(base_digit_part[0].get());
const auto* digits_leaf =
down_cast<const SyntaxTreeLeaf*>(base_digit_part[1].get());
const auto base_text = base_leaf->get().text();
const auto digits_text = digits_leaf->get().text();
const BasedNumber number(base_text, digits_text);
bool is_lower_bound = false;
const size_t actual_width = GetBitWidthOfNumber(number, &is_lower_bound);
if (actual_width > width) {
violations_.insert(LintViolation(
digits_leaf->get(),
absl::StrCat("Number ", width_text, base_text, digits_text,
" occupies ", is_lower_bound ? "at least " : "",
actual_width, " bits, truncated to ", width, " bits."),
context));
// No autofix yet. In particular signed numbers might be hairy, and
// numbers for which we only have a lower bound.
}
}
LintRuleStatus TruncatedNumericLiteralRule::Report() const {
return LintRuleStatus(violations_, GetDescriptor());
}
} // namespace analysis
} // namespace verilog