fold/tools/fold-swift/swan_sil_parser.cpp
2026-07-20 14:52:50 -04:00

1889 lines
63 KiB
C++

// 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 <cctype>
#include <charconv>
#include <cstdlib>
#include <initializer_list>
#include <sstream>
#include <stdexcept>
namespace fold_swift {
namespace {
bool typeNameContainsAny(std::string_view type_name,
std::initializer_list<std::string_view> 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<std::string(const std::string&)> 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<std::size_t>(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<unsigned char>(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<unsigned char>(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<unsigned char>(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<std::uint32_t>(line);
last_comment_loc_.column = static_cast<std::uint32_t>(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<unsigned char>(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<int>(parseInt());
}
return 0;
}
SilLoc SilTextParser::parseLoc() {
SilLoc loc;
// Format: "<path>:<line>:<col>"
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<std::uint32_t>(parseInt());
if (skipToken(":")) {
loc.column = static_cast<std::uint32_t>(parseInt());
}
} catch (const std::exception&) {
// Ignore parse errors in source location
}
}
loc.file = path;
skipWhitespaceAndComments();
return loc;
}
SilSourceInfo SilTextParser::parseSourceInfo() {
SilSourceInfo si;
// Format: , loc "<path>:<line>:<col>", scope <n>
bool had_info = false;
while (true) {
if (skipToken(", loc")) {
si.loc = parseLoc();
had_info = true;
} else if (skipToken(", scope")) {
si.scope_ref = static_cast<std::uint32_t>(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<A, B>
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<T>
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: <T, U where T: P>
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<unsigned char>(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<int>(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<int>(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<unsigned char>(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<int>(op.source_info.loc.line);
return fn;
}
}
}
if (function_entry_loc.valid()) {
fn.start_line = static_cast<int>(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<unsigned char>(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<std::string, SilOperatorKind> 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<unsigned char>(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<unsigned char>(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<std::uint32_t>(fn.blocks.size());
for (const auto& b : fn.blocks) {
mod.total_operators += static_cast<std::uint32_t>(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<int>(op.source_info.loc.line) : 0,
op.source_info.valid() ? static_cast<int>(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<int>(op.source_info.loc.line) : 0,
op.source_info.valid() ? static_cast<int>(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<int>(op.source_info.loc.line) : 0,
op.source_info.valid() ? static_cast<int>(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<int>(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<nlohmann::json>& 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<int>(op.source_info.loc.line) : 0;
int col = op.source_info.valid() ? static_cast<int>(op.source_info.loc.column) : 0;
switch (op.kind) {
case SilOperatorKind::Apply:
case SilOperatorKind::BeginApply:
case SilOperatorKind::PartialApply: {
std::vector<nlohmann::json> 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<nlohmann::json> 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::int64_t>(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