| // 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/verilog_project.h" | |
| #include <iostream> | |
| #include <memory> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/status/status.h" | |
| #include "absl/strings/ascii.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_join.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/time/time.h" | |
| #include "absl/types/optional.h" | |
| #include "common/text/text_structure.h" | |
| #include "common/util/file_util.h" | |
| #include "common/util/logging.h" | |
| #include "verilog/analysis/verilog_analyzer.h" | |
| namespace verilog { | |
| // All files we process with the verilog project, essentially applications that | |
| // build a symbol table (project-tool, kythe-indexer) only benefit from | |
| // processing the same sequence of tokens a synthesis tool sees. | |
| static constexpr verilog::VerilogPreprocess::Config kPreprocessConfig{ | |
| .filter_branches = true, | |
| }; | |
| VerilogSourceFile::VerilogSourceFile(absl::string_view referenced_path, | |
| const absl::Status& status) | |
| : referenced_path_(referenced_path), status_(status) {} | |
| absl::Status VerilogSourceFile::Open() { | |
| // Don't re-open. analyzed_structure_ should be set/written once only. | |
| if (state_ != State::kInitialized) return status_; | |
| // Load file contents. | |
| auto content_status = verible::file::GetContentAsMemBlock(ResolvedPath()); | |
| status_ = content_status.status(); | |
| if (!status_.ok()) return status_; | |
| content_ = std::move(*content_status); | |
| state_ = State::kOpened; | |
| return status_; // status_ is Ok here. | |
| } | |
| absl::string_view VerilogSourceFile::GetContent() const { | |
| return content_ ? content_->AsStringView() : ""; | |
| } | |
| absl::Status VerilogSourceFile::Parse() { | |
| // Parsed state is cached. | |
| if (state_ == State::kParsed) return status_; | |
| // Open file and load contents if not already done. | |
| status_ = Open(); | |
| if (!status_.ok()) return status_; | |
| // Lex, parse, populate underlying TextStructureView. | |
| analyzed_structure_ = std::make_unique<VerilogAnalyzer>( | |
| content_, ResolvedPath(), kPreprocessConfig); | |
| const absl::Time analyze_start = absl::Now(); | |
| status_ = analyzed_structure_->Analyze(); | |
| LOG(INFO) << "Analyzed " << ResolvedPath() << " in " | |
| << absl::ToInt64Milliseconds(absl::Now() - analyze_start) << "ms"; | |
| state_ = State::kParsed; | |
| return status_; | |
| } | |
| const verible::TextStructureView* VerilogSourceFile::GetTextStructure() const { | |
| if (analyzed_structure_ == nullptr) return nullptr; | |
| return &analyzed_structure_->Data(); | |
| } | |
| std::vector<std::string> VerilogSourceFile::ErrorMessages() const { | |
| std::vector<std::string> result; | |
| if (!analyzed_structure_) return result; | |
| result = analyzed_structure_->LinterTokenErrorMessages(false); | |
| return result; | |
| } | |
| std::ostream& operator<<(std::ostream& stream, | |
| const VerilogSourceFile& source) { | |
| stream << "referenced path: " << source.ReferencedPath() << std::endl; | |
| stream << "resolved path: " << source.ResolvedPath() << std::endl; | |
| stream << "corpus: " << source.Corpus() << std::endl; | |
| const auto status = source.Status(); | |
| stream << "status: " << (status.ok() ? "ok" : status.message()) << std::endl; | |
| const auto content = source.GetContent(); | |
| stream << "have content? " << (!content.empty() ? "yes" : "no") << std::endl; | |
| const auto* text_structure = source.GetTextStructure(); | |
| stream << "have text structure? " << (text_structure ? "yes" : "no") | |
| << std::endl; | |
| return stream; | |
| } | |
| absl::Status InMemoryVerilogSourceFile::Open() { | |
| state_ = State::kOpened; | |
| status_ = absl::OkStatus(); | |
| return status_; | |
| } | |
| absl::Status ParsedVerilogSourceFile::Open() { | |
| state_ = State::kOpened; | |
| status_ = absl::OkStatus(); | |
| return status_; | |
| } | |
| absl::Status ParsedVerilogSourceFile::Parse() { | |
| state_ = State::kParsed; | |
| status_ = absl::OkStatus(); | |
| return status_; | |
| } | |
| const verible::TextStructureView* ParsedVerilogSourceFile::GetTextStructure() | |
| const { | |
| return text_structure_; | |
| } | |
| absl::StatusOr<VerilogSourceFile*> VerilogProject::OpenFile( | |
| absl::string_view referenced_filename, absl::string_view resolved_filename, | |
| absl::string_view corpus) { | |
| const auto inserted = files_.emplace( | |
| referenced_filename, std::make_unique<VerilogSourceFile>( | |
| referenced_filename, resolved_filename, corpus)); | |
| CHECK(inserted.second); // otherwise, would have already returned above | |
| const auto file_iter = inserted.first; | |
| VerilogSourceFile& file(*file_iter->second); | |
| // Read the file's contents. | |
| const absl::Status status = file.Open(); | |
| if (!status.ok()) return status; | |
| // NOTE: string view maps don't support removal operation. The following block | |
| // is valid only if files won't be removed from the project. | |
| if (populate_string_maps_) { | |
| const absl::string_view contents(file.GetContent()); | |
| // Register the file's contents range in string_view_map_. | |
| string_view_map_.must_emplace(contents); | |
| // Map the start of the file's contents to its corresponding owner | |
| // VerilogSourceFile. | |
| const auto map_inserted = | |
| buffer_to_analyzer_map_.emplace(contents.begin(), file_iter); | |
| CHECK(map_inserted.second); | |
| } | |
| return &file; | |
| } | |
| bool VerilogProject::RemoveRegisteredFile( | |
| absl::string_view referenced_filename) { | |
| CHECK(!populate_string_maps_) | |
| << "Removing of files added to string maps is not supported! Disable " | |
| "populating string maps."; | |
| if (files_.erase(std::string(referenced_filename)) == 1) { | |
| LOG(INFO) << "Removed " << referenced_filename << " from the project."; | |
| return true; | |
| } | |
| for (const auto& include_path : include_paths_) { | |
| const std::string resolved_filename = | |
| verible::file::JoinPath(include_path, referenced_filename); | |
| if (files_.erase(resolved_filename) == 1) { | |
| LOG(INFO) << "Removed " << resolved_filename << " from the project."; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| VerilogSourceFile* VerilogProject::LookupRegisteredFileInternal( | |
| absl::string_view referenced_filename) const { | |
| const auto opened_file = FindOpenedFile(referenced_filename); | |
| if (opened_file) { | |
| if (!opened_file->ok()) { | |
| return nullptr; | |
| } | |
| return opened_file->value(); | |
| } | |
| // Check if this is already opened include file | |
| for (const auto& include_path : include_paths_) { | |
| const std::string resolved_filename = | |
| verible::file::JoinPath(include_path, referenced_filename); | |
| const auto opened_file = FindOpenedFile(resolved_filename); | |
| if (opened_file) { | |
| return opened_file->ok() ? opened_file->value() : nullptr; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| absl::optional<absl::StatusOr<VerilogSourceFile*>> | |
| VerilogProject::FindOpenedFile(absl::string_view filename) const { | |
| const auto found = files_.find(filename); | |
| if (found != files_.end()) { | |
| const auto status = found->second->Status(); | |
| if (!status.ok()) return status; | |
| return found->second.get(); | |
| } | |
| return absl::nullopt; | |
| } | |
| absl::StatusOr<VerilogSourceFile*> VerilogProject::OpenTranslationUnit( | |
| absl::string_view referenced_filename) { | |
| // Check for a pre-existing entry to avoid duplicate files. | |
| { | |
| const auto opened_file = FindOpenedFile(referenced_filename); | |
| if (opened_file) { | |
| return *opened_file; | |
| } | |
| } | |
| // Locate the file among the base paths. | |
| const std::string resolved_filename = | |
| verible::file::JoinPath(TranslationUnitRoot(), referenced_filename); | |
| // Check if this is already opened file | |
| { | |
| const auto opened_file = FindOpenedFile(resolved_filename); | |
| if (opened_file) { | |
| return *opened_file; | |
| } | |
| } | |
| return OpenFile(referenced_filename, resolved_filename, Corpus()); | |
| } | |
| absl::Status VerilogProject::IncludeFileNotFoundError( | |
| absl::string_view referenced_filename) const { | |
| return absl::NotFoundError(absl::StrCat( | |
| "Unable to find '", referenced_filename, | |
| "' among the included paths: ", absl::StrJoin(include_paths_, ", "))); | |
| } | |
| absl::StatusOr<VerilogSourceFile*> VerilogProject::OpenIncludedFile( | |
| absl::string_view referenced_filename) { | |
| VLOG(1) << __FUNCTION__ << ", referenced: " << referenced_filename; | |
| // Check for a pre-existing entry to avoid duplicate files. | |
| { | |
| const auto opened_file = FindOpenedFile(referenced_filename); | |
| if (opened_file) { | |
| return *opened_file; | |
| } | |
| } | |
| // Check if this is already opened include file | |
| for (const auto& include_path : include_paths_) { | |
| const std::string resolved_filename = | |
| verible::file::JoinPath(include_path, referenced_filename); | |
| const auto opened_file = FindOpenedFile(resolved_filename); | |
| if (opened_file) { | |
| return *opened_file; | |
| } | |
| } | |
| // Locate the file among the base paths. | |
| for (const auto& include_path : include_paths_) { | |
| const std::string resolved_filename = | |
| verible::file::JoinPath(include_path, referenced_filename); | |
| if (verible::file::FileExists(resolved_filename).ok()) { | |
| VLOG(2) << "File '" << resolved_filename << "' exists. Resolved from '" | |
| << referenced_filename << "'"; | |
| return OpenFile(referenced_filename, resolved_filename, Corpus()); | |
| } | |
| VLOG(2) << "Checked for file'" << resolved_filename | |
| << "', but not found. Resolved from '" << referenced_filename | |
| << "'"; | |
| } | |
| // Not found in any path. Cache this status. | |
| const auto inserted = files_.emplace( | |
| referenced_filename, | |
| std::make_unique<VerilogSourceFile>( | |
| referenced_filename, IncludeFileNotFoundError(referenced_filename))); | |
| CHECK(inserted.second) << "Not-found file should have been recorded as such."; | |
| return inserted.first->second->Status(); | |
| } | |
| void VerilogProject::AddVirtualFile(absl::string_view resolved_filename, | |
| absl::string_view content) { | |
| const auto inserted = files_.emplace( | |
| resolved_filename, | |
| std::make_unique<InMemoryVerilogSourceFile>( | |
| resolved_filename, std::make_shared<verible::StringMemBlock>(content), | |
| /*corpus=*/"")); | |
| CHECK(inserted.second); | |
| } | |
| const VerilogSourceFile* VerilogProject::LookupFileOrigin( | |
| absl::string_view content_substring) const { | |
| CHECK(populate_string_maps_) | |
| << "Populating string maps must be enabled for LookupFileOrigin!"; | |
| // Look for corresponding source text (superstring) buffer start. | |
| const auto found_superstring = string_view_map_.find(content_substring); | |
| if (found_superstring == string_view_map_.end()) return nullptr; | |
| const absl::string_view::const_iterator buffer_start = | |
| found_superstring->first; | |
| // Reverse-lookup originating file based on buffer start. | |
| const auto found_file = buffer_to_analyzer_map_.find(buffer_start); | |
| if (found_file == buffer_to_analyzer_map_.end()) return nullptr; | |
| const VerilogSourceFile* file = found_file->second->second.get(); | |
| return file; | |
| } | |
| } // namespace verilog |