Prevent a stack overflow while creating a struct

I create a JSON object with the struct analyzed. I want this JSON to summarize multiple returns of a function in order to create information about scans of a domain. Creating this JSON happens by executing multiple functions in one "parent" function. How can I improve this?
This looks like heavy nested functions.

pub async fn result(
    domain: String
) -> Result<String, error_handler::ExtractCodeError> {

    info!("Analyze domain...");

    let blacklist: Value = 
        check_blacklist_domain(domain.clone()).await?;
    let ip_addr: String = get_ip(&domain)?;

    let (ip, geo_loc, gdpr_friendly) = 
        whois(Some(ip_addr.clone())).await?;
    let traceroute: Vec<String> = get_route(ip_addr)
        .unwrap()
        .nodes
        .into_iter()
        .map(|f| f.ip_addr.to_string())
        .collect::<Vec<String>>();

    debug!("Traceroute: {:#?}", traceroute);
    // Getting the stack overflow here
    let analyzed: Analyze = Analyze {
        ip: ip,
        blacklist: blacklist,
        geo_loc: geo_loc,
        gdpr_friendly: gdpr_friendly,
        traceroute: traceroute,
    };
    debug!("Analyze: {}", analyzed);

    let hash: String = hashing::encode(analyzed.to_string());
    
    Ok(hash)
}
1 Like

In general, there's nothing wrong with functions calling other functions (even lots of functions).
In fact I find the structure of your snippet pretty fine looking (without being able to see any more).

It's pretty weird that you're getting the overflow there, where you create a new Analyze struct.
All that I can think of there is something is getting copied that definitely shouldn't be?
How sure are you that that's where it's happening? And is there any more context to offer?

Usually these overflows are caused by recursion, which doesn't seem to be happening here (but isn't obvious). It's also possible that stack memory is getting exhausted, but for it to happen in that spot, there'd have to be an implicit Copy on a type that definitely shouldn't implement it IMO.

Also, just as an aside, this could be simplified some. But, it's just syntactic sugar. No effect on overflow:

    let analyzed = Analyze { ip, blacklist, geo_loc, gdpr_friendly, traceroute };

You don't need to specify the type, especially when the right hand side is constructing a struct.
Also, when a struct field has the same name as a variable, you don't need to spell it out.
ie. writing geo_loc: geo_loc is equivalent to just writing geo_loc.

1 Like

The other possibility I see is that the static size of Analyze is too large to fit on the stack at all, but that also seems pretty far-fetched. You could always try Boxing some of the values so that they're allocated on the heap instead of the stack, but I don't see any likely candidates.

1 Like

Analyze is not really large.
blacklist is a simple JSON that shows the name of a list,
geo_loc returns the country code where the IP addr is located,
gdpr_friendly returns a bool,
traceroute returns a String vec of the route from host to dst IP.

The overflow has a different origin than the shown function. Because I replaced all declared variables with empty values and that returns a stack overflow too.

I fixed it. After checking the Display trait of my struct I figured out that to_string() was not working because of the Vec<String> key-value.

#[derive(Serialize)]
pub struct Analyze {
    ip: String,
    blacklist: Value,
    geo_loc: String,
    gdpr_friendly: bool,
    traceroute: Vec<String>
}

impl fmt::Display for Analyze {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // before: write!(f, "{}", self)
        // traceroute key had to be displayed with {:#?}
        write!(f, 
            "{{\"ip\":{}, \"blacklist\":{}, \"geo_loc\":{}, \"gdpr_friendly\":{}, \"traceroute\":{:#?}}}",
            self.ip, self.blacklist, self.geo_loc, self.gdpr_friendly, self.traceroute)
    }
}
1 Like

Problem is not with Vec itself. Problem is that calling write!(f, "{}", self) inside impl Display is always an unconditional recursion.

5 Likes

What changes do you propose?
I've followed the Rust by Example

No you didn't. The example uses self.0 not self. The way you did it instead is thus already correct.

2 Likes

Alternatively, if what you really want is the JSON representation, you could dispatch to serde_json::to_writer_pretty:

(untested)

impl fmt::Display for Analyze {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Ok(serde_json::to_writer_pretty(f, self)?)
    }
}
1 Like

Haha, I even created a crate for this that abstracts your implementation into a procedural macro.

4 Likes