1889 lines
63 KiB
C++
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
|