From 38d2856d341358bf9c691157ae0ca2e8960e35df Mon Sep 17 00:00:00 2001 From: Neil Date: Thu, 15 Feb 2024 14:17:08 +1100 Subject: Perform validation of undo state when SCI_SETUNDOCURRENT called, setting status when invalid. --- doc/ScintillaDoc.html | 14 ++++++++++--- src/CellBuffer.cxx | 4 ++-- src/CellBuffer.h | 2 +- src/Document.cxx | 2 +- src/Document.h | 2 +- src/UndoHistory.cxx | 54 +++++++++++++++++++++++++++++++++++++++++++++------ src/UndoHistory.h | 5 ++++- 7 files changed, 68 insertions(+), 15 deletions(-) diff --git a/doc/ScintillaDoc.html b/doc/ScintillaDoc.html index 12f2be0eb..4a10d280f 100644 --- a/doc/ScintillaDoc.html +++ b/doc/ScintillaDoc.html @@ -2063,9 +2063,9 @@ struct Sci_TextToFindFull {

The history should first be set up with SCI_PUSHUNDOACTIONTYPE, and - SCI_CHANGELASTUNDOACTIONTEXT then the save, current, and tentativ points set - with SCI_SETUNDOSAVEPOINT, SCI_SETUNDOCURRENT, and - SCI_SETUNDOTENTATIVE. + SCI_CHANGELASTUNDOACTIONTEXT then the save, current, and tentative points set + with SCI_SETUNDOSAVEPOINT, SCI_SETUNDOTENTATIVE, and + SCI_SETUNDOCURRENT.

SCI_PUSHUNDOACTIONTYPE(int type, position pos) appends an action to the undo stack @@ -2073,6 +2073,14 @@ struct Sci_TextToFindFull { SCI_CHANGELASTUNDOACTIONTEXT(position length, const char *text).

+

+ The last restoration API called should be SCI_SETUNDOCURRENT as this validates + the restored history and values against the document. For example, an undo history that could cause a negative + document length or inserting / removing text outside the document is invalid. + If the restored undo state is invalid then a failure status is set and the undo history cleared. + Check for failure with SCI_GETSTATUS. +

+

The current implementation may only work when the current and save point are the same and there is no tentative point.

diff --git a/src/CellBuffer.cxx b/src/CellBuffer.cxx index b3111914e..691a811dd 100644 --- a/src/CellBuffer.cxx +++ b/src/CellBuffer.cxx @@ -1160,8 +1160,8 @@ int CellBuffer::UndoSavePoint() const noexcept { return uh->SavePoint(); } -void CellBuffer::SetUndoCurrent(int action) noexcept { - uh->SetCurrent(action); +void CellBuffer::SetUndoCurrent(int action) { + uh->SetCurrent(action, Length()); } int CellBuffer::UndoCurrent() const noexcept { diff --git a/src/CellBuffer.h b/src/CellBuffer.h index 5422e92aa..916937845 100644 --- a/src/CellBuffer.h +++ b/src/CellBuffer.h @@ -183,7 +183,7 @@ public: int UndoActions() const noexcept; void SetUndoSavePoint(int action) noexcept; int UndoSavePoint() const noexcept; - void SetUndoCurrent(int action) noexcept; + void SetUndoCurrent(int action); int UndoCurrent() const noexcept; void SetUndoTentative(int action) noexcept; int UndoTentative() const noexcept; diff --git a/src/Document.cxx b/src/Document.cxx index 52081e1cd..8ab837daa 100644 --- a/src/Document.cxx +++ b/src/Document.cxx @@ -363,7 +363,7 @@ int Document::UndoSavePoint() const noexcept { return cb.UndoSavePoint(); } -void Document::SetUndoCurrent(int action) noexcept { +void Document::SetUndoCurrent(int action) { cb.SetUndoCurrent(action); } diff --git a/src/Document.h b/src/Document.h index 185066a6c..81cb299c0 100644 --- a/src/Document.h +++ b/src/Document.h @@ -409,7 +409,7 @@ public: int UndoActions() const noexcept; void SetUndoSavePoint(int action) noexcept; int UndoSavePoint() const noexcept; - void SetUndoCurrent(int action) noexcept; + void SetUndoCurrent(int action); int UndoCurrent() const noexcept; void SetUndoTentative(int action) noexcept; int UndoTentative() const noexcept; diff --git a/src/UndoHistory.cxx b/src/UndoHistory.cxx index 1a956b998..62f871a60 100644 --- a/src/UndoHistory.cxx +++ b/src/UndoHistory.cxx @@ -173,6 +173,14 @@ bool UndoActions::AtStart(size_t index) const noexcept { return !types[index-1].mayCoalesce; } +size_t UndoActions::LengthTo(size_t index) const noexcept { + size_t sum = 0; + for (size_t act = 0; act < index; act++) { + sum += lengths.ValueAt(act); + } + return sum; +} + void ScrapStack::Clear() noexcept { stack.clear(); current = 0; @@ -395,15 +403,49 @@ bool UndoHistory::AfterOrAtDetachPoint() const noexcept { return detach && (*detach <= currentAction); } -void UndoHistory::SetCurrent(int action) noexcept { - // Find position in scraps for action - memory = {}; - size_t position = 0; +intptr_t UndoHistory::Delta(int action) noexcept { + intptr_t sizeChange = 0; for (int act = 0; act < action; act++) { - position += actions.lengths.ValueAt(act); + const intptr_t lengthChange = actions.lengths.SignedValueAt(act); + sizeChange += (actions.types[act].at == ActionType::insert) ? lengthChange : -lengthChange; + } + return sizeChange; +} + +bool UndoHistory::Validate(intptr_t lengthDocument) noexcept { + // Check history for validity + const intptr_t sizeChange = Delta(currentAction); + if (sizeChange > lengthDocument) { + // Current document size too small for changes made in undo history. + return false; } - scraps->SetCurrent(position); + const intptr_t lengthOriginal = lengthDocument - sizeChange; + intptr_t lengthCurrent = lengthOriginal; + for (int act = 0; act < actions.SSize(); act++) { + const intptr_t lengthChange = actions.lengths.SignedValueAt(act); + if (actions.positions.SignedValueAt(act) > lengthCurrent) { + // Change outside document. + return false; + } + lengthCurrent += (actions.types[act].at == ActionType::insert) ? lengthChange : -lengthChange; + if (lengthCurrent < 0) { + return false; + } + } + return true; +} + +void UndoHistory::SetCurrent(int action, intptr_t lengthDocument) { + // Find position in scraps for action + memory = {}; + const size_t lengthSum = actions.LengthTo(action); + scraps->SetCurrent(lengthSum); currentAction = action; + if (!Validate(lengthDocument)) { + currentAction = 0; + DeleteUndoHistory(); + throw std::runtime_error("UndoHistory::SetCurrent: invalid undo history."); + } } int UndoHistory::Current() const noexcept { diff --git a/src/UndoHistory.h b/src/UndoHistory.h index 1186a1e0e..13aaca98c 100644 --- a/src/UndoHistory.h +++ b/src/UndoHistory.h @@ -53,6 +53,7 @@ struct UndoActions { [[nodiscard]] intptr_t SSize() const noexcept; void Create(size_t index, ActionType at_, Sci::Position position_, Sci::Position lenData_, bool mayCoalesce_); [[nodiscard]] bool AtStart(size_t index) const noexcept; + [[nodiscard]] size_t LengthTo(size_t index) const noexcept; }; class ScrapStack { @@ -112,7 +113,9 @@ public: bool AfterDetachPoint() const noexcept; bool AfterOrAtDetachPoint() const noexcept; - void SetCurrent(int action) noexcept; + [[nodiscard]] intptr_t Delta(int action) noexcept; + [[nodiscard]] bool Validate(intptr_t lengthDocument) noexcept; + void SetCurrent(int action, intptr_t lengthDocument); [[nodiscard]] int Current() const noexcept; [[nodiscard]] int Type(int action) const noexcept; [[nodiscard]] Sci::Position Position(int action) const noexcept; -- cgit v1.2.3