use super::{
Analysis, ApiError, Cancellation, MAX_FUNCTION_BYTES, Output,
types::{Data, Pagination, TextPage},
};
use crate::{
c::{COptions, c_one_options},
fields_c::apply_field_sugar,
locations::locations,
runtime::RuntimeMap,
slice::slice_fn_with_mode,
};
impl Analysis {
pub(super) fn render(
&self,
address: &str,
target: Option<&str>,
fields: bool,
keep_gotos: bool,
page: &TextPage,
cancel: &Cancellation,
) -> Output {
if !(4..=16384).contains(&page.limit) {
return Err(ApiError::new("invalid_page", "text limit must be 4..16384 bytes"));
}
let function = self.function(address)?;
if function.end.saturating_sub(function.start) > MAX_FUNCTION_BYTES {
return Err(ApiError::new("function_too_large", "pseudo-C and slices support functions up to 32 KiB"));
}
if target.is_some_and(|t| t.is_empty() || t.len() > 256) {
return Err(ApiError::new("invalid_target", "slice target must contain 1..256 bytes"));
}
cancel.checkpoint()?;
let text = if let Some(target) = target {
let mut out = Vec::new();
slice_fn_with_mode(&self.image, &format!("{:#x}", function.start), target, fields, keep_gotos, &mut out)
.map_err(|e| ApiError::new("slice_failed", e.to_string()))?;
String::from_utf8(out).map_err(|e| ApiError::new("render_failed", e.to_string()))?
} else {
let (locs, runtime) = (locations(&self.image), RuntimeMap::detect(&self.image));
cancel.checkpoint()?;
let (_, raw) = c_one_options(
&self.image,
&locs,
&runtime,
function.start,
COptions { keep_gotos, ..Default::default() },
)
.map_err(|e| ApiError::new("render_failed", e))?;
if fields { apply_field_sugar(&self.image, &function, &raw) } else { raw }
};
cancel.checkpoint()?;
if text.len() > 2 * 1024 * 1024 {
return Err(ApiError::new("render_too_large", "rendered text exceeds 2 MiB"));
}
if page.offset > text.len() || !text.is_char_boundary(page.offset) {
return Err(ApiError::new("invalid_offset", "offset must name a UTF-8 boundary within the text"));
}
let mut end = text.len().min(page.offset.saturating_add(page.limit));
while !text.is_char_boundary(end) {
end -= 1;
}
let next_offset = (end < text.len()).then_some(end);
let meta = Pagination {
unit: "utf8_bytes",
offset: page.offset,
returned: end - page.offset,
next_offset,
complete: next_offset.is_none(),
};
let fragment = page.offset > 0 || next_offset.is_some();
let mut warnings =
vec!["heuristic_reconstruction", "fde_range_is_not_source_function", "cancellation_between_render_phases"];
if fragment {
warnings.push("text_is_a_fragment");
}
Ok((
Data::Text {
format: if target.is_some() { "slice" } else { "pseudo_c" },
function: self.function_info(&function),
text: text[page.offset..end].to_owned(),
total_bytes: text.len(),
fragment,
},
Some(meta),
warnings,
))
}
}