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