-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[ios] Guard against looking up annotation contexts MGLAnnotationTagNotFound #8686
[ios] Guard against looking up annotation contexts MGLAnnotationTagNotFound #8686
Conversation
Will update this to include macOS and changelog entries next cc @1ec5 |
c4c0f29
to
5c2a7bc
Compare
For macOS, I added the guard to:
These methods already had the guard:
|
platform/ios/CHANGELOG.md
Outdated
@@ -2,6 +2,10 @@ | |||
|
|||
Mapbox welcomes participation and contributions from everyone. Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) to get started. | |||
|
|||
## 3.5.2 | |||
|
|||
* Fixed an issue that caused a crash when the user annotation was presenting a callout view and the map was moved. ([#8686](https://github.com/mapbox/mapbox-gl-native/pull/8686)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we tend to call it the “user location annotation”, to make clear that it’s not some annotation otherwise belonging to the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made that change for the iOS changelog in 99eee0a. In that same commit, I also made the macOS changelog less specific since the possibility of a crash there is more theoretical since there is currently no user location annotation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM and works in practice, but I’ll let someone more familiar with the annotation code give the official 🍏.
if ( ! _annotationContextsByAnnotationTag.count(tag)) | ||
{ | ||
if ( ! _annotationContextsByAnnotationTag.count(tag) || | ||
tag == MGLAnnotationTagNotFound) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this return userLocation
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind. The code used to overload MGLAnnotationTagNotFound to mean the user location annotation tag. But in retrospect that's a pretty risky practice. This change moves us further away from that practice, forcing us to explicitly account for the user location annotation explicitly, which is a good thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
platform/ios/src/MGLMapView.mm
Outdated
@@ -3550,6 +3555,10 @@ - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL) | |||
auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), | |||
[&](const MGLAnnotationTag annotationTag) | |||
{ | |||
if (annotationTag == MGLAnnotationTagNotFound) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is redundant to the check in -annotationWithTag:. Should we put an assertion in right above for this case? Is it legitimate for the user location annotation to be a nearby annotation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going to answer my own question here: -annotationTagsInRect:
wouldn’t know about the user location annotation, so it should never return MGLAnnotationTagNotFound
. I still think we should either rely on the assertion below or assert here about MGLAnnotationTagNotFound
, in case some sort of internal consistency leads it to show up in nearbyAnnotations
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. I'll remove this check to rely on the assertion below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, this is the assertion we hit in debug builds if annotations are removed at almost the same time and just barely before the user tries to select them (part of the repro from #8163 (comment)).
The check below recently added in #8637 to make sure the annotation is not nil protects release builds from crashing at the call to std::map::at
also just below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, but I think we should be less shy about asserting about some of these cases in debug builds.
platform/ios/src/MGLMapView.mm
Outdated
@@ -3550,6 +3555,10 @@ - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL) | |||
auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), | |||
[&](const MGLAnnotationTag annotationTag) | |||
{ | |||
if (annotationTag == MGLAnnotationTagNotFound) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going to answer my own question here: -annotationTagsInRect:
wouldn’t know about the user location annotation, so it should never return MGLAnnotationTagNotFound
. I still think we should either rely on the assertion below or assert here about MGLAnnotationTagNotFound
, in case some sort of internal consistency leads it to show up in nearbyAnnotations
.
[self updateCalloutView]; | ||
// Update the annotation’s backing geometry to match the annotation model object. Any associated annotation view is also moved by side effect. However, -updateAnnotationViews disables the view’s animation actions, because it can’t distinguish between moves due to the viewport changing and moves due to the annotation’s coordinate changing. | ||
_mbglMap->updateAnnotation(annotationTag, mbgl::SymbolAnnotation { point, symbolName.UTF8String }); | ||
[self updateCalloutView]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is correct with respect to the user location annotation, because we rely on Core Location, not KVO, to tell us when the model object has changed.
…or_merge * release-ios-v3.5.0-android-v5.0.0: (31 commits) [qt] Renamed qt5 formula to qt macos-v0.4.1 [ios] Update podspecs and changelog for v3.5.2 [ios, macos] Guard against looking up annotation contexts MGLAnnotationTagNotFound (mapbox#8686) [ios] Update podspecs and changelog for v3.5.1 [ios, macos] Hardened std::map usage in MGLMapVIew [ios] replaced link to select a feature example (mapbox#8651) [ios, macos] Rename Data-driven styling guide (mapbox#8627) [ios] assertion to check if an annotationTag > 0 (mapbox#8588) [ios, macos] Preserve symlinks when zipping framework [ios] Silence incompatible type warning for callout view (mapbox#8608) Release android v5.0.2 (mapbox#8629) [macos] Removed MGLUserTrackingMode from jazzy ToC [macos] Fixed broken images in DDS guide [core] cache binary shaders on Android [core] Extract and de-templatize several Program static methods macos-v0.4.0 [ios, macos] Updated changelogs [ios] Replaced UIActionSheet, UIAlertView with UIAlertController [ios, macos] Copyedited data-driven styling guides ...
Fixes #8674
In #8637 we stopped indexing into std::map to avoid silently creating bogus entries in collections like
_annotationContextsByAnnotationTag
. However, since the user annotation is effectively always represented withMGLAnnotationTagNotFound
(that equalsUINT32_MAX
) and the user annotation is processed similarly to regular annotations in some cases, we opened the door to a crash.Specifically, with #8637, attempts to lookup an annotation context for the user annotation's tag (
MGLAnnotationTagNotFound
) in order to update the callout view triggered the map'sstd::out_of_range
exception.This PR guards against this by first checking to see if the annotation tag involved in the context lookup for the callout view update is
MGLAnnotationTagNotFound
or not. It also adds several more similar defensive checks in other areas that attempt to obtain a context.Methods that are now guarded:
handleSingleTapGesture:
updateCalloutView:
observeValueForKeyPath:
(for coordinate)visibleAnnotationsInRect:
selectedAnnotation:
Methods that already had this check in place:
updateCalloutView:
viewForAnnotation:
removeAnnotations:
imageOfAnnotationWithTag:
cc @friedbunny @1ec5 @fabian-guerra