kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/cxx/verifier/souffle_interpreter.cc (about)

     1  /*
     2   * Copyright 2020 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  #include "kythe/cxx/verifier/souffle_interpreter.h"
    18  
    19  #include <array>
    20  #include <cstddef>
    21  #include <functional>
    22  #include <memory>
    23  #include <optional>
    24  #include <string_view>
    25  #include <vector>
    26  
    27  #include "absl/base/no_destructor.h"
    28  #include "absl/container/flat_hash_map.h"
    29  #include "absl/log/check.h"
    30  #include "absl/log/log.h"
    31  #include "absl/strings/numbers.h"
    32  #include "absl/strings/str_cat.h"
    33  #include "interpreter/Engine.h"
    34  #include "kythe/cxx/verifier/assertion_ast.h"
    35  #include "kythe/cxx/verifier/assertions_to_souffle.h"
    36  #include "souffle/RamTypes.h"
    37  #include "souffle/io/IOSystem.h"
    38  #include "third_party/souffle/parse_transform.h"
    39  
    40  namespace kythe::verifier {
    41  namespace {
    42  class KytheReadStream : public souffle::ReadStream {
    43   public:
    44    explicit KytheReadStream(
    45        const std::map<std::string, std::string>& rw_operation,
    46        souffle::SymbolTable& symbol_table, souffle::RecordTable& record_table,
    47        const AnchorMap& anchors, const Database& database,
    48        const SymbolTable& dst)
    49        : souffle::ReadStream(rw_operation, symbol_table, record_table),
    50          anchors_(anchors),
    51          database_(database) {
    52      if (auto op = rw_operation.find("anchors"); op != rw_operation.end()) {
    53        anchor_it_ = anchors_.cbegin();
    54        database_it_ = database_.cend();
    55      } else {
    56        database_it_ = database_.cbegin();
    57        anchor_it_ = anchors_.cend();
    58      }
    59  #ifdef DEBUG_LOWERING
    60      FileHandlePrettyPrinter dprinter(stderr);
    61      for (const auto& d : database) {
    62        dprinter.Print("- ");
    63        d->Dump(dst, &dprinter);
    64        dprinter.Print("\n");
    65      }
    66      for (const auto& a : anchors) {
    67        dprinter.Print(
    68            absl::StrCat("@ ", a.first.first, ", ", a.first.second, " "));
    69        a.second->Dump(dst, &dprinter);
    70        dprinter.Print("\n");
    71      }
    72  #endif
    73      std::array<souffle::RamDomain, 5> fields = {0, 0, 0, 0, 0};
    74      empty_vname_ = record_table.pack(fields.data(), fields.size());
    75    }
    76  
    77   protected:
    78    souffle::Own<souffle::RamDomain[]> readNextTuple() override {
    79      if (database_it_ != database_.end()) {
    80        auto* t = (*database_it_)->AsApp()->rhs()->AsTuple();
    81        auto tuple = std::make_unique<souffle::RamDomain[]>(5);
    82        souffle::RamDomain vname[5];
    83        CopyVName(t->element(0)->AsApp()->rhs()->AsTuple(), vname);
    84        tuple[0] = recordTable.pack(vname, 5);
    85        tuple[1] = t->element(1)->AsIdentifier()->symbol();
    86        if (auto* id = t->element(2)->AsIdentifier(); id != nullptr) {
    87          // TODO(zarko): this should possibly be an explicit nil.
    88          tuple[2] = id->symbol();
    89        } else {
    90          CopyVName(t->element(2)->AsApp()->rhs()->AsTuple(), vname);
    91          tuple[2] = recordTable.pack(vname, 5);
    92        }
    93        tuple[3] = t->element(3)->AsIdentifier()->symbol();
    94        tuple[4] = t->element(4)->AsIdentifier()->symbol();
    95        ++database_it_;
    96        return tuple;
    97      } else if (anchor_it_ != anchors_.end()) {
    98        auto tuple = std::make_unique<souffle::RamDomain[]>(3);
    99        tuple[0] = anchor_it_->first.first;
   100        tuple[1] = anchor_it_->first.second;
   101        souffle::RamDomain vname[5];
   102        CopyVName(anchor_it_->second->AsApp()->rhs()->AsTuple(), vname);
   103        tuple[2] = recordTable.pack(vname, 5);
   104        ++anchor_it_;
   105        return tuple;
   106      }
   107      return nullptr;
   108    }
   109  
   110   private:
   111    void CopyVName(kythe::verifier::Tuple* vname,
   112                   souffle::RamDomain (&target)[5]) {
   113      // sig, corp, root, path, lang
   114      target[0] = vname->element(0)->AsIdentifier()->symbol();
   115      target[1] = vname->element(1)->AsIdentifier()->symbol();
   116      target[2] = vname->element(2)->AsIdentifier()->symbol();
   117      target[3] = vname->element(3)->AsIdentifier()->symbol();
   118      target[4] = vname->element(4)->AsIdentifier()->symbol();
   119    }
   120    const AnchorMap& anchors_;
   121    const Database& database_;
   122    AnchorMap::const_iterator anchor_it_;
   123    Database::const_iterator database_it_;
   124    souffle::RamDomain empty_vname_;
   125  };
   126  
   127  class KytheReadStreamFactory : public souffle::ReadStreamFactory {
   128   public:
   129    KytheReadStreamFactory(const AnchorMap& anchors, const Database& database,
   130                           const SymbolTable& dst)
   131        : anchors_(anchors), database_(database), dst_(dst) {}
   132  
   133    souffle::Own<souffle::ReadStream> getReader(
   134        const std::map<std::string, std::string>& rw_operation,
   135        souffle::SymbolTable& symbol_table,
   136        souffle::RecordTable& record_table) override {
   137      return souffle::mk<KytheReadStream>(
   138          rw_operation, symbol_table, record_table, anchors_, database_, dst_);
   139    }
   140  
   141    const std::string& getName() const override {
   142      static absl::NoDestructor<std::string> name("kythe");
   143      return *name;
   144    }
   145  
   146   private:
   147    const AnchorMap& anchors_;
   148    const Database& database_;
   149    const SymbolTable& dst_;
   150  };
   151  
   152  struct KytheOutput {
   153    std::vector<std::string> outputs;
   154  };
   155  
   156  class KytheWriteStream : public souffle::WriteStream {
   157   public:
   158    explicit KytheWriteStream(
   159        const std::map<std::string, std::string>& rw_operation,
   160        const souffle::SymbolTable& symbol_table,
   161        const souffle::RecordTable& record_table,
   162        const std::vector<EVarType>& output_types,
   163        const std::function<std::string(size_t)>& get_symbol, KytheOutput* output)
   164        : souffle::WriteStream(rw_operation, symbol_table, record_table),
   165          output_types_(output_types),
   166          get_symbol_(get_symbol),
   167          output_(output) {}
   168  
   169   protected:
   170    void writeNullary() override { output_->outputs.push_back(""); }
   171  
   172    void writeNextTuple(const souffle::RamDomain* result_tuple) override {
   173      if (output_types_.empty()) {
   174        output_->outputs.push_back("");
   175        return;
   176      }
   177      const auto* tuple =
   178          recordTable.unpack(result_tuple[0], output_types_.size());
   179      for (size_t ox = 0; ox < output_types_.size(); ++ox) {
   180        output_->outputs.push_back("");
   181        auto* o = &output_->outputs.back();
   182        auto type = output_types_[ox];
   183        switch (type) {
   184          case EVarType::kVName: {
   185            if (tuple[ox] == 0) {
   186              absl::StrAppend(o, "#nil-vname");
   187            } else {
   188              const auto* v = recordTable.unpack(tuple[ox], 5);
   189              absl::StrAppend(o, "vname(", get_symbol_(v[0]), ", ",
   190                              get_symbol_(v[1]), ", ", get_symbol_(v[2]), ", ",
   191                              get_symbol_(v[3]), ", ", get_symbol_(v[4]), ")");
   192            }
   193          } break;
   194          case EVarType::kSymbol: {
   195            *o = get_symbol_(tuple[ox]);
   196          } break;
   197          default:
   198            *o = "#unknown-type";
   199        }
   200      }
   201    }
   202  
   203   private:
   204    std::vector<EVarType> output_types_;
   205    std::function<std::string(size_t)> get_symbol_;
   206    KytheOutput* output_;
   207  };
   208  
   209  class KytheWriteStreamFactory : public souffle::WriteStreamFactory {
   210   public:
   211    KytheWriteStreamFactory(const std::vector<EVarType>& output_types,
   212                            const std::function<std::string(size_t)>& get_symbol)
   213        : output_types_(output_types), get_symbol_(get_symbol) {}
   214    souffle::Own<souffle::WriteStream> getWriter(
   215        const std::map<std::string, std::string>& rw_operation,
   216        const souffle::SymbolTable& symbol_table,
   217        const souffle::RecordTable& record_table) override {
   218      auto output = rw_operation.find("id");
   219      CHECK(output != rw_operation.end());
   220      size_t output_id;
   221      CHECK(absl::SimpleAtoi(output->second, &output_id));
   222      CHECK(output_id < outputs_.size());
   223      return souffle::mk<KytheWriteStream>(rw_operation, symbol_table,
   224                                           record_table, output_types_,
   225                                           get_symbol_, &outputs_[output_id]);
   226    }
   227  
   228    const std::string& getName() const override {
   229      static absl::NoDestructor<std::string> name("kythe");
   230      return *name;
   231    }
   232  
   233    size_t NewOutput() {
   234      outputs_.push_back({});
   235      return outputs_.size() - 1;
   236    }
   237  
   238    const KytheOutput& GetOutput(size_t id) const { return outputs_[id]; }
   239  
   240   private:
   241    std::vector<KytheOutput> outputs_;
   242    std::vector<EVarType> output_types_;
   243    std::function<std::string(size_t)> get_symbol_;
   244  };
   245  }  // anonymous namespace
   246  
   247  SouffleResult RunSouffle(
   248      const SymbolTable& symbol_table, const std::vector<GoalGroup>& goal_groups,
   249      const Database& database, const AnchorMap& anchors,
   250      const std::vector<Inspection>& inspections,
   251      std::function<bool(const Inspection&, std::string_view)> inspect,
   252      std::function<std::string(size_t)> get_symbol) {
   253    SouffleResult result{};
   254    // Trivial cases (no goals, all empty groups, etc) should pass.
   255    result.success = true;
   256    SouffleErrorState error_state(&goal_groups);
   257    bool first_pass = true;
   258    while (error_state.NextStep()) {
   259      SouffleProgram program;
   260      if (!program.Lower(symbol_table, goal_groups, inspections, error_state)) {
   261        // If we fail to lower a program, exit immediately.
   262        result.success = false;
   263        first_pass = false;
   264        result.highest_goal_reached = error_state.target_goal();
   265        result.highest_group_reached = error_state.target_group();
   266        return result;
   267      }
   268      std::vector<EVarType> output_types;
   269      absl::flat_hash_map<EVar*, size_t> output_indices;
   270      for (const auto& ev : program.inspections()) {
   271        auto t = program.EVarTypeFor(ev);
   272        output_indices[ev] = output_types.size();
   273        output_types.push_back(t ? *t : EVarType::kUnknown);
   274      }
   275      auto write_stream_factory =
   276          std::make_shared<KytheWriteStreamFactory>(output_types, get_symbol);
   277      size_t output_id = write_stream_factory->NewOutput();
   278      souffle::IOSystem::getInstance().registerWriteStreamFactory(
   279          write_stream_factory);
   280      souffle::IOSystem::getInstance().registerReadStreamFactory(
   281          std::make_shared<KytheReadStreamFactory>(anchors, database,
   282                                                   symbol_table));
   283      auto ram_tu = souffle::ParseTransform(absl::StrCat(
   284          program.code(), ".output result(IO=kythe, id=", output_id, ")\n"));
   285      if (ram_tu == nullptr) {
   286        LOG(ERROR) << "no ram_tu for program <" << program.code() << ">";
   287        result.success = false;
   288        return result;
   289      }
   290      souffle::Own<souffle::interpreter::Engine> interpreter(
   291          souffle::mk<souffle::interpreter::Engine>(*ram_tu));
   292      interpreter->executeMain();
   293      const auto& actual = write_stream_factory->GetOutput(output_id);
   294      bool succeeded = (!actual.outputs.empty());
   295      // We shouldn't mark successes during error recovery as overall successes.
   296      result.success = first_pass && succeeded;
   297      first_pass = false;
   298      if (!succeeded) {
   299        // We want to identify the first goal to fail.
   300        result.highest_goal_reached = error_state.target_goal();
   301        result.highest_group_reached = error_state.target_group();
   302        continue;
   303      }
   304      for (const auto& i : inspections) {
   305        auto ix = output_indices.find(i.evar);
   306        if (ix != output_indices.end()) {
   307          if (!inspect(i, actual.outputs[ix->second])) {
   308            break;
   309          }
   310        } else if (i.kind == Inspection::Kind::EXPLICIT) {
   311          LOG(ERROR) << "missing output index for inspection with label "
   312                     << i.label;
   313        }
   314      }
   315      break;
   316    }
   317    return result;
   318  }
   319  }  // namespace kythe::verifier