// swan_sil_parser.cpp — SWAN-inspired SIL parser implementation // // This is a focused C++ SIL parser that reads canonical SIL text and: // 1. Parses function/block/operator structure into SilModule AST // 2. Converts security-relevant operators into RustExprNode JSON // 3. Emits findings for known-dangerous SIL patterns // // The parser handles ~80% of common SIL constructs with a recursive-descent // approach modeled after SWAN's SILParser.scala. Security-relevant operators // (unchecked casts, pointer operations, cond_fail) get full attention. // // Fallback: the existing regex parser in fold_swift.cpp handles anything // this parser cannot parse. #include "swan_sil_parser.hpp" #include #include #include #include #include #include namespace fold_swift { namespace { bool typeNameContainsAny(std::string_view type_name, std::initializer_list needles) { for (std::string_view needle : needles) { if (type_name.find(needle) != std::string_view::npos) { return true; } } return false; } } // namespace // ========================================================================= // SilTextParser — Lexer helpers // ========================================================================= SilTextParser::SilTextParser(std::string text, std::string source_path) : source_(std::move(source_path)), text_(std::move(text)), len_(text_.size()) { skipWhitespaceAndComments(); } void SilTextParser::setDemangleHook(std::function hook) { demangle_hook_ = std::move(hook); } char SilTextParser::peek() const { return cursor_ < len_ ? text_[cursor_] : '\0'; } char SilTextParser::peekAt(int offset) const { auto idx = cursor_ + offset; return (idx >= 0 && static_cast(idx) < len_) ? text_[idx] : '\0'; } bool SilTextParser::peekStr(std::string_view s) const { if (cursor_ + s.size() > len_) return false; return text_.compare(cursor_, s.size(), s) == 0; } void SilTextParser::skipWhitespace() { while (cursor_ < len_ && std::isspace(static_cast(text_[cursor_]))) ++cursor_; } void SilTextParser::skipWhitespaceAndComments() { while (cursor_ < len_) { skipWhitespace(); if (cursor_ < len_ && text_[cursor_] == '/' && peekAt(1) == '/') { // Track source location from SIL comments // Format: // path:line:col while (cursor_ < len_ && text_[cursor_] != '\n') ++cursor_; trackCommentLoc(); continue; } if (cursor_ < len_ && text_[cursor_] == '/' && peekAt(1) == '*') { // Skip block comment cursor_ += 2; while (cursor_ + 1 < len_ && !(text_[cursor_] == '*' && text_[cursor_ + 1] == '/')) cursor_++; if (cursor_ + 1 < len_) cursor_ += 2; continue; } break; } } bool SilTextParser::skipToken(std::string_view s) { if (peekStr(s)) { cursor_ += s.size(); skipWhitespaceAndComments(); return true; } return false; } void SilTextParser::takeToken(std::string_view s) { if (!skipToken(s)) { parseError(std::string("expected '") + std::string(s) + "'"); } } std::string SilTextParser::takeIdentifier() { std::string result; // Allow leading $, @, _, %, and digits in identifiers (SIL uses %0, $s, @convention, etc.) while (cursor_ < len_ && (std::isalnum(static_cast(text_[cursor_])) || text_[cursor_] == '_' || text_[cursor_] == '$' || text_[cursor_] == '@' || text_[cursor_] == '%' || text_[cursor_] == '.' || text_[cursor_] == '-')) { result += text_[cursor_]; ++cursor_; } skipWhitespaceAndComments(); return result; } std::string SilTextParser::takeUntil(char delim) { std::string result; while (cursor_ < len_ && text_[cursor_] != delim) { result += text_[cursor_]; ++cursor_; } skipWhitespaceAndComments(); return result; } std::string SilTextParser::takeWhile(int (*pred)(int)) { std::string result; while (cursor_ < len_ && pred(static_cast(text_[cursor_]))) { result += text_[cursor_]; ++cursor_; } skipWhitespaceAndComments(); return result; } bool SilTextParser::tryTake(const std::string& s) { return skipToken(s); } bool SilTextParser::atEnd() const { return cursor_ >= len_; } void SilTextParser::parseError(const std::string& msg) const { std::string context; auto start = (cursor_ > 20) ? cursor_ - 20 : 0; auto end = std::min(cursor_ + 30, len_); context = text_.substr(start, end - start); throw std::runtime_error("SIL parse error at position " + std::to_string(cursor_) + ": " + msg + "\n near: " + context); } // ========================================================================= // SilTextParser — Source location tracking // ========================================================================= void SilTextParser::trackCommentLoc() { // Walk back from cursor to find the most recent // comment line. // The comment should end at cursor (at the '\n' or end-of-buffer). std::size_t line_start = cursor_; // Move back to start of line while (line_start > 0 && text_[line_start - 1] != '\n') --line_start; // Check if this line is a SIL source location comment: // path:line:col if (line_start + 2 < cursor_ && text_[line_start] == '/' && text_[line_start + 1] == '/') { std::string comment(text_.data() + line_start + 2, cursor_ - line_start - 2); // Strip leading/trailing whitespace from comment body auto start = comment.find_first_not_of(" \t"); if (start == std::string::npos) return; auto end = comment.find_last_not_of(" \t\r\n"); std::string body = comment.substr(start, end - start + 1); // Find the last :line:col pattern auto last_colon = body.rfind(':'); if (last_colon == std::string::npos || last_colon + 1 >= body.size()) return; auto second_colon = body.rfind(':', last_colon - 1); if (second_colon == std::string::npos || second_colon + 1 >= last_colon) return; // Try to parse line:col try { int line = std::stoi(body.substr(second_colon + 1, last_colon - second_colon - 1)); int col = std::stoi(body.substr(last_colon + 1)); last_comment_loc_.file = body.substr(0, second_colon); last_comment_loc_.line = static_cast(line); last_comment_loc_.column = static_cast(col); } catch (const std::exception&) { // Not a valid source location comment } } } // ========================================================================= // SilTextParser — Basic parsers // ========================================================================= std::int64_t SilTextParser::parseInt() { // SIL integer literals can be negative bool neg = false; if (peek() == '-') { neg = true; ++cursor_; } std::string digits = takeWhile(::isdigit); if (digits.empty()) { parseError("expected integer"); } std::int64_t val = 0; auto [ptr, ec] = std::from_chars(digits.data(), digits.data() + digits.size(), val); if (ec != std::errc{}) { parseError("invalid integer: " + digits); } skipWhitespaceAndComments(); return neg ? -val : val; } std::string SilTextParser::parseMangledName() { // SIL mangled names: $s..., @convention, or regular identifiers std::string result; if (peek() == '$' || peek() == '@') { result = takeIdentifier(); } else { // Regular name while (cursor_ < len_ && (std::isalnum(static_cast(text_[cursor_])) || text_[cursor_] == '_' || text_[cursor_] == '.' || text_[cursor_] == '<' || text_[cursor_] == '>')) { // Stop at whitespace or SIL delimiters char c = text_[cursor_]; if (c == '(' || c == ':' || c == '{' || c == '}' || c == ',' || c == ';') break; result += c; ++cursor_; } } skipWhitespaceAndComments(); return result; } std::string SilTextParser::parseValueOperand() { // SIL value operand: %0, %1, %name, etc. if (peek() != '%') { parseError("expected SIL value operand (starting with %)"); } return takeIdentifier(); } int SilTextParser::parseScopeRef() { if (skipToken("scope")) { return static_cast(parseInt()); } return 0; } SilLoc SilTextParser::parseLoc() { SilLoc loc; // Format: "::" if (peek() != '"') return loc; std::size_t saved = cursor_; ++cursor_; // skip opening " std::string path; while (cursor_ < len_ && text_[cursor_] != '"' && text_[cursor_] != '\n') { path += text_[cursor_]; ++cursor_; } if (cursor_ < len_ && text_[cursor_] == '"') { ++cursor_; // skip closing " } else { cursor_ = saved; return loc; } if (skipToken(":")) { try { loc.line = static_cast(parseInt()); if (skipToken(":")) { loc.column = static_cast(parseInt()); } } catch (const std::exception&) { // Ignore parse errors in source location } } loc.file = path; skipWhitespaceAndComments(); return loc; } SilSourceInfo SilTextParser::parseSourceInfo() { SilSourceInfo si; // Format: , loc "::", scope bool had_info = false; while (true) { if (skipToken(", loc")) { si.loc = parseLoc(); had_info = true; } else if (skipToken(", scope")) { si.scope_ref = static_cast(parseInt()); had_info = true; } else if (skipToken(", inlined_at")) { parseInt(); // scope ref, skip for now had_info = true; } else { break; } } if (!had_info) { // Some SIL lines have implicit position from the comment // // file:line:col pattern - we handle this in parseOperator } return si; } // ========================================================================= // SilTextParser — Type parser (simplified) // ========================================================================= SilType SilTextParser::parseType() { // Handle type attributes first SilType t = parseAttributedType(); // Check for optional: T? t = parseOptionalType(t); return t; } SilType SilTextParser::parseAttributedType() { // @inout, @guaranteed, @owned, etc. SilType base; if (peekStr("$@convention")) { // SIL function signatures commonly spell function types as // $@convention(...). Strip the sigil so the existing convention parser // can continue into the tuple/arrow body instead of treating it as a // named type. takeToken("$"); } if (skipToken("@inout") || skipToken("@inout_aliasable") || skipToken("@in_guaranteed")) { // Address-of type base = parseType(); base.kind = SilTypeKind::Address; return base; } if (skipToken("@owned") || skipToken("@guaranteed") || skipToken("@unowned") || skipToken("@thin") || skipToken("@thick") || skipToken("@yields") || skipToken("@yield_once") || skipToken("@error") || skipToken("@noescape") || skipToken("@escaping") || skipToken("@autoclosure") || skipToken("@convention") || skipToken("@callee_guaranteed") || skipToken("@sil_weak") || skipToken("@sil_unowned") || skipToken("@sil_unmanaged") || skipToken("@autoreleased") || skipToken("@block_storage") || skipToken("@pseudogeneric") || skipToken("@substituted")) { // Consume the attribute and continue base = parseType(); return base; } // @convention(x) type if (peekStr("@convention")) { takeIdentifier(); // @convention if (skipToken("(")) { // convention kind while (cursor_ < len_ && text_[cursor_] != ')') ++cursor_; if (cursor_ < len_) ++cursor_; // skip ) skipWhitespaceAndComments(); } base = parseType(); return base; } // @opened("...") type if (peekStr("@opened")) { takeIdentifier(); if (skipToken("(")) { takeUntil(')'); if (peek() == ')') ++cursor_; skipWhitespaceAndComments(); } base = parseType(); return base; } // Function type if (peek() == '(' || peek() == '<' || peekStr("()")) { return parseFunctionType(); } // Address type: *T or $*T if (peek() == '*' || peekStr("$*")) { return parseAddressType(); } // $Self, Builtin types if (peek() == '$') { return parseNamedType(); } return parseNamedType(); } SilType SilTextParser::parseNamedType() { SilType t; t.kind = SilTypeKind::Named; t.name = parseMangledName(); if (t.name.empty() && cursor_ < len_) { t.name = takeIdentifier(); } // Generic parameters: T if (skipToken("<")) { while (!atEnd() && peek() != '>') { SilType param = parseType(); t.params.push_back(std::move(param)); skipToken(","); } if (peek() == '>') { ++cursor_; skipWhitespaceAndComments(); } } // Keep the pointer allow-list explicit so new SIL spellings are easy to audit. if (typeNameContainsAny( t.name, {"UnsafePointer", "UnsafeMutablePointer", "UnsafeRawPointer", "UnsafeMutableRawPointer", "UnsafeBufferPointer", "UnsafeMutableBufferPointer", "UnsafeRawBufferPointer", "UnsafeMutableRawBufferPointer", "OpaquePointer", "AutoreleasingUnsafeMutablePointer", "CVaListPointer", "UnsafeRawSpan"})) { t.is_pointer = true; } if (typeNameContainsAny(t.name, {"UnsafeRawPointer", "UnsafeMutableRawPointer", "UnsafeRawBufferPointer", "UnsafeMutableRawBufferPointer", "UnsafeRawSpan"})) { t.is_raw_pointer = true; t.is_pointer = true; } return t; } SilType SilTextParser::parseOptionalType(SilType base) { // Postfix ? for Optional if (skipToken("?")) { SilType opt; opt.kind = SilTypeKind::Optional; opt.name = "Optional<" + base.name + ">"; opt.params.push_back(std::move(base)); opt.is_optional = true; return opt; } // Prefix Optional if (base.name == "Optional" && !base.params.empty()) { base.is_optional = true; } return base; } SilType SilTextParser::parseAddressType() { SilType t; if (skipToken("*")) { // *T t = parseType(); t.kind = SilTypeKind::Address; } else if (skipToken("$*")) { t = parseType(); t.kind = SilTypeKind::Address; } return t; } SilType SilTextParser::parseTupleType() { SilType t; t.kind = SilTypeKind::Tuple; takeToken("("); while (!atEnd() && peek() != ')') { SilType elem = parseType(); t.params.push_back(std::move(elem)); skipToken(","); } takeToken(")"); return t; } SilType SilTextParser::parseFunctionType() { SilType t; t.kind = SilTypeKind::Function; // Generic params: if (skipToken("<")) { // Skip generic parameters for now int depth = 1; while (cursor_ < len_ && depth > 0) { if (text_[cursor_] == '<') ++depth; else if (text_[cursor_] == '>') --depth; ++cursor_; } skipWhitespaceAndComments(); } // Parameter list if (skipToken("(")) { while (!atEnd() && peek() != ')') { SilType param = parseType(); t.params.push_back(std::move(param)); skipToken(","); } takeToken(")"); } // Return arrow: -> T or throws -> T tryTake("throws"); tryTake("rethrows"); if (skipToken("->")) { SilType ret = parseType(); t.params.push_back(std::move(ret)); // Last param is return type } return t; } // ========================================================================= // SilTextParser — DeclRef parser // ========================================================================= SilDeclRef SilTextParser::parseDeclRef() { SilDeclRef dr; // Format: #Module.Class.method!kind.suffix if (peek() != '#') { // Might be just an identifier dr.parts.push_back(takeIdentifier()); return dr; } takeToken("#"); std::string full; while (cursor_ < len_ && !std::isspace(static_cast(text_[cursor_])) && text_[cursor_] != '(' && text_[cursor_] != ')' && text_[cursor_] != ',' && text_[cursor_] != ':' && text_[cursor_] != '}' && text_[cursor_] != '{' && text_[cursor_] != '\n') { full += text_[cursor_]; ++cursor_; } skipWhitespaceAndComments(); // Parse parts separated by . std::string current; for (char c : full) { if (c == '.') { if (!current.empty()) { dr.parts.push_back(current); current.clear(); } } else { current += c; } } if (!current.empty()) { // Last part may contain decl kind after ! or . auto bang_pos = current.find('!'); if (bang_pos != std::string::npos) { dr.parts.push_back(current.substr(0, bang_pos)); dr.kind = current.substr(bang_pos + 1); } else { dr.parts.push_back(current); } } return dr; } std::string SilTextParser::parseQuotedString() { if (peek() != '"') { parseError("expected quoted string"); } ++cursor_; std::string result; while (cursor_ < len_) { char ch = text_[cursor_++]; if (ch == '\\') { if (cursor_ >= len_) { break; } result += text_[cursor_++]; continue; } if (ch == '"') { skipWhitespaceAndComments(); return result; } result += ch; } parseError("unterminated quoted string"); } void SilTextParser::resolveSourceInfo(SilSourceInfo& info) const { if (!info.loc.valid() && info.scope_ref != 0) { auto it = scope_map_.find(static_cast(info.scope_ref)); if (it != scope_map_.end()) { info.loc = it->second; } } } // ========================================================================= // SilTextParser — Module-level parser // ========================================================================= void SilTextParser::parseImport() { // sil_stage canonical | raw // import Builtin // import Swift // import swift if (skipToken("import")) { std::string module = takeIdentifier(); // We could store imports but for now just consume } else if (skipToken("sil_stage")) { takeIdentifier(); // canonical or raw } else if (skipToken("sil_scope")) { int id = static_cast(parseInt()); takeToken("{"); if (skipToken("loc")) { auto loc = parseLoc(); scope_map_[id] = loc; } // Skip rest of scope block int depth = 1; while (cursor_ < len_ && depth > 0) { if (text_[cursor_] == '{') ++depth; else if (text_[cursor_] == '}') --depth; ++cursor_; } skipWhitespaceAndComments(); } else if (skipToken("sil_witness_table") || skipToken("sil_vtable") || skipToken("sil_global") || skipToken("sil_property") || skipToken("sil_default_witness_table")) { // Skip these declarations while (cursor_ < len_ && text_[cursor_] != '\n') ++cursor_; skipWhitespaceAndComments(); } } SilFunction SilTextParser::parseFunction() { SilFunction fn; SilLoc function_entry_loc = last_comment_loc_; // sil [linkage] [attributes] @name : $@convention(...) type { if (!skipToken("sil")) { parseError("expected 'sil' at function start"); } // Optional linkage if (skipToken("hidden") || skipToken("shared") || skipToken("public") || skipToken("private") || skipToken("non_abi") || skipToken("hidden_external") || skipToken("public_external") || skipToken("private_external") || skipToken("shared_external")) { // consumed } // Optional attributes (skip for now) if (skipToken("[")) { int depth = 1; while (cursor_ < len_ && depth > 0) { if (text_[cursor_] == '[') ++depth; else if (text_[cursor_] == ']') --depth; ++cursor_; } skipWhitespaceAndComments(); } // Function name fn.mangled_name = parseMangledName(); fn.name = demangle_hook_ ? demangle_hook_(fn.mangled_name) : fn.mangled_name; // Colon and type if (skipToken(":")) { fn.type = parseType(); } // Opening brace takeToken("{"); // Parse basic blocks while (!atEnd() && peek() != '}') { // Check for nested declarations (sil_witness_table, etc.) if (peekStr("sil_witness_table") || peekStr("sil_vtable") || peekStr("sil_global") || peekStr("sil_property")) { parseImport(); continue; } // Check for end of function if (peek() == '}') break; // Parse block label: bb0, bb1, ... if (peekStr("bb")) { fn.blocks.push_back(parseBlock()); } else if (std::isalpha(static_cast(peek()))) { // Could be a scope or other declaration std::string word = takeIdentifier(); if (word == "sil_scope" || word == "sil_witness_table" || word == "sil_vtable" || word == "sil_global") { // back up and use parseImport cursor_ -= word.size(); parseImport(); } else { // Unknown — skip rest of line while (cursor_ < len_ && text_[cursor_] != '\n' && text_[cursor_] != '}') ++cursor_; skipWhitespaceAndComments(); } } else { // Skip unknown character ++cursor_; skipWhitespaceAndComments(); } } takeToken("}"); for (const auto& block : fn.blocks) { for (const auto& op : block.operators) { if (op.source_info.valid()) { fn.start_line = static_cast(op.source_info.loc.line); return fn; } } } if (function_entry_loc.valid()) { fn.start_line = static_cast(function_entry_loc.line); } return fn; } SilBlock SilTextParser::parseBlock() { SilBlock block; // bb0, bb1, ... block.identifier = takeIdentifier(); // Optional arguments: bb0(%arg : $Type, ...): if (skipToken("(")) { while (!atEnd() && peek() != ')') { SilOperand arg; arg.value = takeIdentifier(); if (skipToken(":")) { arg.type = parseType(); } block.arguments.push_back(std::move(arg)); skipToken(","); } takeToken(")"); } // Colon after block label if (peek() == ':') { takeToken(":"); } // Parse operators and terminators until next block or end of function while (!atEnd() && peek() != '}' && !peekStr("bb")) { // Check for comment line with source location // // path:line:col if (peek() == '/' && peekAt(1) == '/') { std::string comment_line; while (cursor_ < len_ && text_[cursor_] != '\n') { comment_line += text_[cursor_]; ++cursor_; } // Try to extract source location from comment // Format: // path:line:col // We'll attach it to the next operator skipWhitespaceAndComments(); continue; } // Check for terminator keywords if (peekStr("return") || peekStr("throw") || peekStr("br") || peekStr("cond_br") || peekStr("switch_enum") || peekStr("switch_value") || peekStr("checked_cast_br") || peekStr("try_apply") || peekStr("unreachable") || peekStr("yield") || peekStr("unwind") || peekStr("dynamic_method_br")) { block.terminator = parseTerminator(); break; // Terminator ends the block } // Parse operator block.operators.push_back(parseOperator()); } return block; } SilOperator SilTextParser::parseOperator() { SilOperator op; // Format: [%result = ]operator_name operands [, loc ...] [, scope ...] // Check for result assignment if (peek() == '%') { op.result = parseResult(); } // Read operator name std::string op_name; while (cursor_ < len_ && (std::isalpha(static_cast(text_[cursor_])) || text_[cursor_] == '_')) { op_name += text_[cursor_]; ++cursor_; } skipWhitespaceAndComments(); if (op_name.empty()) { // Skip unknown line while (cursor_ < len_ && text_[cursor_] != '\n' && text_[cursor_] != '}') ++cursor_; skipWhitespaceAndComments(); op.kind = SilOperatorKind::Unknown; return op; } // Route to appropriate sub-parser static const std::unordered_map op_map = { // Allocation {"alloc_stack", SilOperatorKind::AllocStack}, {"alloc_ref", SilOperatorKind::AllocRef}, {"alloc_box", SilOperatorKind::AllocBox}, {"dealloc_stack", SilOperatorKind::DeallocStack}, {"dealloc_ref", SilOperatorKind::DeallocRef}, {"dealloc_box", SilOperatorKind::DeallocBox}, {"project_box", SilOperatorKind::ProjectBox}, // Memory Access {"load", SilOperatorKind::Load}, {"store", SilOperatorKind::Store}, {"load_borrow", SilOperatorKind::LoadBorrow}, {"begin_borrow", SilOperatorKind::BeginBorrow}, {"end_borrow", SilOperatorKind::EndBorrow}, {"end_lifetime", SilOperatorKind::EndLifetime}, {"copy_addr", SilOperatorKind::CopyAddr}, {"destroy_addr", SilOperatorKind::DestroyAddr}, {"index_addr", SilOperatorKind::IndexAddr}, {"index_raw_pointer", SilOperatorKind::IndexRawPointer}, {"begin_access", SilOperatorKind::BeginAccess}, {"end_access", SilOperatorKind::EndAccess}, // Reference Counting {"strong_retain", SilOperatorKind::StrongRetain}, {"strong_release", SilOperatorKind::StrongRelease}, {"set_deallocating", SilOperatorKind::SetDeallocating}, {"unowned_retain", SilOperatorKind::UnownedRetain}, {"unowned_release", SilOperatorKind::UnownedRelease}, {"load_weak", SilOperatorKind::LoadWeak}, {"store_weak", SilOperatorKind::StoreWeak}, {"fix_lifetime", SilOperatorKind::FixLifetime}, {"is_unique", SilOperatorKind::IsUnique}, {"is_escaping_closure", SilOperatorKind::IsEscapingClosure}, // Literals {"function_ref", SilOperatorKind::FunctionRef}, {"dynamic_function_ref", SilOperatorKind::DynamicFunctionRef}, {"global_addr", SilOperatorKind::GlobalAddr}, {"integer_literal", SilOperatorKind::IntegerLiteral}, {"float_literal", SilOperatorKind::FloatLiteral}, {"string_literal", SilOperatorKind::StringLiteral}, // Dynamic Dispatch {"class_method", SilOperatorKind::ClassMethod}, {"objc_method", SilOperatorKind::ObjcMethod}, {"super_method", SilOperatorKind::SuperMethod}, {"witness_method", SilOperatorKind::WitnessMethod}, // Function Application {"apply", SilOperatorKind::Apply}, {"begin_apply", SilOperatorKind::BeginApply}, {"abort_apply", SilOperatorKind::AbortApply}, {"end_apply", SilOperatorKind::EndApply}, {"partial_apply", SilOperatorKind::PartialApply}, {"builtin", SilOperatorKind::Builtin}, // Aggregate Types {"retain_value", SilOperatorKind::RetainValue}, {"release_value", SilOperatorKind::ReleaseValue}, {"copy_value", SilOperatorKind::CopyValue}, {"destroy_value", SilOperatorKind::DestroyValue}, {"tuple", SilOperatorKind::Tuple}, {"tuple_extract", SilOperatorKind::TupleExtract}, {"tuple_element_addr", SilOperatorKind::TupleElementAddr}, {"destructure_tuple", SilOperatorKind::DestructureTuple}, {"struct", SilOperatorKind::Struct}, {"struct_extract", SilOperatorKind::StructExtract}, {"struct_element_addr", SilOperatorKind::StructElementAddr}, {"destructure_struct", SilOperatorKind::DestructureStruct}, {"ref_element_addr", SilOperatorKind::RefElementAddr}, {"ref_tail_addr", SilOperatorKind::RefTailAddr}, // Enums {"enum", SilOperatorKind::Enum}, {"unchecked_enum_data", SilOperatorKind::UncheckedEnumData}, {"init_enum_data_addr", SilOperatorKind::InitEnumDataAddr}, {"inject_enum_addr", SilOperatorKind::InjectEnumAddr}, {"unchecked_take_enum_data_addr", SilOperatorKind::UncheckedTakeEnumDataAddr}, {"select_enum", SilOperatorKind::SelectEnum}, {"select_enum_addr", SilOperatorKind::SelectEnumAddr}, // Protocol / Existential {"init_existential_addr", SilOperatorKind::InitExistentialAddr}, {"deinit_existential_addr", SilOperatorKind::DeinitExistentialAddr}, {"open_existential_addr", SilOperatorKind::OpenExistentialAddr}, {"open_existential_ref", SilOperatorKind::OpenExistentialRef}, // Unchecked Conversions {"upcast", SilOperatorKind::Upcast}, {"address_to_pointer", SilOperatorKind::AddressToPointer}, {"pointer_to_address", SilOperatorKind::PointerToAddress}, {"unchecked_ref_cast", SilOperatorKind::UncheckedRefCast}, {"unchecked_ref_cast_addr", SilOperatorKind::UncheckedRefCastAddr}, {"unchecked_addr_cast", SilOperatorKind::UncheckedAddrCast}, {"unchecked_trivial_bit_cast", SilOperatorKind::UncheckedTrivialBitCast}, {"unchecked_bitwise_cast", SilOperatorKind::UncheckedBitwiseCast}, {"unchecked_ownership_conversion", SilOperatorKind::UncheckedOwnershipConversion}, {"ref_to_raw_pointer", SilOperatorKind::RefToRawPointer}, {"raw_pointer_to_ref", SilOperatorKind::RawPointerToRef}, {"ref_to_unowned", SilOperatorKind::RefToUnowned}, {"unowned_to_ref", SilOperatorKind::UnownedToRef}, {"ref_to_unmanaged", SilOperatorKind::RefToUnmanaged}, {"unmanaged_to_ref", SilOperatorKind::UnmanagedToRef}, {"convert_function", SilOperatorKind::ConvertFunction}, {"convert_escape_to_noescape", SilOperatorKind::ConvertEscapeToNoescape}, // Checked Conversions {"unconditional_checked_cast", SilOperatorKind::UnconditionalCheckedCast}, {"unconditional_checked_cast_addr", SilOperatorKind::UnconditionalCheckedCastAddr}, // Runtime Failures {"cond_fail", SilOperatorKind::CondFail}, // Misc {"metatype", SilOperatorKind::Metatype}, {"value_metatype", SilOperatorKind::ValueMetatype}, {"existential_metatype", SilOperatorKind::ExistentialMetatype}, {"select_value", SilOperatorKind::SelectValue}, {"keypath", SilOperatorKind::Keypath}, {"debug_value", SilOperatorKind::DebugValue}, {"debug_value_addr", SilOperatorKind::DebugValueAddr}, }; auto it = op_map.find(op_name); if (it != op_map.end()) { op.kind = it->second; } else { op.kind = SilOperatorKind::Unknown; } // Parse operands based on operator kind switch (op.kind) { // Unchecked casts — security critical case SilOperatorKind::UncheckedRefCast: case SilOperatorKind::UncheckedAddrCast: case SilOperatorKind::UncheckedTrivialBitCast: case SilOperatorKind::UncheckedBitwiseCast: case SilOperatorKind::UncheckedRefCastAddr: op.is_unchecked_cast = true; op.operands.push_back(parseOperand()); if (skipToken("to")) { op.type = parseType(); } if (op.kind == SilOperatorKind::UncheckedRefCastAddr) { // unchecked_ref_cast_addr takes two operands + types if (skipToken("to")) { // second operand op.operands.push_back(parseOperand()); if (skipToken("to")) { op.type = parseType(); } } } break; case SilOperatorKind::UncheckedOwnershipConversion: op.operands.push_back(parseOperand()); // from @attribute to @attribute if (skipToken("from")) { if (peek() == '@') takeIdentifier(); // @owned, @guaranteed, etc. } if (skipToken("to")) { if (peek() == '@') takeIdentifier(); } op.is_unchecked_cast = true; break; // Pointer conversions case SilOperatorKind::AddressToPointer: case SilOperatorKind::PointerToAddress: case SilOperatorKind::RefToRawPointer: case SilOperatorKind::RawPointerToRef: case SilOperatorKind::RefToUnowned: case SilOperatorKind::UnownedToRef: case SilOperatorKind::RefToUnmanaged: case SilOperatorKind::UnmanagedToRef: op.operands.push_back(parseOperand()); if (skipToken("to")) { op.type = parseType(); } op.is_unchecked_cast = true; break; // Load / Store case SilOperatorKind::Load: case SilOperatorKind::LoadBorrow: case SilOperatorKind::LoadWeak: { // Optional ownership: [copy], [take], [trivial] if (peek() == '[') { takeIdentifier(); // [copy], etc. } op.operands.push_back(parseOperand()); // Check if loading through unsafe pointer if (!op.operands.empty() && (op.operands[0].type.is_pointer || op.operands[0].type.is_raw_pointer)) { op.is_load_through_pointer = true; } } break; case SilOperatorKind::Store: if (peek() == '[') { takeIdentifier(); // [assign], [init], [trivial] } { std::string from_val = takeIdentifier(); // value if (skipToken("to")) { op.operands.push_back(parseOperand()); } // Store the 'from' as a named reference SilOperand from_op; from_op.value = from_val; op.operands.insert(op.operands.begin(), from_op); if (op.operands.size() > 1 && (op.operands[1].type.is_pointer || op.operands[1].type.is_raw_pointer)) { op.is_load_through_pointer = true; } } break; // Apply case SilOperatorKind::Apply: case SilOperatorKind::BeginApply: op.nothrow = tryTake("nothrow"); { op.callee_name = takeIdentifier(); if (skipToken("<")) { // Substitutions while (!atEnd() && peek() != '>') { takeIdentifier(); skipToken(","); } if (peek() == '>') { ++cursor_; skipWhitespaceAndComments(); } } takeToken("("); while (!atEnd() && peek() != ')') { op.apply_args.push_back(takeIdentifier()); skipToken(","); } takeToken(")"); if (skipToken(":")) { op.type = parseType(); } } break; // FunctionRef case SilOperatorKind::FunctionRef: case SilOperatorKind::DynamicFunctionRef: op.callee_name = parseMangledName(); if (skipToken(":")) { op.type = parseType(); } break; // GlobalAddr case SilOperatorKind::GlobalAddr: op.callee_name = parseMangledName(); if (skipToken(":")) { op.type = parseType(); } break; // IntegerLiteral case SilOperatorKind::IntegerLiteral: if (!atEnd() && peek() == '$') { op.type = parseType(); } if (skipToken(",")) { op.int_value = parseInt(); } break; // FloatLiteral case SilOperatorKind::FloatLiteral: if (!atEnd() && peek() == '$') { op.type = parseType(); } if (skipToken(",")) { op.string_value = takeIdentifier(); // float string } break; // StringLiteral case SilOperatorKind::StringLiteral: { std::string encoding = takeIdentifier(); // utf8, utf16, objc_selector (void)encoding; op.string_value = parseQuotedString(); } break; // Struct / StructExtract case SilOperatorKind::Struct: op.type = parseType(); takeToken("("); while (!atEnd() && peek() != ')') { op.operands.push_back(parseOperand()); skipToken(","); } takeToken(")"); break; case SilOperatorKind::StructExtract: case SilOperatorKind::StructElementAddr: case SilOperatorKind::DestructureStruct: op.operands.push_back(parseOperand()); if (peek() == '#') { op.decl_ref = parseDeclRef(); } break; // Tuple / TupleExtract case SilOperatorKind::Tuple: takeToken("("); while (!atEnd() && peek() != ')') { op.operands.push_back(parseOperand()); skipToken(","); } takeToken(")"); break; case SilOperatorKind::TupleExtract: case SilOperatorKind::TupleElementAddr: case SilOperatorKind::DestructureTuple: op.operands.push_back(parseOperand()); if (skipToken(",")) { op.int_value = parseInt(); } break; // Enum operations case SilOperatorKind::Enum: op.type = parseType(); if (skipToken(",")) { op.decl_ref = parseDeclRef(); } if (skipToken(",")) { op.operands.push_back(parseOperand()); } break; case SilOperatorKind::UncheckedEnumData: case SilOperatorKind::InitEnumDataAddr: case SilOperatorKind::InjectEnumAddr: case SilOperatorKind::UncheckedTakeEnumDataAddr: op.operands.push_back(parseOperand()); if (skipToken("#")) { op.decl_ref = parseDeclRef(); } break; // CondFail case SilOperatorKind::CondFail: op.is_cond_fail = true; op.operands.push_back(parseOperand()); if (skipToken(",")) { if (peek() == '"') { op.string_value = parseQuotedString(); } else { op.string_value = takeIdentifier(); } } break; // Keypath case SilOperatorKind::Keypath: if (!atEnd() && peek() == '$') { op.type = parseType(); } if (skipToken(",")) { // elements while (!atEnd() && peek() != ',' && peek() != '\n') { ++cursor_; } skipWhitespaceAndComments(); } break; // SelectEnum / SelectEnumAddr case SilOperatorKind::SelectEnum: case SilOperatorKind::SelectEnumAddr: op.operands.push_back(parseOperand()); if (skipToken(",")) { // cases while (!atEnd() && peek() != ':' && peek() != '\n') { ++cursor_; } skipWhitespaceAndComments(); } break; // Builtin case SilOperatorKind::Builtin: if (skipToken("\"")) { op.string_value = takeUntil('"'); if (peek() == '"') ++cursor_; skipWhitespaceAndComments(); } if (skipToken("(")) { while (!atEnd() && peek() != ')') { op.operands.push_back(parseOperand()); skipToken(","); } takeToken(")"); } if (skipToken(":")) { op.type = parseType(); } break; // Casts (checked) case SilOperatorKind::UnconditionalCheckedCast: case SilOperatorKind::UnconditionalCheckedCastAddr: case SilOperatorKind::Upcast: case SilOperatorKind::ConvertFunction: case SilOperatorKind::ConvertEscapeToNoescape: op.operands.push_back(parseOperand()); if (skipToken("to") || skipToken(":")) { op.type = parseType(); } break; // All other operators — generic operand parsing default: { // Generic operand parsing while (!atEnd()) { char c = peek(); if (c == '\n' || c == '}' || c == '{' || c == ';') break; // Check for source info markers if (skipToken(", loc") || skipToken(", scope") || skipToken(", inlined_at")) { if (cursor_ > 0 && text_[cursor_ - 1] == 'c' && peek() == '"') { parseLoc(); } else if (cursor_ > 0 && (std::isdigit(static_cast(peek())) || peek() == '-')) { parseInt(); } continue; } // Check for sil-type if (peek() == '$' || peek() == '@') { SilType t = parseType(); break; // type usually ends the instruction } // value operand if (peek() == '%') { op.operands.push_back(parseOperand()); continue; } // String literal if (peek() == '"') { ++cursor_; op.string_value = takeUntil('"'); if (peek() == '"') ++cursor_; skipWhitespaceAndComments(); continue; } // Integer if (std::isdigit(static_cast(c)) || c == '-') { op.int_value = parseInt(); continue; } // Other (likely type name or keyword) std::string word = takeIdentifier(); if (word.empty() || word == "to" || word == "from" || word == "and" || word == "of") { continue; } break; } break; } } // Consume source info op.source_info = parseSourceInfo(); resolveSourceInfo(op.source_info); // If no explicit source info, attach tracked comment location if (!op.source_info.valid() && last_comment_loc_.valid()) { op.source_info.loc = last_comment_loc_; } return op; } SilTerminator SilTextParser::parseTerminator() { SilTerminator term; std::string word = takeIdentifier(); if (word == "return") { term.kind = SilTerminatorKind::Return; term.operand = parseOperand(); } else if (word == "throw") { term.kind = SilTerminatorKind::Throw; term.operand = parseOperand(); } else if (word == "unreachable") { term.kind = SilTerminatorKind::Unreachable; } else if (word == "br") { term.kind = SilTerminatorKind::Branch; term.target_label = takeIdentifier(); if (skipToken("(")) { while (!atEnd() && peek() != ')') { term.args.push_back(parseOperand()); skipToken(","); } takeToken(")"); } } else if (word == "cond_br") { term.kind = SilTerminatorKind::CondBranch; // cond_br %cond, bb_true, bb_false term.operand = parseOperand(); // condition if (skipToken(",")) { term.true_label = takeIdentifier(); } if (skipToken(",")) { term.false_label = takeIdentifier(); } } else if (word == "switch_enum") { term.kind = SilTerminatorKind::SwitchEnum; term.operand = parseOperand(); // Skip case labels for now while (cursor_ < len_ && text_[cursor_] != '\n' && text_[cursor_] != '}') ++cursor_; skipWhitespaceAndComments(); } else if (word == "switch_value") { term.kind = SilTerminatorKind::SwitchValue; term.operand = parseOperand(); while (cursor_ < len_ && text_[cursor_] != '\n' && text_[cursor_] != '}') ++cursor_; skipWhitespaceAndComments(); } else if (word == "checked_cast_br" || word == "checked_cast_addr_br") { term.kind = (word == "checked_cast_br") ? SilTerminatorKind::CheckedCastBranch : SilTerminatorKind::CheckedCastAddrBranch; term.operand = parseOperand(); if (skipToken("to")) { if (!atEnd() && peek() == '$') parseType(); // cast target type } term.true_label = takeIdentifier(); term.false_label = takeIdentifier(); } else if (word == "try_apply") { term.kind = SilTerminatorKind::TryApply; // try_apply %fn(args) : type, normal bb, error bb term.operand = parseOperand(); if (skipToken("(")) { while (!atEnd() && peek() != ')') { ++cursor_; } if (peek() == ')') ++cursor_; skipWhitespaceAndComments(); } if (skipToken(":")) { if (!atEnd() && peek() == '$') parseType(); } if (skipToken(",")) term.normal_label = takeIdentifier(); if (skipToken(",")) term.error_label = takeIdentifier(); } else if (word == "yield") { term.kind = SilTerminatorKind::Yield; while (!atEnd() && ptrdiff_t(cursor_) < ptrdiff_t(len_) && text_[cursor_] != '\n' && text_[cursor_] != '}') ++cursor_; skipWhitespaceAndComments(); } else if (word == "unwind") { term.kind = SilTerminatorKind::Unwind; } else if (word == "dynamic_method_br") { term.kind = SilTerminatorKind::DynamicMethodBranch; term.operand = parseOperand(); term.true_label = takeIdentifier(); term.false_label = takeIdentifier(); } term.source_info = parseSourceInfo(); resolveSourceInfo(term.source_info); return term; } SilOperand SilTextParser::parseOperand() { SilOperand op; op.value = takeIdentifier(); if (skipToken(":")) { op.type = parseType(); } return op; } SilResult SilTextParser::parseResult() { SilResult result; result.value_names.push_back(takeIdentifier()); while (skipToken(",")) { result.value_names.push_back(takeIdentifier()); } if (skipToken("=")) { // consumed } return result; } // ========================================================================= // SilTextParser — Main entry point // ========================================================================= SilModule SilTextParser::parseModule() { SilModule mod; mod.source_file = source_; try { while (!atEnd()) { if (peek() == '\0') break; if (peekStr("sil_stage") || peekStr("import") || peekStr("sil_scope") || peekStr("sil_witness_table") || peekStr("sil_vtable") || peekStr("sil_global") || peekStr("sil_property") || peekStr("sil_default_witness_table")) { parseImport(); } else if (peekStr("sil ")) { SilFunction fn = parseFunction(); mod.total_blocks += static_cast(fn.blocks.size()); for (const auto& b : fn.blocks) { mod.total_operators += static_cast(b.operators.size()); } mod.functions.push_back(std::move(fn)); } else { // Skip unknown line while (cursor_ < len_ && text_[cursor_] != '\n') ++cursor_; skipWhitespaceAndComments(); } } mod.parse_success = true; } catch (const std::exception& e) { mod.parse_success = false; mod.parse_error = e.what(); } return mod; } // ========================================================================= // SilToJsonConverter — SIL → fold NDJSON // ========================================================================= void SilToJsonConverter::convertModule(const SilModule& mod, std::ostream& out) { const std::string& file = mod.source_file; emitInfo(file, out); for (const auto& fn : mod.functions) { emitFunction(fn, file, out); emitInterface(fn, file, out); emitIr(fn, file, out); // Emit findings from operators for (const auto& block : fn.blocks) { for (const auto& op : block.operators) { if (op.is_unchecked_cast) { std::string hazard; switch (op.kind) { case SilOperatorKind::UncheckedRefCast: hazard = "swift-unchecked_ref_cast"; break; case SilOperatorKind::UncheckedAddrCast: hazard = "swift-unchecked_addr_cast"; break; case SilOperatorKind::UncheckedTrivialBitCast: hazard = "swift-unchecked_trivial_bit_cast"; break; case SilOperatorKind::UncheckedBitwiseCast: hazard = "swift-unchecked_bitwise_cast"; break; default: hazard = "swift-unchecked_cast"; break; } emitFinding( file, op.source_info.valid() ? static_cast(op.source_info.loc.line) : 0, op.source_info.valid() ? static_cast(op.source_info.loc.column) : 0, fn.name, "swift-unsafe-cast", hazard, hazard + " bypasses Swift type safety; verify layout and provenance", out); } if (op.is_load_through_pointer) { const bool is_store = op.kind == SilOperatorKind::Store; emitFinding( file, op.source_info.valid() ? static_cast(op.source_info.loc.line) : 0, op.source_info.valid() ? static_cast(op.source_info.loc.column) : 0, fn.name, "swift-unsafe-load", is_store ? "swift-unsafe-store" : "swift-unsafe-load", is_store ? "store through pointer type; verify pointer validity and " "lifetime for writes" : "load through pointer type; verify pointer validity", out); } if (op.is_cond_fail) { emitFinding( file, op.source_info.valid() ? static_cast(op.source_info.loc.line) : 0, op.source_info.valid() ? static_cast(op.source_info.loc.column) : 0, fn.name, "swift-assert-failure", "swift-cond_fail", op.string_value.empty() ? "cond_fail (force-unwrap / precondition failure)" : "cond_fail: " + op.string_value, out); } } } } } void SilToJsonConverter::emitInfo(const std::string& unit, std::ostream& out) { nlohmann::json line; line["kind"] = "info"; line["data"] = { {"message", "fold-swift — SWAN SIL parser (structured)"}, {"version", "0.2.0"}, {"unit", unit}, {"parser", "swan-sil"}, }; out << line.dump() << '\n'; } void SilToJsonConverter::emitFunction(const SilFunction& fn, const std::string& file, std::ostream& out) { nlohmann::json line; line["kind"] = "function"; line["data"] = { {"qualified_name", fn.name}, {"source_file", file}, {"start_line", fn.start_line}, {"end_line", fn.start_line + 1}, {"callees", nlohmann::json::array()}, }; out << line.dump() << '\n'; } void SilToJsonConverter::emitInterface(const SilFunction& fn, const std::string& file, std::ostream& out) { nlohmann::json line; auto params = nlohmann::json::array(); int param_count = 0; // Count parameters from function type if (!fn.blocks.empty() && !fn.blocks[0].arguments.empty()) { param_count = static_cast(fn.blocks[0].arguments.size()); } for (int i = 0; i < param_count; ++i) { params.push_back({ {"name", "swift_arg" + std::to_string(i)}, {"kind", "primitive"}, {"ty", "unknown"}, }); } line["kind"] = "interface"; line["data"] = { {"function", fn.name}, {"source_file", file}, {"line", fn.start_line}, {"params", std::move(params)}, {"fuzzable", param_count > 0}, }; out << line.dump() << '\n'; } void SilToJsonConverter::emitIr(const SilFunction& fn, const std::string& file, std::ostream& out) { // Build expression tree from function body nlohmann::json root = nlohmann::json::object(); // For now, emit a structured IR block with the function's operations auto ops = nlohmann::json::array(); for (const auto& block : fn.blocks) { for (const auto& op : block.operators) { auto expr = operatorToExpr(op, file); if (!expr.is_null()) { ops.push_back(std::move(expr)); } } } root["kind"] = "Block"; root["statements"] = std::move(ops); nlohmann::json line; line["kind"] = "ir"; line["data"] = { {"function", fn.name}, {"root", std::move(root)}, }; out << line.dump() << '\n'; } void SilToJsonConverter::emitFinding(const std::string& file, int line, int col, const std::string& function, const std::string& kind, const std::string& hazard, const std::string& message, std::ostream& out) { nlohmann::json j; j["kind"] = "finding"; j["data"] = { {"finding_kind", kind}, {"confidence", "Heuristic"}, {"function", function}, {"source_file", file}, {"line", line}, {"column", col}, {"hazard", hazard}, {"message", message}, {"backend", "fold-swift-swan"}, }; out << j.dump() << '\n'; } // ========================================================================= // SilToJsonConverter — Operator → RustExprNode JSON // ========================================================================= nlohmann::json SilToJsonConverter::makeLoc(int line, int col, const std::string& file) { nlohmann::json loc; loc["file"] = file; loc["line"] = line; loc["column"] = col; return loc; } nlohmann::json SilToJsonConverter::buildLiteralExpr(std::int64_t val, int width, bool signedness, int line, int col, const std::string& file) { nlohmann::json j; j["kind"] = "IntegerLiteral"; j["int_value"] = val; j["int_width"] = width; j["int_signed"] = signedness; j["loc"] = makeLoc(line, col, file); return j; } nlohmann::json SilToJsonConverter::buildStringLiteralExpr(const std::string& val, int line, int col, const std::string& file) { nlohmann::json j; j["kind"] = "StringLiteral"; j["string_value"] = val; j["loc"] = makeLoc(line, col, file); return j; } nlohmann::json SilToJsonConverter::buildDeclRefExpr(const std::string& symbol, int line, int col, const std::string& file) { nlohmann::json j; j["kind"] = "DeclRef"; j["symbol"] = symbol; j["loc"] = makeLoc(line, col, file); return j; } nlohmann::json SilToJsonConverter::buildUnaryExpr(const std::string& op_name, nlohmann::json sub, int line, int col, const std::string& file) { nlohmann::json j; if (op_name == "deref" || op_name == "Deref") { j["kind"] = "Deref"; j["unary_op"] = "Deref"; } else { j["kind"] = "UnaryOp"; j["unary_op"] = op_name; } j["sub"] = std::move(sub); j["loc"] = makeLoc(line, col, file); return j; } nlohmann::json SilToJsonConverter::buildBinaryExpr(const std::string& op_name, nlohmann::json lhs, nlohmann::json rhs, int line, int col, const std::string& file) { nlohmann::json j; j["kind"] = "BinaryOp"; j["binary_op"] = op_name; j["lhs"] = std::move(lhs); j["rhs"] = std::move(rhs); j["loc"] = makeLoc(line, col, file); return j; } nlohmann::json SilToJsonConverter::buildCastExpr(const std::string& target_type, nlohmann::json sub, int line, int col, const std::string& file) { nlohmann::json j; j["kind"] = "Cast"; j["cast_type_name"] = target_type; j["sub"] = std::move(sub); j["loc"] = makeLoc(line, col, file); return j; } nlohmann::json SilToJsonConverter::buildMemberExpr(nlohmann::json base, const std::string& field, int line, int col, const std::string& file) { nlohmann::json j; j["kind"] = "Member"; j["member_base"] = std::move(base); j["member_field"] = field; j["loc"] = makeLoc(line, col, file); return j; } nlohmann::json SilToJsonConverter::buildCallExpr(const std::string& callee, const std::vector& args, int line, int col, const std::string& file) { nlohmann::json j; j["kind"] = "Call"; j["callee_name"] = callee; if (!args.empty()) { auto args_arr = nlohmann::json::array(); for (const auto& a : args) { args_arr.push_back(a); } j["call_args"] = std::move(args_arr); } j["loc"] = makeLoc(line, col, file); return j; } nlohmann::json SilToJsonConverter::operatorToExpr(const SilOperator& op, const std::string& file) { int line = op.source_info.valid() ? static_cast(op.source_info.loc.line) : 0; int col = op.source_info.valid() ? static_cast(op.source_info.loc.column) : 0; switch (op.kind) { case SilOperatorKind::Apply: case SilOperatorKind::BeginApply: case SilOperatorKind::PartialApply: { std::vector args; for (const auto& arg_val : op.apply_args) { args.push_back(buildDeclRefExpr(arg_val, line, col, file)); } return buildCallExpr(op.callee_name, args, line, col, file); } case SilOperatorKind::Builtin: { std::vector args; for (const auto& operand : op.operands) { args.push_back(buildDeclRefExpr(operand.value, line, col, file)); } return buildCallExpr("builtin." + op.string_value, args, line, col, file); } case SilOperatorKind::FunctionRef: case SilOperatorKind::DynamicFunctionRef: case SilOperatorKind::GlobalAddr: return buildDeclRefExpr(op.callee_name, line, col, file); case SilOperatorKind::IntegerLiteral: return buildLiteralExpr(op.int_value, 64, true, line, col, file); case SilOperatorKind::FloatLiteral: return buildLiteralExpr( static_cast(std::strtod(op.string_value.c_str(), nullptr) * 1000000), 64, true, line, col, file); case SilOperatorKind::StringLiteral: return buildStringLiteralExpr(op.string_value, line, col, file); case SilOperatorKind::Struct: case SilOperatorKind::Tuple: { // Translate struct construction as ConstructorCall nlohmann::json j; j["kind"] = "ConstructorCall"; j["callee_name"] = op.type.name; j["loc"] = makeLoc(line, col, file); if (!op.operands.empty()) { auto args = nlohmann::json::array(); for (const auto& operand : op.operands) { args.push_back(buildDeclRefExpr(operand.value, line, col, file)); } j["call_args"] = std::move(args); } return j; } case SilOperatorKind::StructExtract: case SilOperatorKind::TupleExtract: { // Field access nlohmann::json base; if (!op.operands.empty()) { base = buildDeclRefExpr(op.operands[0].value, line, col, file); } std::string field; if (!op.decl_ref.parts.empty()) { // Join decl ref parts for (size_t i = 0; i < op.decl_ref.parts.size(); ++i) { if (i > 0) field += "."; field += op.decl_ref.parts[i]; } } else if (op.int_value != 0) { field = "field_" + std::to_string(op.int_value); } return buildMemberExpr(std::move(base), field, line, col, file); } case SilOperatorKind::UncheckedRefCast: case SilOperatorKind::UncheckedAddrCast: case SilOperatorKind::UncheckedTrivialBitCast: case SilOperatorKind::UncheckedBitwiseCast: case SilOperatorKind::Upcast: case SilOperatorKind::UnconditionalCheckedCast: { nlohmann::json sub; if (!op.operands.empty()) { sub = buildDeclRefExpr(op.operands[0].value, line, col, file); } return buildCastExpr(op.type.name, std::move(sub), line, col, file); } case SilOperatorKind::Load: case SilOperatorKind::LoadBorrow: { if (!op.operands.empty()) { return buildUnaryExpr("Deref", buildDeclRefExpr(op.operands[0].value, line, col, file), line, col, file); } break; } case SilOperatorKind::Store: { if (op.operands.size() > 1) { return buildBinaryExpr("Store", buildDeclRefExpr(op.operands[1].value, line, col, file), buildDeclRefExpr(op.operands[0].value, line, col, file), line, col, file); } break; } case SilOperatorKind::CondFail: { // cond_fail is a conditional that forces failure — model as // a BinaryOp (EQ condition → trap) if (!op.operands.empty()) { return buildBinaryExpr( "CondFail", buildDeclRefExpr(op.operands[0].value, line, col, file), buildStringLiteralExpr(op.string_value.empty() ? "force_unwrap" : op.string_value, line, col, file), line, col, file); } break; } default: break; } return nlohmann::json(); } } // namespace fold_swift