Fix handling input file path separator (#4515) (#4516)

This commit is contained in:
Anthony Donlon 2023-09-27 03:42:15 +08:00 committed by GitHub
parent be45a9b7d5
commit 229ce1aecf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 335 additions and 206 deletions

View File

@ -636,7 +636,7 @@ public:
puts("vlSymsp->_traceDumpOpen();\n");
} else {
puts("VL_PRINTF_MT(\"-Info: ");
puts(protect(nodep->fileline()->filename()));
puts(V3OutFormatter::quoteNameControls(protect(nodep->fileline()->filename())));
puts(":");
puts(cvtToStr(nodep->fileline()->lineno()));
puts(": $dumpvar ignored, as Verilated without --trace");

View File

@ -43,15 +43,11 @@ class CMakeEmitter final {
template <typename List>
static string cmake_list(const List& strs) {
string s;
if (strs.begin() != strs.end()) {
s.append("\"");
s.append(VString::quoteAny(*strs.begin(), '"', '\\'));
s.append("\"");
for (typename List::const_iterator it = ++strs.begin(); it != strs.end(); ++it) {
s.append(" \"");
s.append(VString::quoteAny(*it, '"', '\\'));
s.append("\"");
}
for (auto it = strs.begin(); it != strs.end(); ++it) {
s += '"';
s += V3OutFormatter::quoteNameControls(*it);
s += '"';
if (it != strs.end()) s += ' ';
}
return s;
}
@ -63,13 +59,13 @@ class CMakeEmitter final {
static void cmake_set_raw(std::ofstream& of, const string& name, const string& raw_value,
const string& cache_type = "", const string& docstring = "") {
of << "set(" << name << " " << raw_value;
if (!cache_type.empty()) of << " CACHE " << cache_type << " \"" << docstring << "\"";
if (!cache_type.empty()) of << " CACHE " << cache_type << " \"" << docstring << '"';
of << ")\n";
}
static void cmake_set(std::ofstream& of, const string& name, const string& value,
const string& cache_type = "", const string& docstring = "") {
const string raw_value = "\"" + value + "\"";
const string raw_value = '"' + value + '"';
cmake_set_raw(of, name, raw_value, cache_type, docstring);
}
@ -88,9 +84,10 @@ class CMakeEmitter final {
*of << "# which becomes available after executing `find_package(verilator).\n";
*of << "\n### Constants...\n";
cmake_set(*of, "PERL", V3Options::getenvPERL(), "FILEPATH",
"Perl executable (from $PERL)");
cmake_set(*of, "VERILATOR_ROOT", V3Options::getenvVERILATOR_ROOT(), "PATH",
cmake_set(*of, "PERL", V3OutFormatter::quoteNameControls(V3Options::getenvPERL()),
"FILEPATH", "Perl executable (from $PERL)");
cmake_set(*of, "VERILATOR_ROOT",
V3OutFormatter::quoteNameControls(V3Options::getenvVERILATOR_ROOT()), "PATH",
"Path to Verilator kit (from $VERILATOR_ROOT)");
*of << "\n### Compiler flags...\n";

View File

@ -154,9 +154,10 @@ public:
}
of.puts("\n### Constants...\n");
of.puts("# Perl executable (from $PERL)\n");
of.puts("PERL = " + V3Options::getenvPERL() + "\n");
of.puts("PERL = " + V3OutFormatter::quoteNameControls(V3Options::getenvPERL()) + "\n");
of.puts("# Path to Verilator kit (from $VERILATOR_ROOT)\n");
of.puts("VERILATOR_ROOT = " + V3Options::getenvVERILATOR_ROOT() + "\n");
of.puts("VERILATOR_ROOT = "
+ V3OutFormatter::quoteNameControls(V3Options::getenvVERILATOR_ROOT()) + "\n");
of.puts("# SystemC include directory with systemc.h (from $SYSTEMC_INCLUDE)\n");
of.puts(string{"SYSTEMC_INCLUDE ?= "} + V3Options::getenvSYSTEMC_INCLUDE() + "\n");
of.puts("# SystemC library directory with libsystemc.a (from $SYSTEMC_LIBDIR)\n");

View File

@ -16,6 +16,7 @@
// clang-format off
#include "V3Error.h"
#include "V3Os.h"
#ifndef V3ERROR_NO_GLOBAL_
# include "V3Ast.h"
# include "V3Global.h"
@ -262,13 +263,8 @@ void V3Error::init() {
string V3Error::lineStr(const char* filename, int lineno) VL_PURE {
std::ostringstream out;
const char* const fnslashp = std::strrchr(filename, '/');
if (fnslashp) filename = fnslashp + 1;
out << filename << ":" << std::dec << lineno << ":";
const char* const spaces = " ";
size_t numsp = out.str().length();
if (numsp > 20) numsp = 20;
out << (spaces + numsp);
out << V3Os::filenameNonDir(filename) << ":" << std::dec << lineno << ":";
out << std::string(std::max<int>(0, 20 - static_cast<int>(out.str().length())), ' ');
return out.str();
}

View File

@ -859,7 +859,7 @@ void V3OutFormatter::putcNoTracking(char chr) {
string V3OutFormatter::quoteNameControls(const string& namein,
V3OutFormatter::Language lang) VL_PURE {
// Encode control chars into output-appropriate escapes
// Reverse is V3Parse::deQuote
// Reverse is VString::unquoteSVString
string out;
if (lang == LA_XML) {
// Encode chars into XML string

View File

@ -203,63 +203,62 @@ string FileLine::xmlDetailedLocation() const {
}
string FileLine::lineDirectiveStrg(int enterExit) const {
return std::string{"`line "} + cvtToStr(lastLineno()) + " \"" + filename() + "\" "
+ cvtToStr(enterExit) + "\n";
return std::string{"`line "} + cvtToStr(lastLineno()) + " \""
+ V3OutFormatter::quoteNameControls(filename()) + "\" " + cvtToStr(enterExit) + "\n";
}
void FileLine::lineDirective(const char* textp, int& enterExitRef) {
// Handle `line directive
// Does not parse streamNumber/streamLineno as the next input token
// will come from the same stream as the previous line.
do {
int lineNo;
// Skip `line
while (*textp && std::isspace(*textp)) ++textp;
while (*textp && !std::isspace(*textp)) ++textp;
while (*textp && std::isspace(*textp)) ++textp;
// Skip `line
while (*textp && std::isspace(*textp)) ++textp;
while (*textp && !std::isspace(*textp)) ++textp;
while (*textp && (std::isspace(*textp) || *textp == '"')) ++textp;
// Grab linenumber
const char* const ln = textp;
while (*textp && !std::isspace(*textp)) ++textp;
if (0 == strncmp(ln, "`__LINE__", textp - ln)) {
// Special case - see docs - don't change other than accounting for `line itself
lineNo = lineno() + 1;
} else if (std::isdigit(*ln)) {
lineNo = std::atoi(ln);
} else {
break; // Fail
}
lineno(lineNo);
while (*textp && (std::isspace(*textp))) ++textp;
// Grab linenumber
bool fail = false;
const char* const ln = textp;
while (*textp && !std::isspace(*textp)) ++textp;
if (0 == strncmp(ln, "`__LINE__", strlen("`__LINE__"))) {
// Special case - see docs - don't change other than accounting for `line itself
lineno(lineno() + 1);
}
if (std::isdigit(*ln)) {
lineno(std::atoi(ln));
} else {
fail = true;
}
while (*textp && (std::isspace(*textp))) ++textp;
if (*textp != '"') fail = true;
while (*textp && (std::isspace(*textp) || *textp == '"')) ++textp;
// Grab filename
if (*textp != '"') break; // Fail
const char* const fn = ++textp;
while (*textp && *textp != '"') ++textp;
if (*textp != '"') break; // Fail
string errMsg;
const string& parsedFilename = VString::unquoteSVString(string{fn, textp}, errMsg);
if (!errMsg.empty()) this->v3error(errMsg.c_str());
filename(parsedFilename);
++textp;
while (*textp && std::isspace(*textp)) ++textp;
// Grab filename
const char* const fn = textp;
while (*textp && !(std::isspace(*textp) || *textp == '"')) ++textp;
if (textp != fn) {
string strfn = fn;
strfn = strfn.substr(0, textp - fn);
filename(strfn);
} else {
fail = true;
}
// Grab level
if (!std::isdigit(*textp)) break; // Fail
const int level = std::atoi(textp);
if (level < 0 || level >= 3) break; // Fail
/// TODO: store lineno/filename only when the `line directive is valid
/// lineno(lineNo);
/// filename(filenameNew);
enterExitRef = level;
return;
} while (false);
// Grab level
while (*textp && (std::isspace(*textp) || *textp == '"')) ++textp;
if (std::isdigit(*textp)) {
enterExitRef = std::atoi(textp);
if (enterExitRef >= 3) fail = true;
} else {
enterExitRef = 0;
fail = true;
}
if (fail && v3Global.opt.pedantic()) {
v3error("`line was not properly formed with '`line number \"filename\" level'\n");
}
// printf ("PPLINE %d '%s'\n", s_lineno, s_filename.c_str());
// Fail
// TODO: show correct place of the code
v3error("`line was not properly formed with '`line number \"filename\" level'\n");
enterExitRef = 0;
}
void FileLine::forwardToken(const char* textp, size_t size, bool trackLines) {
@ -293,12 +292,7 @@ FileLine* FileLine::copyOrSameFileLine() {
string FileLine::filebasename() const VL_MT_SAFE { return V3Os::filenameNonDir(filename()); }
string FileLine::filebasenameNoExt() const {
string name = filebasename();
string::size_type pos;
if ((pos = name.find('.')) != string::npos) name = name.substr(0, pos);
return name;
}
string FileLine::filebasenameNoExt() const { return V3Os::filenameNonDirExt(filename()); }
string FileLine::firstColumnLetters() const VL_MT_SAFE {
const char a = ((firstColumn() / 26) % 26) + 'a';

View File

@ -81,19 +81,21 @@ public:
// ACCESSOR METHODS
void addIncDirUser(const string& incdir) {
const auto itFoundPair = m_incDirUserSet.insert(incdir);
const string& dir = V3Os::filenameCleanup(incdir);
const auto itFoundPair = m_incDirUserSet.insert(dir);
if (itFoundPair.second) {
// cppcheck-suppress stlFindInsert // cppcheck 1.90 bug
m_incDirUsers.push_back(incdir);
m_incDirFallbacks.remove(incdir); // User has priority over Fallback
m_incDirFallbackSet.erase(incdir); // User has priority over Fallback
m_incDirUsers.push_back(dir);
m_incDirFallbacks.remove(dir); // User has priority over Fallback
m_incDirFallbackSet.erase(dir); // User has priority over Fallback
}
}
void addIncDirFallback(const string& incdir) {
if (m_incDirUserSet.find(incdir)
const string& dir = V3Os::filenameCleanup(incdir);
if (m_incDirUserSet.find(dir)
== m_incDirUserSet.end()) { // User has priority over Fallback
const auto itFoundPair = m_incDirFallbackSet.insert(incdir);
if (itFoundPair.second) m_incDirFallbacks.push_back(incdir);
const auto itFoundPair = m_incDirFallbackSet.insert(dir);
if (itFoundPair.second) m_incDirFallbacks.push_back(dir);
}
}
void addLangExt(const string& langext, const V3LangCode& lc) {
@ -508,20 +510,16 @@ string V3Options::fileExists(const string& filename) {
return ""; // Not found
}
// Check if it is a directory, ignore if so
string filenameOut = V3Os::filenameFromDirBase(dir, basename);
string filenameOut = V3Os::filenameJoin(dir, basename);
if (!fileStatNormal(filenameOut)) return ""; // Directory
return filenameOut;
}
string V3Options::filePathCheckOneDir(const string& modname, const string& dirname) {
for (const string& i : m_impp->m_libExtVs) {
const string fn = V3Os::filenameFromDirBase(dirname, modname + i);
const string fn = V3Os::filenameJoin(dirname, modname + i);
string exists = fileExists(fn);
if (exists != "") {
// Strip ./, it just looks ugly
if (exists.substr(0, 2) == "./") exists.erase(0, 2);
return exists;
}
if (exists != "") return exists;
}
return "";
}
@ -548,29 +546,30 @@ string V3Options::filePath(FileLine* fl, const string& modname, const string& la
// Find a filename to read the specified module name,
// using the incdir and libext's.
// Return "" if not found.
if (!V3Os::filenameIsRel(modname)) {
// modname is an absolute path, so can find getStdPackagePath()
string exists = filePathCheckOneDir(modname, "");
const string filename = V3Os::filenameCleanup(modname);
if (!V3Os::filenameIsRel(filename)) {
// filename is an absolute path, so can find getStdPackagePath()
string exists = filePathCheckOneDir(filename, "");
if (exists != "") return exists;
}
for (const string& dir : m_impp->m_incDirUsers) {
string exists = filePathCheckOneDir(modname, dir);
string exists = filePathCheckOneDir(filename, dir);
if (exists != "") return exists;
}
for (const string& dir : m_impp->m_incDirFallbacks) {
string exists = filePathCheckOneDir(modname, dir);
string exists = filePathCheckOneDir(filename, dir);
if (exists != "") return exists;
}
if (m_relativeIncludes) {
const string exists = filePathCheckOneDir(modname, lastpath);
const string exists = filePathCheckOneDir(filename, lastpath);
if (exists != "") return V3Os::filenameRealPath(exists);
}
// Warn and return not found
if (errmsg != "") {
fl->v3error(errmsg + modname);
filePathLookedMsg(fl, modname);
fl->v3error(errmsg + filename);
filePathLookedMsg(fl, filename);
}
return "";
}
@ -592,13 +591,13 @@ void V3Options::filePathLookedMsg(FileLine* fl, const string& modname) {
std::cerr << V3Error::warnMoreStandalone() << "... Looked in:" << endl;
for (const string& dir : m_impp->m_incDirUsers) {
for (const string& ext : m_impp->m_libExtVs) {
const string fn = V3Os::filenameFromDirBase(dir, modname + ext);
const string fn = V3Os::filenameJoin(dir, modname + ext);
std::cerr << V3Error::warnMoreStandalone() << " " << fn << endl;
}
}
for (const string& dir : m_impp->m_incDirFallbacks) {
for (const string& ext : m_impp->m_libExtVs) {
const string fn = V3Os::filenameFromDirBase(dir, modname + ext);
const string fn = V3Os::filenameJoin(dir, modname + ext);
std::cerr << V3Error::warnMoreStandalone() << " " << fn << endl;
}
}
@ -657,7 +656,7 @@ string V3Options::getenvMAKEFLAGS() { //
}
string V3Options::getenvPERL() { //
return V3Os::getenvStr("PERL", "perl");
return V3Os::filenameCleanup(V3Os::getenvStr("PERL", "perl"));
}
string V3Options::getenvSYSTEMC() {
@ -669,7 +668,7 @@ string V3Options::getenvSYSTEMC() {
var = defenv;
V3Os::setenvStr("SYSTEMC", var, "Hardcoded at build time");
}
return var;
return V3Os::filenameCleanup(var);
}
string V3Options::getenvSYSTEMC_ARCH() {
@ -718,9 +717,9 @@ string V3Options::getenvSYSTEMC_INCLUDE() {
}
if (var == "") {
const string sc = getenvSYSTEMC();
if (sc != "") var = sc + "/include";
if (sc != "") var = V3Os::filenameJoin(sc, "include");
}
return var;
return V3Os::filenameCleanup(var);
}
string V3Options::getenvSYSTEMC_LIBDIR() {
@ -735,9 +734,9 @@ string V3Options::getenvSYSTEMC_LIBDIR() {
if (var == "") {
const string sc = getenvSYSTEMC();
const string arch = getenvSYSTEMC_ARCH();
if (sc != "" && arch != "") var = sc + "/lib-" + arch;
if (sc != "" && arch != "") var = V3Os::filenameJoin(sc, "lib-" + arch);
}
return var;
return V3Os::filenameCleanup(var);
}
string V3Options::getenvVERILATOR_ROOT() {
@ -750,11 +749,11 @@ string V3Options::getenvVERILATOR_ROOT() {
V3Os::setenvStr("VERILATOR_ROOT", var, "Hardcoded at build time");
}
if (var == "") v3fatal("$VERILATOR_ROOT needs to be in environment\n");
return var;
return V3Os::filenameCleanup(var);
}
string V3Options::getStdPackagePath() {
return getenvVERILATOR_ROOT() + "/include/verilated_std.sv";
return V3Os::filenameJoin(getenvVERILATOR_ROOT(), "include", "verilated_std.sv");
}
string V3Options::getSupported(const string& var) {
@ -1890,7 +1889,8 @@ void V3Options::parseOptsFile(FileLine* fl, const string& filename, bool rel) VL
string V3Options::parseFileArg(const string& optdir, const string& relfilename) {
string filename = V3Os::filenameSubstitute(relfilename);
if (optdir != "." && V3Os::filenameIsRel(filename)) filename = optdir + "/" + filename;
if (optdir != "." && V3Os::filenameIsRel(filename))
filename = V3Os::filenameJoin(optdir, filename);
return filename;
}

View File

@ -124,15 +124,46 @@ void V3Os::setenvStr(const string& envvar, const string& value, const string& wh
//######################################################################
// Generic filename utilities
static bool isSlash(char ch) VL_PURE { return ch == '/' || ch == '\\'; }
#if defined(_WIN32) || defined(__MINGW32__)
static constexpr char V3OS_SLASH = '\\';
#else
static constexpr char V3OS_SLASH = '/';
#endif
string V3Os::filenameFromDirBase(const string& dir, const string& basename) VL_PURE {
// Don't return ./{filename} because if filename was absolute, that makes it relative
if (dir.empty() || dir == ".") {
return basename;
} else {
return dir + "/" + basename;
static bool isSlash(char ch) VL_PURE {
#if defined(_WIN32) || defined(__MINGW32__)
return ch == '/' || ch == '\\';
#else
return ch == '/';
#endif
}
string V3Os::filenameCleanup(const string& filename) VL_PURE {
string str;
str.reserve(filename.length());
bool lastIsSlash = false;
for (const char ch : filename) {
const bool lastIsSlashOld = lastIsSlash;
lastIsSlash = isSlash(ch);
if (lastIsSlash && lastIsSlashOld) continue;
str += ch;
}
if (str.size() > 1 && isSlash(str.back())) str.pop_back();
while (str.size() > 2 && str[0] == '.' && isSlash(str[1])) str.erase(0, 2);
return str;
}
string V3Os::filenameJoin(std::initializer_list<const std::string> paths) VL_PURE {
string fullpath;
for (const auto& item : paths) {
if (item.empty() || item == ".") {
continue;
} else {
if (!fullpath.empty()) fullpath += V3OS_SLASH;
fullpath += item;
}
}
return fullpath;
}
string V3Os::filenameDir(const string& filename) VL_PURE {
@ -144,7 +175,7 @@ string V3Os::filenameDir(const string& filename) VL_PURE {
if (it.base() == filename.begin()) {
return ".";
} else {
return {filename.begin(), (++it).base()};
return string{filename.begin(), (++it).base()};
}
}
@ -164,6 +195,10 @@ string V3Os::filenameNonExt(const string& filename) VL_PURE {
return base;
}
string V3Os::filenameNonDirExt(const string& filename) VL_PURE {
return filenameNonExt(filenameNonDir(filename));
}
string V3Os::filenameSubstitute(const string& filename) {
string result;
// cppcheck-has-bug-suppress unusedLabel
@ -392,3 +427,27 @@ int V3Os::system(const string& command) {
return exit_code;
}
}
void V3Os::selfTest() {
#ifdef VL_DEBUG
UASSERT_SELFTEST(string, filenameCleanup(""), "");
UASSERT_SELFTEST(string, filenameCleanup("."), ".");
UASSERT_SELFTEST(string, filenameCleanup(".."), "..");
UASSERT_SELFTEST(string, filenameCleanup("/"), "/");
UASSERT_SELFTEST(string, filenameCleanup("../"), "..");
UASSERT_SELFTEST(string, filenameCleanup("//"), "/");
UASSERT_SELFTEST(string, filenameCleanup("//."), "/.");
UASSERT_SELFTEST(string, filenameCleanup("./"), ".");
UASSERT_SELFTEST(string, filenameCleanup("././"), ".");
UASSERT_SELFTEST(string, filenameCleanup(".///"), ".");
UASSERT_SELFTEST(string, filenameCleanup("a"), "a");
UASSERT_SELFTEST(string, filenameCleanup("a/"), "a");
UASSERT_SELFTEST(string, filenameCleanup("a/b"), "a/b");
UASSERT_SELFTEST(string, filenameCleanup("././//./a/b"), "a/b");
UASSERT_SELFTEST(string, filenameCleanup(".//./a///"), "a");
UASSERT_SELFTEST(string, filenameCleanup("///a/./b///."), "/a/./b/.");
UASSERT_SELFTEST(string, filenameCleanup("aaa/bbb/ccc/"), "aaa/bbb/ccc");
UASSERT_SELFTEST(string, filenameCleanup("./aaa/bbb/ccc/"), "aaa/bbb/ccc");
UASSERT_SELFTEST(string, filenameCleanup("../aaa/bbb/ccc/"), "../aaa/bbb/ccc");
#endif
}

View File

@ -35,22 +35,28 @@ public:
static void setenvStr(const string& envvar, const string& value, const string& why);
// METHODS (generic filename utilities)
static string filenameFromDirBase(const string& dir, const string& basename) VL_PURE;
///< Return non-directory part of filename
///< @return concatenated path
static string filenameJoin(std::initializer_list<const std::string> paths) VL_PURE;
template <typename... Args>
static string filenameJoin(Args... args) VL_PURE {
return filenameJoin({args...});
};
///< @return file path without repeated separators and ./ prefix
static string filenameCleanup(const string& filename) VL_PURE;
///< @return non-directory part of filename
static string filenameNonDir(const string& filename) VL_PURE;
///< Return non-extensioned (no .) part of filename
///< @return non-extensioned (no .) part of filename
static string filenameNonExt(const string& filename) VL_PURE;
///< Return basename of filename
static string filenameNonDirExt(const string& filename) VL_PURE {
return filenameNonExt(filenameNonDir(filename));
}
///< Return directory part of filename
///< @return basename of filename
static string filenameNonDirExt(const string& filename) VL_PURE;
///< @return directory part of filename
static string filenameDir(const string& filename) VL_PURE;
/// Return filename with env vars removed
///< @return filename with env vars removed
static string filenameSubstitute(const string& filename);
///< Return realpath of filename
///< @return realpath of filename
static string filenameRealPath(const string& filename) VL_PURE;
static bool filenameIsRel(const string& filename) VL_PURE; ///< True if relative
///< @return filename is relative
static bool filenameIsRel(const string& filename) VL_PURE;
// METHODS (file utilities)
static string getline(std::istream& is, char delim = '\n');
@ -72,6 +78,7 @@ public:
// METHODS (sub command)
/// Run system command, returns the exit code of the child process.
static int system(const string& command);
static void selfTest();
};
#endif // Guard

View File

@ -251,63 +251,9 @@ AstVar* V3ParseGrammar::createVariable(FileLine* fileline, const string& name,
return nodep;
}
string V3ParseGrammar::deQuote(FileLine* fileline, string text) {
// Fix up the quoted strings the user put in, for example "\"" becomes "
// Reverse is V3OutFormatter::quoteNameControls(...)
bool quoted = false;
string newtext;
unsigned char octal_val = 0;
int octal_digits = 0;
for (string::const_iterator cp = text.begin(); cp != text.end(); ++cp) {
if (quoted) {
if (std::isdigit(*cp)) {
octal_val = octal_val * 8 + (*cp - '0');
if (++octal_digits == 3) {
octal_digits = 0;
quoted = false;
newtext += octal_val;
}
} else {
if (octal_digits) {
// Spec allows 1-3 digits
octal_digits = 0;
quoted = false;
newtext += octal_val;
--cp; // Backup to reprocess terminating character as non-escaped
continue;
}
quoted = false;
if (*cp == 'n') {
newtext += '\n';
} else if (*cp == 'a') {
newtext += '\a'; // SystemVerilog 3.1
} else if (*cp == 'f') {
newtext += '\f'; // SystemVerilog 3.1
} else if (*cp == 'r') {
newtext += '\r';
} else if (*cp == 't') {
newtext += '\t';
} else if (*cp == 'v') {
newtext += '\v'; // SystemVerilog 3.1
} else if (*cp == 'x' && std::isxdigit(cp[1])
&& std::isxdigit(cp[2])) { // SystemVerilog 3.1
#define vl_decodexdigit(c) ((std::isdigit(c) ? ((c) - '0') : (std::tolower((c)) - 'a' + 10)))
newtext
+= static_cast<char>(16 * vl_decodexdigit(cp[1]) + vl_decodexdigit(cp[2]));
cp += 2;
} else if (std::isalnum(*cp)) {
fileline->v3error("Unknown escape sequence: \\" << *cp);
break;
} else {
newtext += *cp;
}
}
} else if (*cp == '\\') {
quoted = true;
octal_digits = 0;
} else {
newtext += *cp;
}
}
return newtext;
string V3ParseGrammar::unquoteString(FileLine* fileline, string text) {
string errMsg;
string res = VString::unquoteSVString(text, errMsg);
if (!errMsg.empty()) fileline->v3error(errMsg.c_str());
return res;
}

View File

@ -358,7 +358,7 @@ string V3PreProcImp::removeDefines(const string& text) {
string rtnsym = text;
for (int loopprevent = 0; loopprevent < 100; loopprevent++) {
string xsym = rtnsym;
if (xsym.substr(0, 1) == "`") xsym.replace(0, 1, "");
if (xsym[0] == '`') xsym.erase(0, 1);
if (defExists(xsym)) {
val = defValue(xsym);
if (val != rtnsym) {

View File

@ -663,7 +663,8 @@ std::pair<AstVarScope*, AstNodeStmt*> makeEvalLoop(AstNetlist* netlistp, const s
newcallp->dtypeSetVoid();
blockp->addNodesp(newcallp->makeStmt());
add("#endif\n");
add("VL_FATAL_MT(\"" + file + "\", " + line + ", \"\", ");
add("VL_FATAL_MT(\"" + V3OutFormatter::quoteNameControls(file) + "\", " + line
+ ", \"\", ");
add("\"" + name + " region did not converge.\");\n");
}

View File

@ -20,6 +20,7 @@
#include "V3String.h"
#include "V3Error.h"
#include "V3FileLine.h"
#ifndef V3ERROR_NO_GLOBAL_
#include "V3Global.h"
@ -130,6 +131,70 @@ string VString::escapeStringForPath(const string& str) {
return result;
}
static int vl_decodexdigit(char c) {
return std::isdigit(c) ? c - '0' : std::tolower(c) - 'a' + 10;
}
string VString::unquoteSVString(const string& text, string& errOut) {
bool quoted = false;
string newtext;
newtext.reserve(text.size());
unsigned char octal_val = 0;
int octal_digits = 0;
for (string::const_iterator cp = text.begin(); cp != text.end(); ++cp) {
if (quoted) {
if (std::isdigit(*cp)) {
octal_val = octal_val * 8 + (*cp - '0');
if (++octal_digits == 3) {
octal_digits = 0;
quoted = false;
newtext += octal_val;
}
} else {
if (octal_digits) {
// Spec allows 1-3 digits
octal_digits = 0;
quoted = false;
newtext += octal_val;
--cp; // Backup to reprocess terminating character as non-escaped
continue;
}
quoted = false;
if (*cp == 'n') {
newtext += '\n';
} else if (*cp == 'a') {
newtext += '\a'; // SystemVerilog 3.1
} else if (*cp == 'f') {
newtext += '\f'; // SystemVerilog 3.1
} else if (*cp == 'r') {
newtext += '\r';
} else if (*cp == 't') {
newtext += '\t';
} else if (*cp == 'v') {
newtext += '\v'; // SystemVerilog 3.1
} else if (*cp == 'x' && std::isxdigit(cp[1])
&& std::isxdigit(cp[2])) { // SystemVerilog 3.1
newtext
+= static_cast<char>(16 * vl_decodexdigit(cp[1]) + vl_decodexdigit(cp[2]));
cp += 2;
} else if (std::isalnum(*cp)) {
errOut = "Unknown escape sequence: \\";
errOut += *cp;
break;
} else {
newtext += *cp;
}
}
} else if (*cp == '\\') {
quoted = true;
octal_digits = 0;
} else {
newtext += *cp;
}
}
return newtext;
}
string VString::spaceUnprintable(const string& str) VL_PURE {
string result;
for (const char c : str) {

View File

@ -102,6 +102,9 @@ public:
// Surround a raw string by double quote and escape if necessary
// e.g. input abc's becomes "\"abc\'s\""
static string escapeStringForPath(const string& str);
// Convert SV quoted string input from source code to normal form.
// Reverse is V3OutFormatter::quoteNameControls(...)
static string unquoteSVString(const string& text, string& errOut);
// Escape path in Windows
// e.g. input `C:\Program Files\My Program\My Program.exe` becomes
// `C:\\Program\ Files\\My\ Program\\My\ Program.exe`

View File

@ -642,6 +642,7 @@ static void verilate(const string& argString) {
{
const V3MtDisabledLockGuard mtDisabler{v3MtDisabledLock()};
V3Os::selfTest();
VHashSha256::selfTest();
VSpellCheck::selfTest();
V3Graph::selfTest();
@ -782,7 +783,7 @@ int main(int argc, char** argv) {
V3PreShell::boot();
// Command option parsing
v3Global.opt.buildDepBin(VString::escapeStringForPath(argv[0]));
v3Global.opt.buildDepBin(V3Os::filenameCleanup(argv[0]));
v3Global.opt.parseOpts(new FileLine{FileLine::commandLineFilename()}, argc - 1, argv + 1);
// Validate settings (aka Boost.Program_options)

View File

@ -129,7 +129,7 @@ public:
AstNode* attrsp) VL_MT_DISABLED;
AstNode* createSupplyExpr(FileLine* fileline, const string& name, int value) VL_MT_DISABLED;
AstText* createTextQuoted(FileLine* fileline, const string& text) {
string newtext = deQuote(fileline, text);
string newtext = GRAMMARP->unquoteString(fileline, text);
return new AstText{fileline, newtext};
}
AstNode* createCellOrIfaceRef(FileLine* fileline, const string& name, AstPin* pinlistp,
@ -253,7 +253,7 @@ public:
return createArray(dtypep, rangearraysp, isPacked);
}
}
string deQuote(FileLine* fileline, string text) VL_MT_DISABLED;
string unquoteString(FileLine* fileline, string text) VL_MT_DISABLED;
void checkDpiVer(FileLine* fileline, const string& str) {
if (str != "DPI-C" && !v3Global.opt.bboxSys()) {
fileline->v3error("Unsupported DPI type '" << str << "': Use 'DPI-C'");
@ -5675,13 +5675,13 @@ parseRefBase<nodep>:
// yaSTRING shouldn't be used directly, instead via an abstraction below
str<strp>: // yaSTRING but with \{escapes} need decoded
yaSTRING { $$ = PARSEP->newString(GRAMMARP->deQuote($<fl>1, *$1)); }
yaSTRING { $$ = PARSEP->newString(GRAMMARP->unquoteString($<fl>1, *$1)); }
;
strAsInt<nodeExprp>:
yaSTRING
{ // Numeric context, so IEEE 1800-2017 11.10.3 "" is a "\000"
$$ = new AstConst{$<fl>1, AstConst::VerilogStringLiteral{}, GRAMMARP->deQuote($<fl>1, *$1)}; }
$$ = new AstConst{$<fl>1, AstConst::VerilogStringLiteral{}, GRAMMARP->unquoteString($<fl>1, *$1)}; }
;
strAsIntIgnore<nodeExprp>: // strAsInt, but never matches for when expr shouldn't parse strings

View File

@ -0,0 +1,16 @@
-Info: some file:100:1: aaaaaaaa
: ... note: In instance 't'
100 | $info("aaaaaaaa");
| ^~~~~
-Info: some file:101:1: bbbbbbbb
: ... note: In instance 't'
101 | $info("bbbbbbbb");
| ^~~~~
-Info: somefile.v:200:1: cccccccc
: ... note: In instance 't'
200 | $info("cccccccc");
| ^~~~~
-Info: /a/somefile.v:300:1: dddddddd
: ... note: In instance 't'
300 | $info("dddddddd");
| ^~~~~

18
test_regress/t/t_pp_line.pl Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2003 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
scenarios(linter => 1);
lint(
expect_filename => $Self->{golden_filename},
);
ok(1);
1;

View File

@ -0,0 +1,15 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2023 by Anthony Donlon.
// SPDX-License-Identifier: CC0-1.0
module t;
`line 100 "some file" 0
$info("aaaaaaaa");
$info("bbbbbbbb");
`line 200 "somefile.v" 0
$info("cccccccc");
`line 300 "/a/somefile.v" 0
$info("dddddddd");
endmodule

View File

@ -1,13 +1,22 @@
%Error: t/t_pp_line_bad.v:100:1: `line was not properly formed with '`line number "filename" level'
100 | `line 100
| ^
%Error: somefile:100:1: `line was not properly formed with '`line number "filename" level'
100 | `line 100
%Error: t/t_pp_line_bad.v:200:1: `line was not properly formed with '`line number "filename" level'
200 | `line 100
| ^
%Error: t/t_pp_line_bad.v:300:1: `line was not properly formed with '`line number "filename" level'
300 | `line 100
| ^
%Error: some file:400:1: `line was not properly formed with '`line number "filename" level'
400 | `line 100
| ^
%Error: somefile:500:1: `line was not properly formed with '`line number "filename" level'
500 | `line 100
| ^
%Error: some file:600:1: `line was not properly formed with '`line number "filename" level'
600 | `line 100
| ^
%Error: somefile:100:1: `line was not properly formed with '`line number "filename" level'
%Error: t/t_pp_line_bad.v:7:1: Define or directive not defined: '`line'
7 | `line
| ^~~~~
%Error: somefile:100:1: `line was not properly formed with '`line number "filename" level'
t/t_pp_line_bad.v:101:1: ... note: In file included from 't_pp_line_bad.v'
%Error: Exiting due to

View File

@ -8,10 +8,9 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
scenarios(vlt => 1);
scenarios(linter => 1);
lint(
verilator_flags2 => ["-Wpedantic"],
fails => 1,
expect_filename => $Self->{golden_filename},
);

View File

@ -6,6 +6,8 @@
`line
`line 100
`line 100 somefile 1
`line 100 "somefile"
`line 100 "somefile" 3
`line 200 somefile
`line 300 "somefile 1
`line 400 "some file"
`line 500 "somefile" 3
`line 600 "some file" 3