From a1bc6948f4a6c0958b11e984bb837aa6d7f33225 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Tue, 8 Nov 2016 10:10:50 -0500 Subject: [PATCH 01/13] Define application-level exception handler of last resort. --- src/AudacityApp.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ src/AudacityApp.h | 1 + 2 files changed, 43 insertions(+) diff --git a/src/AudacityApp.cpp b/src/AudacityApp.cpp index 3c62c80b2..e6d21cc61 100644 --- a/src/AudacityApp.cpp +++ b/src/AudacityApp.cpp @@ -60,6 +60,7 @@ It handles initialization and termination by subclassing wxApp. #include #endif +#include "AudacityException.h" #include "AudacityLogger.h" #include "AboutDialog.h" #include "AColor.h" @@ -1084,6 +1085,47 @@ void AudacityApp::OnFatalException() exit(-1); } +bool AudacityApp::OnExceptionInMainLoop() +{ + // This function is invoked from catch blocks in the wxWidgets framework, + // and throw; without argument re-throws the exception being handled, + // letting us dispatch according to its type. + + try { throw; } + catch ( AudacityException &e ) { + // Here is the catch-all for our own exceptions + + // Use CallAfter to delay this to the next pass of the event loop, + // rather than risk doing it inside stack unwinding. + auto pProject = ::GetActiveProject(); + std::shared_ptr< AudacityException > pException { e.Move().release() }; + CallAfter( [=] // Capture pException by value! + { + + // Restore the state of the project to what it was before the + // failed operation + pProject->RollbackState(); + + pProject->RedrawProject(); + + // Give the user an alert + pException->DelayedHandlerAction(); + + } ); + + // Don't quit the program + return true; + } + catch ( ... ) { + // There was some other type of exception we don't know. + // Let the inherited function do throw; again and whatever else it does. + return wxApp::OnExceptionInMainLoop(); + } + + // Shouldn't ever reach this line + return false; +} + #if defined(EXPERIMENTAL_CRASH_REPORT) void AudacityApp::GenerateCrashReport(wxDebugReport::Context ctx) { diff --git a/src/AudacityApp.h b/src/AudacityApp.h index dabffbf2d..0129d430c 100644 --- a/src/AudacityApp.h +++ b/src/AudacityApp.h @@ -63,6 +63,7 @@ class AudacityApp final : public wxApp { bool OnInit(void) override; int OnExit(void) override; void OnFatalException() override; + bool OnExceptionInMainLoop() override; int FilterEvent(wxEvent & event); From f4e2fb5eace3f7f3fccbb2e7cc9ffaca2cf26bc5 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Fri, 25 Nov 2016 11:00:14 -0500 Subject: [PATCH 02/13] AudioIO::StopStream stops exceptions in many non-editing actions --- src/AudioIO.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 2ebdf08e3..756c4e398 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -296,6 +296,7 @@ writing audio. #include #include "AudacityApp.h" +#include "AudacityException.h" #include "Mix.h" #include "MixerBoard.h" #include "Resample.h" @@ -2436,8 +2437,13 @@ void AudioIO::StopStream() double recordingOffset = mLastRecordingOffset + latencyCorrection / 1000.0; - for (unsigned int i = 0; i < mCaptureTracks.size(); i++) - { + for (unsigned int i = 0; i < mCaptureTracks.size(); i++) { + // The calls to Flush, and (less likely) Clear and InsertSilence, + // may cause exceptions because of exhaustion of disk space. + // Stop those exceptions here, or else they propagate through too + // many parts of Audacity that are not effects or editing + // operations. GuardedCall ensures that the user sees a warning. + GuardedCall( [&] { WaveTrack* track = mCaptureTracks[i]; track->Flush(); @@ -2475,14 +2481,16 @@ void AudioIO::StopStream() track->SetOffset(track->GetStartTime() + recordingOffset); if(track->GetEndTime() < 0.) { - wxMessageDialog m(NULL, _("Latency Correction setting has caused the recorded audio to be hidden before zero.\nAudacity has brought it back to start at zero.\nYou may have to use the Time Shift Tool (<---> or F5) to drag the track to the right place."), + wxMessageDialog m(NULL, _( +"Latency Correction setting has caused the recorded audio to be hidden before zero.\nAudacity has brought it back to start at zero.\nYou may have to use the Time Shift Tool (<---> or F5) to drag the track to the right place."), _("Latency problem"), wxOK); m.ShowModal(); track->SetOffset(0.); } } } - } + } ); + } } } From eeb301e50de2207f27470fa0ae8188bb59b47133 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Tue, 22 Nov 2016 14:10:53 -0500 Subject: [PATCH 03/13] Gracefully handle disk exhaustion exceptions during recording --- src/AudioIO.cpp | 164 ++++++++++++++++++++++++++++++------------------ src/AudioIO.h | 9 +++ 2 files changed, 112 insertions(+), 61 deletions(-) diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index 756c4e398..4320e964c 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -1650,6 +1650,8 @@ int AudioIO::StartStream(const ConstWaveTrackArray &playbackTracks, double t0, double t1, const AudioIOStartStreamOptions &options) { + auto cleanup = finally ( [this] { ClearRecordingException(); } ); + if( IsBusy() ) return 0; @@ -2250,6 +2252,8 @@ void AudioIO::SetMeters() void AudioIO::StopStream() { + auto cleanup = finally ( [this] { ClearRecordingException(); } ); + if( mPortStreamV19 == NULL #ifdef EXPERIMENTAL_MIDI_OUT && mMidiStream == NULL @@ -3420,6 +3424,25 @@ void AudioIO::FillBuffers() { unsigned int i; + auto delayedHandler = [this] ( AudacityException * pException ) { + // In the main thread, stop recording + // This is one place where the application handles disk + // exhaustion exceptions from wave track operations, without rolling + // back to the last pushed undo state. Instead, partial recording + // results are pushed as a NEW undo state. For this reason, as + // commented elsewhere, we want an exception safety guarantee for + // the output wave tracks, after the failed append operation, that + // the tracks remain as they were after the previous successful + // (block-level) appends. + + // Note that the Flush in StopStream() may throw another exception, + // but StopStream() contains that exception, and the logic in + // AudacityException::DelayedHandlerAction prevents redundant message + // boxes. + StopStream(); + DefaultDelayedHandlerAction{}( pException ); + }; + if (mPlaybackTracks.size() > 0) { // Though extremely unlikely, it is possible that some buffers @@ -3595,78 +3618,97 @@ void AudioIO::FillBuffers() } } // end of playback buffering - if (mCaptureTracks.size() > 0) // start record buffering - { - auto commonlyAvail = GetCommonlyAvailCapture(); + if (!mRecordingException && + mCaptureTracks.size() > 0) + GuardedCall( [&] { + // start record buffering + auto commonlyAvail = GetCommonlyAvailCapture(); - // - // Determine how much this will add to captured tracks - // - double deltat = commonlyAvail / mRate; + // + // Determine how much this will add to captured tracks + // + double deltat = commonlyAvail / mRate; - if (mAudioThreadShouldCallFillBuffersOnce || - deltat >= mMinCaptureSecsToCopy) - { - // Append captured samples to the end of the WaveTracks. - // The WaveTracks have their own buffering for efficiency. - AutoSaveFile blockFileLog; - auto numChannels = mCaptureTracks.size(); - - for( i = 0; (int)i < numChannels; i++ ) + if (mAudioThreadShouldCallFillBuffersOnce || + deltat >= mMinCaptureSecsToCopy) { - auto avail = commonlyAvail; - sampleFormat trackFormat = mCaptureTracks[i]->GetSampleFormat(); + // Append captured samples to the end of the WaveTracks. + // The WaveTracks have their own buffering for efficiency. + AutoSaveFile blockFileLog; + auto numChannels = mCaptureTracks.size(); - AutoSaveFile appendLog; - - if( mFactor == 1.0 ) + for( i = 0; (int)i < numChannels; i++ ) { - SampleBuffer temp(avail, trackFormat); - const auto got = + auto avail = commonlyAvail; + sampleFormat trackFormat = mCaptureTracks[i]->GetSampleFormat(); + + AutoSaveFile appendLog; + + if( mFactor == 1.0 ) + { + SampleBuffer temp(avail, trackFormat); + const auto got = mCaptureBuffers[i]->Get(temp.ptr(), trackFormat, avail); - // wxASSERT(got == avail); - // but we can't assert in this thread - wxUnusedVar(got); - mCaptureTracks[i]-> Append(temp.ptr(), trackFormat, avail, 1, - &appendLog); - } - else - { - size_t size = lrint(avail * mFactor); - SampleBuffer temp1(avail, floatSample); - SampleBuffer temp2(size, floatSample); - const auto got = + // wxASSERT(got == avail); + // but we can't assert in this thread + wxUnusedVar(got); + // see comment in second handler about guarantee + mCaptureTracks[i]-> Append(temp.ptr(), trackFormat, avail, 1, + &appendLog); + } + else + { + size_t size = lrint(avail * mFactor); + SampleBuffer temp1(avail, floatSample); + SampleBuffer temp2(size, floatSample); + const auto got = mCaptureBuffers[i]->Get(temp1.ptr(), floatSample, avail); - // wxASSERT(got == avail); - // but we can't assert in this thread - wxUnusedVar(got); - /* we are re-sampling on the fly. The last resampling call - * must flush any samples left in the rate conversion buffer - * so that they get recorded - */ - const auto results = + // wxASSERT(got == avail); + // but we can't assert in this thread + wxUnusedVar(got); + /* we are re-sampling on the fly. The last resampling call + * must flush any samples left in the rate conversion buffer + * so that they get recorded + */ + const auto results = mResample[i]->Process(mFactor, (float *)temp1.ptr(), avail, - !IsStreamActive(), (float *)temp2.ptr(), size); - size = results.second; - mCaptureTracks[i]-> Append(temp2.ptr(), floatSample, size, 1, - &appendLog); + !IsStreamActive(), (float *)temp2.ptr(), size); + size = results.second; + // see comment in second handler about guarantee + mCaptureTracks[i]-> Append(temp2.ptr(), floatSample, size, 1, + &appendLog); + } + + if (!appendLog.IsEmpty()) + { + blockFileLog.StartTag(wxT("recordingrecovery")); + blockFileLog.WriteAttr(wxT("id"), mCaptureTracks[i]->GetAutoSaveIdent()); + blockFileLog.WriteAttr(wxT("channel"), (int)i); + blockFileLog.WriteAttr(wxT("numchannels"), numChannels); + blockFileLog.WriteSubTree(appendLog); + blockFileLog.EndTag(wxT("recordingrecovery")); + } } - if (!appendLog.IsEmpty()) - { - blockFileLog.StartTag(wxT("recordingrecovery")); - blockFileLog.WriteAttr(wxT("id"), mCaptureTracks[i]->GetAutoSaveIdent()); - blockFileLog.WriteAttr(wxT("channel"), (int)i); - blockFileLog.WriteAttr(wxT("numchannels"), numChannels); - blockFileLog.WriteSubTree(appendLog); - blockFileLog.EndTag(wxT("recordingrecovery")); - } + if (mListener && !blockFileLog.IsEmpty()) + mListener->OnAudioIONewBlockFiles(blockFileLog); } - - if (mListener && !blockFileLog.IsEmpty()) - mListener->OnAudioIONewBlockFiles(blockFileLog); - } - } // end of record buffering + // end of record buffering + }, + // handler + [this] ( AudacityException *pException ) { + if ( pException ) { + // So that we don't attempt to fill the recording buffer again + // before the main thread stops recording + SetRecordingException(); + return ; + } + else + // Don't want to intercept other exceptions (?) + throw; + }, + delayedHandler + ); } void AudioIO::SetListener(AudioIOListener* listener) diff --git a/src/AudioIO.h b/src/AudioIO.h index 4180bceef..e77cd3559 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -19,6 +19,7 @@ #include "MemoryX.h" #include +#include #ifdef USE_MIDI @@ -692,6 +693,14 @@ private: bool mSilentScrub; sampleCount mScrubDuration; #endif + + // A flag tested and set in one thread, cleared in another. Perhaps + // this guarantee of atomicity is more cautious than necessary. + wxAtomicInt mRecordingException {}; + void SetRecordingException() + { wxAtomicInc( mRecordingException ); } + void ClearRecordingException() + { if (mRecordingException) wxAtomicDec( mRecordingException ); } }; #endif From 87e75176c53219416714a78bdf358469bfd97df6 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 3 Dec 2016 14:33:37 -0500 Subject: [PATCH 04/13] Guarded calls in batch processing --- src/BatchProcessDialog.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/BatchProcessDialog.cpp b/src/BatchProcessDialog.cpp index 9ce8e3421..c7297977d 100644 --- a/src/BatchProcessDialog.cpp +++ b/src/BatchProcessDialog.cpp @@ -33,6 +33,7 @@ #include #include +#include "AudacityException.h" #include "ShuttleGui.h" #include "Prefs.h" #include "Project.h" @@ -192,7 +193,8 @@ void BatchProcessDialog::OnApplyToProject(wxCommandEvent & WXUNUSED(event)) bool success; { wxWindowDisabler wd(pD); - success = mBatchCommands.ApplyChain(); + success = GuardedCall< bool >( + [this]{ return mBatchCommands.ApplyChain(); } ); } if (!success) { @@ -356,15 +358,21 @@ void BatchProcessDialog::OnApplyToFiles(wxCommandEvent & WXUNUSED(event)) mList->SetItemImage(i, 1, 1); mList->EnsureVisible(i); - project->Import(files[i]); - project->OnSelectAll(); - if (!mBatchCommands.ApplyChain()) { - break; - } + auto success = GuardedCall< bool >( [&] { + project->Import(files[i]); + project->OnSelectAll(); + if (!mBatchCommands.ApplyChain()) + return false; - if (!pD->IsShown() || mAbort) { + if (!pD->IsShown() || mAbort) + return false; + + return true; + } ); + + if (!success) break; - } + UndoManager *um = project->GetUndoManager(); um->ClearStates(); project->OnSelectAll(); From 576d3e3013d3e7ce47aa8abe819921996f486167 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Mon, 7 Nov 2016 16:34:14 -0500 Subject: [PATCH 05/13] Don't let the catch-all block in Effect intercept the new exception --- src/effects/Effect.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/effects/Effect.cpp b/src/effects/Effect.cpp index 6a7d1ae9a..1685f6de6 100644 --- a/src/effects/Effect.cpp +++ b/src/effects/Effect.cpp @@ -39,6 +39,7 @@ greater use in future. #include "audacity/ConfigInterface.h" +#include "../AudacityException.h" #include "../AudioIO.h" #include "../LabelTrack.h" #include "../Mix.h" @@ -1612,8 +1613,18 @@ bool Effect::ProcessTrack(int count, { processed = ProcessBlock(mInBufPos.get(), mOutBufPos.get(), curBlockSize); } + catch( const AudacityException &e ) + { + // PRL: Bug 437: + // Pass this along to our application-level handler + throw; + } catch(...) { + // PRL: + // Exceptions for other reasons, maybe in third-party code... + // Continue treating them as we used to, but I wonder if these + // should now be treated the same way. return false; } wxASSERT(processed == curBlockSize); From 6525bb18cf06fbc53f0cf23d29f8ea575b3a2f37 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Mon, 21 Nov 2016 16:32:16 -0500 Subject: [PATCH 06/13] Translate exceptions to error codes in callback functions... ... That is what the library protocols allow, and libraries may be written in C and might corrupt their state if C++ exceptions pass through them. --- src/Project.cpp | 34 +++++++++---- src/commands/CommandManager.cpp | 85 ++++++++++++++++--------------- src/effects/nyquist/Nyquist.cpp | 28 ++++++----- src/import/ImportFLAC.cpp | 52 ++++++++++--------- src/import/ImportGStreamer.cpp | 24 +++++---- src/import/ImportMP3.cpp | 89 +++++++++++++++++---------------- 6 files changed, 173 insertions(+), 139 deletions(-) diff --git a/src/Project.cpp b/src/Project.cpp index 134a77eb7..65e4de40a 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -91,6 +91,7 @@ scroll information. It also has some status flags. #endif #endif +#include "AudacityException.h" #include "FreqWindow.h" #include "effects/Contrast.h" #include "AutoRecovery.h" @@ -446,19 +447,25 @@ public: bool OnDropFiles(wxCoord WXUNUSED(x), wxCoord WXUNUSED(y), const wxArrayString& filenames) override { - //sort by OD non OD. load Non OD first so user can start editing asap. - wxArrayString sortednames(filenames); + // Experiment shows that this function can be reached while there is no + // catch block above in wxWidgets. So stop all exceptions here. + return GuardedCall< bool > ( [&] { + //sort by OD non OD. load Non OD first so user can start editing asap. + wxArrayString sortednames(filenames); - ODManager::Pauser pauser; + ODManager::Pauser pauser; - sortednames.Sort(CompareNoCaseFileName); - for (unsigned int i = 0; i < sortednames.GetCount(); i++) { + sortednames.Sort(CompareNoCaseFileName); - mProject->Import(sortednames[i]); - } - mProject->HandleResize(); // Adjust scrollers for NEW track sizes. + for (unsigned int i = 0; i < sortednames.GetCount(); i++) { - return true; + mProject->Import(sortednames[i]); + } + + mProject->HandleResize(); // Adjust scrollers for NEW track sizes. + + return true; + } ); } private: @@ -487,7 +494,14 @@ bool ImportXMLTagHandler::HandleXMLTag(const wxChar *tag, const wxChar **attrs) } WaveTrackArray trackArray; - mProject->Import(strAttr, &trackArray); + + // Guard this call so that C++ exceptions don't propagate through + // the expat library + GuardedCall< void >( + [&] { mProject->Import(strAttr, &trackArray); }, + [&] (AudacityException*) { trackArray.clear(); } + ); + if (trackArray.empty()) return false; diff --git a/src/commands/CommandManager.cpp b/src/commands/CommandManager.cpp index 8b82cfde1..198f2091c 100644 --- a/src/commands/CommandManager.cpp +++ b/src/commands/CommandManager.cpp @@ -86,6 +86,7 @@ CommandManager. It holds the callback for one command. #include #include +#include "../AudacityException.h" #include "../Prefs.h" #include "../Project.h" @@ -190,47 +191,51 @@ public: int FilterEvent(wxEvent& event) override { - // Quickly bail if this isn't something we want. - wxEventType type = event.GetEventType(); - if (type != wxEVT_CHAR_HOOK && type != wxEVT_KEY_UP) - { + // Unguarded exception propagation may crash the program, at least + // on Mac while in the objective-C closure above + return GuardedCall< int > ( [&] { + // Quickly bail if this isn't something we want. + wxEventType type = event.GetEventType(); + if (type != wxEVT_CHAR_HOOK && type != wxEVT_KEY_UP) + { + return Event_Skip; + } + + // We must have a project since we will be working with the Command Manager + // and capture handler, both of which are (currently) tied to individual projects. + // + // Shouldn't they be tied to the application instead??? + AudacityProject *project = GetActiveProject(); + if (!project || !project->IsEnabled()) + { + return Event_Skip; + } + + // Make a copy of the event and (possibly) make it look like a key down + // event. + wxKeyEvent key = (wxKeyEvent &) event; + if (type == wxEVT_CHAR_HOOK) + { + key.SetEventType(wxEVT_KEY_DOWN); + } + + // Give the capture handler first dibs at the event. + wxWindow *handler = project->GetKeyboardCaptureHandler(); + if (handler && HandleCapture(handler, key)) + { + return Event_Processed; + } + + // Capture handler didn't want it, so ask the Command Manager. + CommandManager *manager = project->GetCommandManager(); + if (manager && manager->FilterKeyEvent(project, key)) + { + return Event_Processed; + } + + // Give it back to WX for normal processing. return Event_Skip; - } - - // We must have a project since we will be working with the Command Manager - // and capture handler, both of which are (currently) tied to individual projects. - // - // Shouldn't they be tied to the application instead??? - AudacityProject *project = GetActiveProject(); - if (!project || !project->IsEnabled()) - { - return Event_Skip; - } - - // Make a copy of the event and (possibly) make it look like a key down - // event. - wxKeyEvent key = (wxKeyEvent &) event; - if (type == wxEVT_CHAR_HOOK) - { - key.SetEventType(wxEVT_KEY_DOWN); - } - - // Give the capture handler first dibs at the event. - wxWindow *handler = project->GetKeyboardCaptureHandler(); - if (handler && HandleCapture(handler, key)) - { - return Event_Processed; - } - - // Capture handler didn't want it, so ask the Command Manager. - CommandManager *manager = project->GetCommandManager(); - if (manager && manager->FilterKeyEvent(project, key)) - { - return Event_Processed; - } - - // Give it back to WX for normal processing. - return Event_Skip; + }, MakeSimpleGuard( Event_Skip ) ); } private: diff --git a/src/effects/nyquist/Nyquist.cpp b/src/effects/nyquist/Nyquist.cpp index c9943fc0c..bee5dde8c 100644 --- a/src/effects/nyquist/Nyquist.cpp +++ b/src/effects/nyquist/Nyquist.cpp @@ -45,6 +45,7 @@ effects from this one class. #include #include "../../AudacityApp.h" +#include "../../AudacityException.h" #include "../../FileNames.h" #include "../../Internat.h" #include "../../LabelTrack.h" @@ -1832,23 +1833,26 @@ int NyquistEffect::StaticPutCallback(float *buffer, int channel, int NyquistEffect::PutCallback(float *buffer, int channel, long start, long len, long totlen) { - if (channel == 0) { - double progress = mScale*((float)(start+len)/totlen); + // Don't let C++ exceptions propagate through the Nyquist library + return GuardedCall( [&] { + if (channel == 0) { + double progress = mScale*((float)(start+len)/totlen); - if (progress > mProgressOut) { - mProgressOut = progress; + if (progress > mProgressOut) { + mProgressOut = progress; + } + + if (TotalProgress(mProgressIn+mProgressOut+mProgressTot)) { + return -1; + } } - if (TotalProgress(mProgressIn+mProgressOut+mProgressTot)) { - return -1; + if (mOutputTrack[channel]->Append((samplePtr)buffer, floatSample, len)) { + return 0; // success } - } - if (mOutputTrack[channel]->Append((samplePtr)buffer, floatSample, len)) { - return 0; // success - } - - return -1; // failure + return -1; // failure + }, MakeSimpleGuard( -1 ) ); // translate all exceptions into failure } void NyquistEffect::StaticOutputCallback(int c, void *This) diff --git a/src/import/ImportFLAC.cpp b/src/import/ImportFLAC.cpp index 172140614..3fcffba18 100644 --- a/src/import/ImportFLAC.cpp +++ b/src/import/ImportFLAC.cpp @@ -37,6 +37,7 @@ #include // needed for _("translated stings") even if we // don't have libflac available +#include "../AudacityException.h" #include "Import.h" #include "ImportPlugin.h" @@ -252,35 +253,38 @@ void MyFLACFile::error_callback(FLAC__StreamDecoderErrorStatus WXUNUSED(status)) FLAC__StreamDecoderWriteStatus MyFLACFile::write_callback(const FLAC__Frame *frame, const FLAC__int32 * const buffer[]) { - ArrayOf tmp{ frame->header.blocksize }; + // Don't let C++ exceptions propagate through libflac + return GuardedCall< FLAC__StreamDecoderWriteStatus > ( [&] { + auto tmp = ArrayOf< short >{ frame->header.blocksize }; - auto iter = mFile->mChannels.begin(); - for (unsigned int chn=0; chnmNumChannels; ++iter, ++chn) { - if (frame->header.bits_per_sample == 16) { - for (unsigned int s=0; sheader.blocksize; s++) { - tmp[s]=buffer[chn][s]; + auto iter = mFile->mChannels.begin(); + for (unsigned int chn=0; chnmNumChannels; ++iter, ++chn) { + if (frame->header.bits_per_sample == 16) { + for (unsigned int s=0; sheader.blocksize; s++) { + tmp[s]=buffer[chn][s]; + } + + iter->get()->Append((samplePtr)tmp.get(), + int16Sample, + frame->header.blocksize); + } + else { + iter->get()->Append((samplePtr)buffer[chn], + int24Sample, + frame->header.blocksize); } - - iter->get()->Append((samplePtr)tmp.get(), - int16Sample, - frame->header.blocksize); } - else { - iter->get()->Append((samplePtr)buffer[chn], - int24Sample, - frame->header.blocksize); + + mFile->mSamplesDone += frame->header.blocksize; + + mFile->mUpdateResult = mFile->mProgress->Update((wxULongLong_t) mFile->mSamplesDone, mFile->mNumSamples != 0 ? (wxULongLong_t)mFile->mNumSamples : 1); + if (mFile->mUpdateResult != ProgressResult::Success) + { + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } - } - mFile->mSamplesDone += frame->header.blocksize; - - mFile->mUpdateResult = mFile->mProgress->Update((wxULongLong_t) mFile->mSamplesDone, mFile->mNumSamples != 0 ? (wxULongLong_t)mFile->mNumSamples : 1); - if (mFile->mUpdateResult != ProgressResult::Success) - { - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + }, MakeSimpleGuard(FLAC__STREAM_DECODER_WRITE_STATUS_ABORT) ); } diff --git a/src/import/ImportGStreamer.cpp b/src/import/ImportGStreamer.cpp index 48a7a088a..5ad602c87 100644 --- a/src/import/ImportGStreamer.cpp +++ b/src/import/ImportGStreamer.cpp @@ -46,6 +46,7 @@ Licensed under the GNU General Public License v2 or later #endif // all the includes live here by default +#include "../AudacityException.h" #include "../SampleFormat.h" #include "../Tags.h" #include "../Internat.h" @@ -489,20 +490,23 @@ inline void GstSampleUnref(GstSample *p) { gst_sample_unref(p); } // I can't use static GstFlowReturn GStreamerNewSample(GstAppSink *appsink, gpointer data) { - GStreamerImportFileHandle *handle = (GStreamerImportFileHandle *)data; - static GMutex mutex; + // Don't let C++ exceptions propagate through GStreamer + return GuardedCall< GstFlowReturn > ( [&] { + GStreamerImportFileHandle *handle = (GStreamerImportFileHandle *)data; + static GMutex mutex; - // Get the sample - std::unique_ptr < GstSample, Deleter< GstSample, GstSampleUnref> > - sample{ gst_app_sink_pull_sample(appsink) }; + // Get the sample + std::unique_ptr < GstSample, Deleter< GstSample, GstSampleUnref> > + sample{ gst_app_sink_pull_sample(appsink) }; - // We must single thread here to prevent concurrent use of the - // Audacity track functions. - g_mutex_locker locker{ mutex }; + // We must single thread here to prevent concurrent use of the + // Audacity track functions. + g_mutex_locker locker{ mutex }; - handle->OnNewSample(GETCTX(appsink), sample.get()); + handle->OnNewSample(GETCTX(appsink), sample.get()); - return GST_FLOW_OK; + return GST_FLOW_OK; + }, MakeSimpleGuard(GST_FLOW_ERROR) ); } // ---------------------------------------------------------------------------- diff --git a/src/import/ImportMP3.cpp b/src/import/ImportMP3.cpp index 0fd3e5eb1..14663e944 100644 --- a/src/import/ImportMP3.cpp +++ b/src/import/ImportMP3.cpp @@ -39,6 +39,7 @@ #include #include +#include "../AudacityException.h" #include "../Prefs.h" #include "Import.h" #include "ImportPlugin.h" @@ -480,59 +481,61 @@ enum mad_flow output_cb(void *_data, struct mad_header const * WXUNUSED(header), struct mad_pcm *pcm) { - int samplerate; - struct private_data *data = (struct private_data *)_data; + // Don't C++ exceptions propagate through mad + return GuardedCall< mad_flow > ( [&] { + int samplerate; + struct private_data *data = (struct private_data *)_data; - samplerate= pcm->samplerate; - auto channels = pcm->channels; - const auto samples = pcm->length; + samplerate= pcm->samplerate; + auto channels = pcm->channels; + const auto samples = pcm->length; - /* If this is the first run, we need to create the WaveTracks that - * will hold the data. We do this now because now is the first - * moment when we know how many channels there are. */ + /* If this is the first run, we need to create the WaveTracks that + * will hold the data. We do this now because now is the first + * moment when we know how many channels there are. */ - if(data->channels.empty()) { - data->channels.resize(channels); + if(data->channels.empty()) { + data->channels.resize(channels); - sampleFormat format = (sampleFormat) gPrefs-> - Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample); + sampleFormat format = (sampleFormat) gPrefs-> + Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample); - for(auto &channel: data->channels) { - channel = data->trackFactory->NewWaveTrack(format, samplerate); - channel->SetChannel(Track::MonoChannel); + for(auto &channel: data->channels) { + channel = data->trackFactory->NewWaveTrack(format, samplerate); + channel->SetChannel(Track::MonoChannel); + } + + /* special case: 2 channels is understood to be stereo */ + if(channels == 2) { + data->channels.begin()->get()->SetChannel(Track::LeftChannel); + data->channels.rbegin()->get()->SetChannel(Track::RightChannel); + data->channels.begin()->get()->SetLinked(true); + } + data->numChannels = channels; + } + else { + // This is not the first run, protect us from libmad glitching + // on the number of channels + channels = data->numChannels; } - /* special case: 2 channels is understood to be stereo */ - if(channels == 2) { - data->channels.begin()->get()->SetChannel(Track::LeftChannel); - data->channels.rbegin()->get()->SetChannel(Track::RightChannel); - data->channels.begin()->get()->SetLinked(true); - } - data->numChannels = channels; - } - else { - // This is not the first run, protect us from libmad glitching - // on the number of channels - channels = data->numChannels; - } + /* TODO: get rid of this by adding fixed-point support to SampleFormat. + * For now, we allocate temporary float buffers to convert the fixed + * point samples into something we can feed to the WaveTrack. Allocating + * big blocks of data like this isn't a great idea, but it's temporary. + */ + FloatBuffers channelBuffers{ channels, samples }; + for(size_t smpl = 0; smpl < samples; smpl++) + for(int chn = 0; chn < channels; chn++) + channelBuffers[chn][smpl] = scale(pcm->samples[chn][smpl]); - /* TODO: get rid of this by adding fixed-point support to SampleFormat. - * For now, we allocate temporary float buffers to convert the fixed - * point samples into something we can feed to the WaveTrack. Allocating - * big blocks of data like this isn't a great idea, but it's temporary. - */ - FloatBuffers channelBuffers{ channels, samples }; - - for (size_t smpl = 0; smpl < samples; smpl++) for(int chn = 0; chn < channels; chn++) - channelBuffers[chn][smpl] = scale(pcm->samples[chn][smpl]); + data->channels[chn]->Append((samplePtr)channelBuffers[chn].get(), + floatSample, + samples); - for (int chn = 0; chn < channels; chn++) - data->channels[chn]->Append((samplePtr)channelBuffers[chn].get(), - floatSample, - samples); - - return MAD_FLOW_CONTINUE; + return MAD_FLOW_CONTINUE; + }, MakeSimpleGuard(MAD_FLOW_BREAK) ); } enum mad_flow error_cb(void * WXUNUSED(_data), struct mad_stream * WXUNUSED(stream), From 01c5f25a19dd45efe5760481eddfd4306f24de67 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Thu, 16 Mar 2017 22:40:27 -0400 Subject: [PATCH 07/13] Rewrite one try-catch with GuardedCall so we can show the user errors --- src/ondemand/ODComputeSummaryTask.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ondemand/ODComputeSummaryTask.cpp b/src/ondemand/ODComputeSummaryTask.cpp index 324f30717..0035c0928 100644 --- a/src/ondemand/ODComputeSummaryTask.cpp +++ b/src/ondemand/ODComputeSummaryTask.cpp @@ -19,6 +19,7 @@ updating the ODPCMAliasBlockFile and the GUI of the newly available data. #include "ODComputeSummaryTask.h" +#include "../AudacityException.h" #include "../blockfile/ODPCMAliasBlockFile.h" #include "../Sequence.h" #include "../WaveTrack.h" @@ -77,9 +78,10 @@ void ODComputeSummaryTask::DoSomeInternal() { // WriteSummary might throw, but this is a worker thread, so stop // the exceptions here! - success = true; - try { bf->DoWriteSummary(); } - catch(...) { success = false; } + success = GuardedCall( [&] { + bf->DoWriteSummary(); + return true; + } ); blockStartSample = bf->GetStart(); blockEndSample = blockStartSample + bf->GetLength(); } From f5084935617c2609989449ad8a904748a627af70 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Tue, 20 Dec 2016 16:15:20 -0500 Subject: [PATCH 08/13] more guarded calls --- src/AudacityApp.cpp | 18 +++++++++++++++--- src/AudacityApp.h | 2 ++ src/Project.cpp | 4 +++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/AudacityApp.cpp b/src/AudacityApp.cpp index e6d21cc61..af8e265b2 100644 --- a/src/AudacityApp.cpp +++ b/src/AudacityApp.cpp @@ -849,6 +849,11 @@ bool AudacityApp::MRUOpen(const wxString &fullPathStr) { return(true); } +bool AudacityApp::SafeMRUOpen(const wxString &fullPathStr) +{ + return GuardedCall< bool >( [&]{ return MRUOpen( fullPathStr ); } ); +} + void AudacityApp::OnMRUClear(wxCommandEvent& WXUNUSED(event)) { mRecentFiles->Clear(); @@ -866,13 +871,16 @@ void AudacityApp::OnMRUFile(wxCommandEvent& event) { // because we don't want to RemoveFileFromHistory() just because it already exists, // and AudacityApp::OnMacOpenFile() calls MRUOpen() directly. // that method does not return the bad result. + // PRL: Don't call SafeMRUOpen + // -- if open fails for some exceptional reason of resource exhaustion that + // the user can correct, leave the file in history. if (!AudacityProject::IsAlreadyOpen(fullPathStr) && !MRUOpen(fullPathStr)) mRecentFiles->RemoveFileFromHistory(n); } void AudacityApp::OnTimer(wxTimerEvent& WXUNUSED(event)) { - // Filenames are queued when Audacity receives the a few of the + // Filenames are queued when Audacity receives a few of the // AppleEvent messages (via wxWidgets). So, open any that are // in the queue and clean the queue. if (gInited) { @@ -901,7 +909,9 @@ void AudacityApp::OnTimer(wxTimerEvent& WXUNUSED(event)) // LL: In all but one case an appropriate message is already displayed. The // instance that a message is NOT displayed is when a failure to write // to the config file has occurred. - if (!MRUOpen(name)) { + // PRL: Catch any exceptions, don't try this file again, continue to + // other files. + if (!SafeMRUOpen(name)) { wxFAIL_MSG(wxT("MRUOpen failed")); } } @@ -1615,7 +1625,9 @@ bool AudacityApp::OnInit() #if !defined(__WXMAC__) for (size_t i = 0, cnt = parser->GetParamCount(); i < cnt; i++) { - MRUOpen(parser->GetParam(i)); + // PRL: Catch any exceptions, don't try this file again, continue to + // other files. + SafeMRUOpen(parser->GetParam(i)); } #endif } diff --git a/src/AudacityApp.h b/src/AudacityApp.h index 0129d430c..edf1961f7 100644 --- a/src/AudacityApp.h +++ b/src/AudacityApp.h @@ -87,6 +87,8 @@ class AudacityApp final : public wxApp { void OnMRUFile(wxCommandEvent &event); // Backend for above - returns true for success, false for failure bool MRUOpen(const wxString &fileName); + // A wrapper of the above that does not throw + bool SafeMRUOpen(const wxString &fileName); void OnReceiveCommand(AppCommandEvent &event); diff --git a/src/Project.cpp b/src/Project.cpp index 65e4de40a..52b765742 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -2509,7 +2509,9 @@ void AudacityProject::OnCloseWindow(wxCloseEvent & event) wxYES_NO | wxCANCEL | wxICON_QUESTION, this); - if (result == wxCANCEL || (result == wxYES && !Save())) { + if (result == wxCANCEL || (result == wxYES && + !GuardedCall( [&]{ return Save(); } ) + )) { event.Veto(); return; } From d11027c2a7d659571755a5aa16f9e0a00371952f Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Mon, 26 Dec 2016 15:42:12 -0500 Subject: [PATCH 09/13] Allow exceptions from BlockFile::Recover, handle them in ProjectFSCK --- src/BlockFile.h | 1 + src/DirManager.cpp | 60 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/BlockFile.h b/src/BlockFile.h index edd479125..a45d0c4de 100644 --- a/src/BlockFile.h +++ b/src/BlockFile.h @@ -149,6 +149,7 @@ class PROFILE_DLL_API BlockFile /* not final, abstract */ { //summary only), write out a placeholder of silence data (missing //.au) or mark the blockfile to deal some other way without spewing //errors. + // May throw exceptions for i/o errors. virtual void Recover() = 0; /// if we've detected an on-disk problem, the user opted to //continue and the error persists, don't keep reporting it. The diff --git a/src/DirManager.cpp b/src/DirManager.cpp index 0a16c49ab..7a544ca3e 100644 --- a/src/DirManager.cpp +++ b/src/DirManager.cpp @@ -88,6 +88,7 @@ #endif #include "AudacityApp.h" +#include "AudacityException.h" #include "BlockFile.h" #include "blockfile/LegacyBlockFile.h" #include "blockfile/LegacyAliasBlockFile.h" @@ -1609,10 +1610,8 @@ _("Project check of \"%s\" folder \ wxASSERT(b); if (b) { auto ab = static_cast< AliasBlockFile * > ( &*b ); - if (action == 1) - // Silence error logging for this block in this session. - ab->SilenceAliasLog(); - else if (action == 2) + + if (action == 2) { // silence the blockfiles by yanking the filename // This is done, eventually, in PCMAliasBlockFile::ReadData() @@ -1621,9 +1620,22 @@ _("Project check of \"%s\" folder \ wxFileNameWrapper dummy; dummy.Clear(); ab->ChangeAliasedFileName(std::move(dummy)); - ab->Recover(); + + // If recovery fails for one file, silence it, + // and don't try to recover other files but + // silence them too. GuardedCall will cause an appropriate + // error message for the user. + GuardedCall( + [&] { ab->Recover(); }, + [&] (AudacityException*) { action = 1; } + ); + nResult = FSCKstatus_CHANGED | FSCKstatus_SAVE_AUP; } + + if (action == 1) + // Silence error logging for this block in this session. + ab->SilenceAliasLog(); } ++iter; } @@ -1673,11 +1685,22 @@ _("Project check of \"%s\" folder \ BlockFilePtr b = iter->second.lock(); wxASSERT(b); if (b) { - if(action==0){ + if(action==0) { //regenerate from data - b->Recover(); - nResult |= FSCKstatus_CHANGED; - }else if (action==1){ + // If recovery fails for one file, silence it, + // and don't try to recover other files but + // silence them too. GuardedCall will cause an appropriate + // error message for the user. + GuardedCall( + [&] { + b->Recover(); + nResult |= FSCKstatus_CHANGED; + }, + [&] (AudacityException*) { action = 1; } + ); + } + + if (action==1){ // Silence error logging for this block in this session. b->SilenceLog(); } @@ -1737,11 +1760,22 @@ _("Project check of \"%s\" folder \ if (b) { if (action == 2) { - //regenerate with zeroes - b->Recover(); - nResult = FSCKstatus_CHANGED; + //regenerate from data + // If recovery fails for one file, silence it, + // and don't try to recover other files but + // silence them too. GuardedCall will cause an appropriate + // error message for the user. + GuardedCall( + [&] { + //regenerate with zeroes + b->Recover(); + nResult |= FSCKstatus_CHANGED; + }, + [&] (AudacityException*) { action = 1; } + ); } - else if (action == 1) + + if (action == 1) b->SilenceLog(); } ++iter; From a8a2598ba3a299a70b93618f44d707c9c430ab9e Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Wed, 15 Mar 2017 11:33:04 -0400 Subject: [PATCH 10/13] Intercept exceptions in command script, report failure. --- src/commands/Command.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commands/Command.cpp b/src/commands/Command.cpp index 81a455b9b..1b08caa50 100644 --- a/src/commands/Command.cpp +++ b/src/commands/Command.cpp @@ -20,6 +20,7 @@ ApplyAndSendResponse, and CommandImplementation classes #include #include +#include "../AudacityException.h" #include "Validators.h" #include "CommandType.h" #include "CommandMisc.h" @@ -71,7 +72,9 @@ bool DecoratedCommand::SetParameter(const wxString ¶mName, bool ApplyAndSendResponse::Apply(CommandExecutionContext context) { - bool result = mCommand->Apply(context); + auto result = GuardedCall( + [&] { return mCommand->Apply(context); } + ); wxString response = GetName(); // These three strings are deliberately not localised. // They are used in script responses and always happen in English. From 8e5975b10db08a4d2f25c93f96d1179a23c119e0 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Thu, 22 Dec 2016 23:23:23 -0500 Subject: [PATCH 11/13] Report read exceptions met in SBSMS and Nyquist callbacks to user... ... without letting them propagate through the libraries. --- src/effects/SBSMSEffect.cpp | 68 +++++++++++++++++++++++++-------- src/effects/nyquist/Nyquist.cpp | 35 ++++++++++++++--- src/effects/nyquist/Nyquist.h | 3 ++ 3 files changed, 84 insertions(+), 22 deletions(-) diff --git a/src/effects/SBSMSEffect.cpp b/src/effects/SBSMSEffect.cpp index 48e60c48c..91070f553 100644 --- a/src/effects/SBSMSEffect.cpp +++ b/src/effects/SBSMSEffect.cpp @@ -22,6 +22,7 @@ effect that uses SBSMS to do its processing (TimeScale) #include "../WaveTrack.h" #include "../Project.h" #include "TimeWarper.h" +#include "../FileException.h" enum { SBSMSOutBlockSize = 512 @@ -62,6 +63,9 @@ public: std::unique_ptr quality; std::unique_ptr outputLeftTrack; std::unique_ptr outputRightTrack; + + wxFileName failedFileName; + bool error{ false }; }; class SBSMSEffectInterface final : public SBSMSInterfaceSliding { @@ -95,8 +99,28 @@ long resampleCB(void *cb_data, SBSMSFrame *data) ); // Get the samples from the tracks and put them in the buffers. - r->leftTrack->Get((samplePtr)(r->leftBuffer.get()), floatSample, r->offset, blockSize); - r->rightTrack->Get((samplePtr)(r->rightBuffer.get()), floatSample, r->offset, blockSize); + // I don't know if we can safely propagate errors through sbsms, and it + // does not seem to let us report error codes, so use this roundabout to + // stop the effect early. + // This would be easier with std::exception_ptr but we don't have that yet. + try { + r->leftTrack->Get( + (samplePtr)(r->leftBuffer.get()), floatSample, r->offset, blockSize); + r->rightTrack->Get( + (samplePtr)(r->rightBuffer.get()), floatSample, r->offset, blockSize); + } + catch ( const FileException& e ) { + if ( e.cause == FileException::Cause::Read ) + r->failedFileName = e.fileName; + data->size = 0; + r->error = true; + return 0; + } + catch ( ... ) { + data->size = 0; + r->error = true; + return 0; + } // convert to sbsms audio format for(decltype(blockSize) i=0; iGetKind() == Track::Label && (t->GetSelected() || (mustSync && t->IsSyncLockSelected())) ) { @@ -403,22 +427,34 @@ bool EffectSBSMS::Process() if (TrackProgress(nWhichTrack, frac)) return false; } - rb.outputLeftTrack->Flush(); - if(rightTrack) - rb.outputRightTrack->Flush(); + if (rb.failedFileName.IsOk()) + // re-construct an exception + // I wish I had std::exception_ptr instead + // and could re-throw any AudacityException + throw FileException{ + FileException::Cause::Read, rb.failedFileName }; + else if (rb.error) + // well, what? + bGoodResult = false; - bool bResult = + if (bGoodResult) { + rb.outputLeftTrack->Flush(); + if(rightTrack) + rb.outputRightTrack->Flush(); + + bool bResult = leftTrack->ClearAndPaste(mCurT0, mCurT1, rb.outputLeftTrack.get(), - true, false, warper.get()); - wxASSERT(bResult); // TO DO: Actually handle this. - wxUnusedVar(bResult); - - if(rightTrack) - { - bResult = - rightTrack->ClearAndPaste(mCurT0, mCurT1, rb.outputRightTrack.get(), - true, false, warper.get()); + true, false, warper.get()); wxASSERT(bResult); // TO DO: Actually handle this. + wxUnusedVar(bResult); + + if(rightTrack) + { + bResult = + rightTrack->ClearAndPaste(mCurT0, mCurT1, rb.outputRightTrack.get(), + true, false, warper.get()); + wxASSERT(bResult); // TO DO: Actually handle this. + } } } mCurTrackNum++; diff --git a/src/effects/nyquist/Nyquist.cpp b/src/effects/nyquist/Nyquist.cpp index bee5dde8c..a402dc8dc 100644 --- a/src/effects/nyquist/Nyquist.cpp +++ b/src/effects/nyquist/Nyquist.cpp @@ -45,7 +45,7 @@ effects from this one class. #include #include "../../AudacityApp.h" -#include "../../AudacityException.h" +#include "../../FileException.h" #include "../../FileNames.h" #include "../../Internat.h" #include "../../LabelTrack.h" @@ -834,6 +834,9 @@ bool NyquistEffect::TransferDataFromWindow() bool NyquistEffect::ProcessOne() { + mError = false; + mFailedFileName.Clear(); + nyx_rval rval; wxString cmd; @@ -1236,10 +1239,22 @@ bool NyquistEffect::ProcessOne() int success = nyx_get_audio(StaticPutCallback, (void *)this); + // See if GetCallback found read errors + if (mFailedFileName.IsOk()) + // re-construct an exception + // I wish I had std::exception_ptr instead + // and could re-throw any AudacityException + throw FileException{ + FileException::Cause::Read, mFailedFileName }; + else if (mError) + // what, then? + success = false; + if (!success) { for(i = 0; i < outChannels; i++) { mOutputTrack[i].reset(); } + return false; } @@ -1790,11 +1805,19 @@ int NyquistEffect::GetCallback(float *buffer, int ch, mCurStart[ch] + mCurLen - mCurBufferStart[ch] ); mCurBuffer[ch].Allocate(mCurBufferLen[ch], floatSample); - if (!mCurTrack[ch]->Get(mCurBuffer[ch].ptr(), floatSample, - mCurBufferStart[ch], mCurBufferLen[ch])) { - - wxPrintf(wxT("GET error\n")); - + try { + mCurTrack[ch]->Get( + mCurBuffer[ch].ptr(), floatSample, + mCurBufferStart[ch], mCurBufferLen[ch]); + } + catch ( const FileException& e ) { + if ( e.cause == FileException::Cause::Read ) + mFailedFileName = e.fileName; + mError = true; + return -1; + } + catch ( ... ) { + mError = true; return -1; } } diff --git a/src/effects/nyquist/Nyquist.h b/src/effects/nyquist/Nyquist.h index a26a3d038..7009d4ece 100644 --- a/src/effects/nyquist/Nyquist.h +++ b/src/effects/nyquist/Nyquist.h @@ -237,6 +237,9 @@ private: wxTextCtrl *mCommandText; wxCheckBox *mVersionCheckBox; + bool mError{ false }; + wxFileName mFailedFileName; + DECLARE_EVENT_TABLE() friend class NyquistEffectsModule; From 006aeda0a970306da53d8289391a443bf72f1bc1 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Fri, 18 Nov 2016 10:49:28 -0500 Subject: [PATCH 12/13] Use SafelyProcessEvent --- src/widgets/AButton.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/widgets/AButton.cpp b/src/widgets/AButton.cpp index f899294a9..da19a9657 100644 --- a/src/widgets/AButton.cpp +++ b/src/widgets/AButton.cpp @@ -563,7 +563,9 @@ void AButton::Click() { wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId()); event.SetEventObject(this); - GetEventHandler()->ProcessEvent(event); + // Be sure to use SafelyProcessEvent so that exceptions do not propagate + // out of DoDefaultAction + GetEventHandler()->SafelyProcessEvent(event); } void AButton::SetShift(bool shift) From e9a0876818f6a5574c275e4a82ddd8d2a86c58e5 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sun, 18 Dec 2016 15:57:03 -0500 Subject: [PATCH 13/13] TrackPanel aborts any dragging action when an exception escapes --- src/TrackPanel.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index c1488fd9e..118bbbbd9 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -6064,6 +6064,7 @@ void TrackPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event)) /// on our current state, we forward the mouse events to /// various interested parties. void TrackPanel::OnMouseEvent(wxMouseEvent & event) +try { #if defined(__WXMAC__) && defined(EVT_MAGNIFY) // PRL: @@ -6211,6 +6212,22 @@ void TrackPanel::OnMouseEvent(wxMouseEvent & event) EnsureVisible(t); } } +catch( ... ) +{ + // Abort any dragging, as if by hitting Esc + if ( HandleEscapeKey( true ) ) + ; + else { + // Ensure these steps, if escape handling did nothing + SetCapturedTrack(NULL, IsUncaptured); + if (HasCapture()) + ReleaseMouse(); + wxMouseEvent dummy; + HandleCursor(dummy); + Refresh(false); + } + throw; +} namespace { int FindMergeLine(WaveTrack *track, double time)