History

[clone] a better workaround for the 'drop scope' issue

Sebastian Thiel committed 6 years ago

Commit 3ccf32b1 parent 6b479231 file · +28 −56Browse files

Unified · Split

git-protocol/src/fetch.rs+28−56
@@ -84,36 +84,6 @@ impl From<client::Capabilities> for Capabilities { · 83 unmodified lines
8484 // V1
8585 // 0098want 808e50d724f604f69ab93c6da2919c014667bedb multi_ack_detailed no-done side-band-64k thin-pack ofs-delta deepen-since deepen-not agent=git/2.28.0
8686
87−/// This is only needed because for some reason, a match statement takes the drop scope of the enclosing scope, and not of
88−/// the match arm. This makes it think that a borrowed Ok(value) is still in scope, even though we are in the Err(err) branch.
89−/// The idea here is that we can workaround this by setting the scope to the level of the function, by splitting everything up accordingly.
90−/// Tracking issue: https://github.com/rust-lang/rust/issues/76149 - discarded, as currently documented/known behaviour.
91−fn perform_handshake<'a, F: FnMut(credentials::Action) -> credentials::Result>(
92− transport: &'a mut impl client::Transport,
93− delegate: &mut impl Delegate,
94− mut authenticate: Option<&mut F>,
95− out: &mut Option<credentials::NextAction>,
96−) -> Result<client::SetServiceResponse<'a>, Error> {
97− if let Some(authenticate) = authenticate.as_mut() {
98− let url = transport.to_url();
99− let credentials::Outcome { identity, next } = authenticate(credentials::Action::Fill(&url))?;
100− transport.set_identity(identity)?;
101− // Remember the output of the authentication function to provide in case we still error with
102− // a permission issue.
103− *out = Some(next);
104− };
105−
106− let response = transport.handshake(Service::UploadPack)?;
107− if let Some(authenticate) = authenticate {
108− authenticate(
109− out.take()
110− .expect("we put next action in before if an authenticator is present")
111− .approve(),
112− )?;
113− }
114− Ok(response)
115−}
116−
11787 /// This types sole purpose is to 'disable' the destructor on the Box provided in the `SetServiceResponse` type
11888 /// by leaking the box. We provide a method to restore the box and drop it right away to not actually leak.
11989 /// However, we do leak in error cases because we don't call the manual destructor then.
@@ -140,7 +110,9 @@ impl<'a> LeakedSetServiceResponse<'a> { · 20 unmodified lines
140110 if let Some(b) = self.refs.take() {
141111 // SAFETY: We are bound to lifetime 'a, which is the lifetime of the thing pointed to by the trait object in the box.
142112 // Thus we can only drop the box while that thing is indeed valid, due to Rusts standard lifetime rules.
143− // The box itself was leaked by us
113+ // The box itself was leaked by us.
114+ // Note that this is only required because Drop scopes are the outer ones in the match, not the match arms, making them
115+ // too broad to be usable intuitively. I consider this a technical shortcoming and hope there is a way to resolve it.
144116 #[allow(unsafe_code)]
145117 unsafe {
146118 drop(Box::from_raw(b as *mut _))
@@ -154,32 +126,32 @@ pub fn fetch<F: FnMut(credentials::Action) -> credentials::Result>( · 7 unmodified lines
154126 mut delegate: impl Delegate,
155127 mut authenticate: F,
156128 ) -> Result<(), Error> {
157− let mut next = None;
158− let mut res: LeakedSetServiceResponse =
159− match perform_handshake(&mut transport, &mut delegate, None::<&mut F>, &mut next).map(Into::into) {
160− Ok(v) => Ok(v),
161− Err(Error::Transport(client::Error::Io { err })) if err.kind() == io::ErrorKind::PermissionDenied => {
162− perform_handshake(&mut transport, &mut delegate, Some(&mut authenticate), &mut next)
163− .map_err(|err| {
164− if let Some(next) = next {
165− match &err {
166− // Still no permission? Reject the credentials
167− Error::Transport(client::Error::Io { err })
168− if err.kind() == io::ErrorKind::PermissionDenied =>
169− {
170− authenticate(next.reject())
171− }
172− // Otherwise it's some other error, still OK to approve the credentials
173− _ => authenticate(next.approve()),
174− }
175− .ok();
176− };
177− err
178− })
179− .map(Into::into)
129+ let mut res: LeakedSetServiceResponse = match transport.handshake(Service::UploadPack).map(Into::into) {
130+ Ok(v) => Ok(v),
131+ Err(client::Error::Io { err }) if err.kind() == io::ErrorKind::PermissionDenied => {
132+ let url = transport.to_url();
133+ let credentials::Outcome { identity, next } = authenticate(credentials::Action::Fill(&url))?;
134+ transport.set_identity(identity)?;
135+ match transport.handshake(Service::UploadPack).map(Into::into) {
136+ Ok(v) => {
137+ authenticate(next.approve())?;
138+ Ok(v)
139+ }
140+ // Still no permission? Reject the credentials.
141+ Err(client::Error::Io { err }) if err.kind() == io::ErrorKind::PermissionDenied => {
142+ authenticate(next.reject())?;
143+ Err(client::Error::Io { err }.into())
144+ }
145+ // Otherwise it's some other error, still OK to approve the credentials. We also do this to not accidentally
146+ // discard credentials that have been previously stored.
147+ Err(err) => {
148+ authenticate(next.approve())?;
149+ Err(err.into())
150+ }
180151 }
181− Err(err) => Err(err),
182− }?;
152+ }
153+ Err(err) => Err(err),
154+ }?;
183155
184156 delegate.adjust_capabilities(res.actual_protocol, &mut res.capabilities);
185157 res.capabilities.set_agent_version();