1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//-
// Copyright 2017, 2018 The proptest developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Arbitrary implementations for `std::env`.

use std::env::*;
use std::ffi::OsString;
use std::iter::once;

use crate::arbitrary::*;
use crate::strategy::statics::static_map;
use crate::strategy::*;

// FIXME: SplitPaths when lifetimes in strategies are possible.

lazy_just!(
    Args, args;
    ArgsOs, args_os;
    Vars, vars;
    VarsOs, vars_os;
    JoinPathsError, jpe
);

#[cfg(not(target_os = "windows"))]
fn jpe() -> JoinPathsError {
    join_paths(once(":")).unwrap_err()
}

#[cfg(target_os = "windows")]
fn jpe() -> JoinPathsError {
    join_paths(once("\"")).unwrap_err()
}

// Algorithm from: https://stackoverflow.com/questions/47749164
#[cfg(any(target_os = "windows", test))]
fn make_utf16_invalid(buf: &mut [u16], p: usize) {
    // Verify that length is non-empty.
    // An empty string is always valid UTF-16.
    assert!(buf.len() > 0);

    // If first elem or previous entry is not a leading surrogate.
    let gen_trail = 0 == p || 0xd800 != (buf[p - 1] & 0xfc00);
    // If last element or succeeding entry is not a traililng surrogate.
    let gen_lead = p == buf.len() - 1 || 0xdc00 != (buf[p + 1] & 0xfc00);
    let (force_bits_mask, force_bits_value) = if gen_trail {
        if gen_lead {
            // Trailing or leading surrogate.
            (0xf800, 0xd800)
        } else {
            // Trailing surrogate.
            (0xfc00, 0xdc00)
        }
    } else {
        // Leading surrogate.
        // Note that `gen_lead` and `gen_trail` could both be false here if `p`
        // lies exactly between a leading and a trailing surrogate. In this
        // case, it doesn't matter what we do because the UTF-16 will be
        // invalid regardless, so just always force a leading surrogate.
        (0xfc00, 0xd800)
    };
    debug_assert_eq!(0, (force_bits_value & !force_bits_mask));
    buf[p] = (buf[p] & !force_bits_mask) | force_bits_value;
}

#[cfg(not(target_arch = "wasm32"))]
mod var_error {
    use super::*;

    /// Generates the set of `WTF-16 \ UTF-16` and makes
    /// an `OsString` that is not a valid String from it.
    #[cfg(target_os = "windows")]
    fn osstring_invalid_string() -> impl Strategy<Value = OsString> {
        use std::os::windows::ffi::OsStringExt;
        let size = 1..::std::u16::MAX as usize;
        let vec_gen = crate::collection::vec(..::std::u16::MAX, size.clone());
        (size, vec_gen).prop_map(|(p, mut sbuf)| {
            // Not quite a uniform distribution due to clamping,
            // but probably good enough
            let p = ::std::cmp::min(p, sbuf.len() - 1);
            make_utf16_invalid(&mut sbuf, p);
            OsString::from_wide(sbuf.as_slice())
                .into_string()
                .unwrap_err()
        })
    }

    #[cfg(not(target_os = "windows"))]
    fn osstring_invalid_string() -> impl Strategy<Value = OsString> {
        use crate::arbitrary::_std::string::not_utf8_bytes;
        use std::os::unix::ffi::OsStringExt;
        static_map(not_utf8_bytes(true), OsString::from_vec)
    }

    arbitrary!(VarError,
        TupleUnion<(
            WA<Just<Self>>,
            WA<SFnPtrMap<BoxedStrategy<OsString>, Self>>
        )>;
        prop_oneof![
            Just(VarError::NotPresent),
            static_map(osstring_invalid_string().boxed(), VarError::NotUnicode)
        ]
    );
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::num;
    use crate::test_runner::Config;

    no_panic_test!(
        args => Args,
        args_os => ArgsOs,
        vars => Vars,
        vars_os => VarsOs,
        join_paths_error => JoinPathsError,
        var_error => VarError
    );

    proptest! {
        #![proptest_config(Config {
            cases: 65536,
            .. Config::default()
        })]

        #[test]
        fn make_utf16_invalid_doesnt_panic(
            mut buf in [num::u16::ANY; 3],
            p in 0usize..3
        ) {
            make_utf16_invalid(&mut buf, p);
        }
    }
}