Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix panic: runtime error: slice bounds out of range #128

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 19 additions & 22 deletions diffmatchpatch/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ const (
DiffInsert Operation = 1
// DiffEqual item represents an equal diff.
DiffEqual Operation = 0
//IndexSeparator is used to seperate the array indexes in an index string
IndexSeparator = ","
)

// Diff represents one diff operation
Expand Down Expand Up @@ -390,33 +388,32 @@ func (dmp *DiffMatchPatch) diffBisectSplit(runes1, runes2 []rune, x, y int,
}

// DiffLinesToChars splits two texts into a list of strings, and educes the texts to a string of hashes where each Unicode character represents one line.
// It's slightly faster to call DiffLinesToRunes first, followed by DiffMainRunes.
func (dmp *DiffMatchPatch) DiffLinesToChars(text1, text2 string) (string, string, []string) {
chars1, chars2, lineArray := dmp.diffLinesToStrings(text1, text2)
return chars1, chars2, lineArray
chars1, chars2, lineArray := dmp.diffLinesToIndexes(text1, text2)
return indexesToString(chars1), indexesToString(chars2), lineArray
}

// DiffLinesToRunes splits two texts into a list of runes.
func (dmp *DiffMatchPatch) DiffLinesToRunes(text1, text2 string) ([]rune, []rune, []string) {
chars1, chars2, lineArray := dmp.diffLinesToIndexes(text1, text2)
return []rune(indexesToString(chars1)), []rune(indexesToString(chars2)), lineArray
}

// diffLinesToIndexes splits two texts into a list of indexes
func (dmp *DiffMatchPatch) diffLinesToIndexes(text1, text2 string) ([]index, []index, []string) {
chars1, chars2, lineArray := dmp.diffLinesToStrings(text1, text2)
return []rune(chars1), []rune(chars2), lineArray
return chars1, chars2, lineArray
}

// DiffCharsToLines rehydrates the text in a diff from a string of line hashes to real lines of text.
func (dmp *DiffMatchPatch) DiffCharsToLines(diffs []Diff, lineArray []string) []Diff {
hydrated := make([]Diff, 0, len(diffs))
for _, aDiff := range diffs {
chars := strings.Split(aDiff.Text, IndexSeparator)
text := make([]string, len(chars))

for i, r := range chars {
i1, err := strconv.Atoi(r)
if err == nil {
text[i] = lineArray[i1]
}
var sb strings.Builder
for _, i := range stringToIndex(aDiff.Text) {
sb.WriteString(lineArray[i])
}

aDiff.Text = strings.Join(text, "")
aDiff.Text = sb.String()
hydrated = append(hydrated, aDiff)
}
return hydrated
Expand Down Expand Up @@ -1309,24 +1306,24 @@ func (dmp *DiffMatchPatch) DiffFromDelta(text1 string, delta string) (diffs []Di
}

// diffLinesToStrings splits two texts into a list of strings. Each string represents one line.
func (dmp *DiffMatchPatch) diffLinesToStrings(text1, text2 string) (string, string, []string) {
func (dmp *DiffMatchPatch) diffLinesToStrings(text1, text2 string) ([]index, []index, []string) {
// '\x00' is a valid character, but various debuggers don't like it. So we'll insert a junk entry to avoid generating a null character.
lineArray := []string{""} // e.g. lineArray[4] == 'Hello\n'

//Each string has the index of lineArray which it points to
strIndexArray1 := dmp.diffLinesToStringsMunge(text1, &lineArray)
strIndexArray2 := dmp.diffLinesToStringsMunge(text2, &lineArray)

return intArrayToString(strIndexArray1), intArrayToString(strIndexArray2), lineArray
return strIndexArray1, strIndexArray2, lineArray
}

// diffLinesToStringsMunge splits a text into an array of strings, and reduces the texts to a []string.
func (dmp *DiffMatchPatch) diffLinesToStringsMunge(text string, lineArray *[]string) []uint32 {
func (dmp *DiffMatchPatch) diffLinesToStringsMunge(text string, lineArray *[]string) []index {
// Walk the text, pulling out a substring for each line. text.split('\n') would would temporarily double our memory footprint. Modifying text would create many large strings to garbage collect.
lineHash := map[string]int{} // e.g. lineHash['Hello\n'] == 4
lineStart := 0
lineEnd := -1
strs := []uint32{}
strs := []index{}

for lineEnd < len(text)-1 {
lineEnd = indexOf(text, "\n", lineStart)
Expand All @@ -1340,11 +1337,11 @@ func (dmp *DiffMatchPatch) diffLinesToStringsMunge(text string, lineArray *[]str
lineValue, ok := lineHash[line]

if ok {
strs = append(strs, uint32(lineValue))
strs = append(strs, index(lineValue))
} else {
*lineArray = append(*lineArray, line)
lineHash[line] = len(*lineArray) - 1
strs = append(strs, uint32(len(*lineArray)-1))
strs = append(strs, index(len(*lineArray)-1))
}
}

Expand Down
24 changes: 12 additions & 12 deletions diffmatchpatch/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,10 @@ func TestDiffLinesToChars(t *testing.T) {
dmp := New()

for i, tc := range []TestCase{
{"", "alpha\r\nbeta\r\n\r\n\r\n", "", "1,2,3,3", []string{"", "alpha\r\n", "beta\r\n", "\r\n"}},
{"a", "b", "1", "2", []string{"", "a", "b"}},
{"", "alpha\r\nbeta\r\n\r\n\r\n", "", "\x01\x02\x03\x03", []string{"", "alpha\r\n", "beta\r\n", "\r\n"}},
{"a", "b", "\x01", "\x02", []string{"", "a", "b"}},
// Omit final newline.
{"alpha\nbeta\nalpha", "", "1,2,3", "", []string{"", "alpha\n", "beta\n", "alpha"}},
{"alpha\nbeta\nalpha", "", "\x01\x02\x03", "", []string{"", "alpha\n", "beta\n", "alpha"}},
} {
actualChars1, actualChars2, actualLines := dmp.DiffLinesToChars(tc.Text1, tc.Text2)
assert.Equal(t, tc.ExpectedChars1, actualChars1, fmt.Sprintf("Test case #%d, %#v", i, tc))
Expand All @@ -330,14 +330,14 @@ func TestDiffLinesToChars(t *testing.T) {
lineList := []string{
"", // Account for the initial empty element of the lines array.
}
var charList []string
var charList []index
for x := 1; x < n+1; x++ {
lineList = append(lineList, strconv.Itoa(x)+"\n")
charList = append(charList, strconv.Itoa(x))
charList = append(charList, index(x))
}
lines := strings.Join(lineList, "")
chars := strings.Join(charList[:], ",")
assert.Equal(t, n, len(strings.Split(chars, ",")))
chars := indexesToString(charList)
assert.Equal(t, n, len(charList))

actualChars1, actualChars2, actualLines := dmp.DiffLinesToChars(lines, "")
assert.Equal(t, chars, actualChars1)
Expand All @@ -358,8 +358,8 @@ func TestDiffCharsToLines(t *testing.T) {
for i, tc := range []TestCase{
{
Diffs: []Diff{
{DiffEqual, "1,2,1"},
{DiffInsert, "2,1,2"},
{DiffEqual, "\x01\x02\x01"},
{DiffInsert, "\x02\x01\x02"},
},
Lines: []string{"", "alpha\n", "beta\n"},

Expand All @@ -378,13 +378,13 @@ func TestDiffCharsToLines(t *testing.T) {
lineList := []string{
"", // Account for the initial empty element of the lines array.
}
charList := []string{}
charList := []index{}
for x := 1; x <= n; x++ {
lineList = append(lineList, strconv.Itoa(x)+"\n")
charList = append(charList, strconv.Itoa(x))
charList = append(charList, index(x))
}
assert.Equal(t, n, len(charList))
chars := strings.Join(charList[:], ",")
chars := indexesToString(charList)

actual := dmp.DiffCharsToLines([]Diff{Diff{DiffDelete, chars}}, lineList)
assert.Equal(t, []Diff{Diff{DiffDelete, strings.Join(lineList, "")}}, actual)
Expand Down
32 changes: 32 additions & 0 deletions diffmatchpatch/index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package diffmatchpatch

type index uint32

const runeSkipStart = 0xd800
const runeSkipEnd = 0xdfff + 1
const runeMax = 0x110000 // next invalid code point

func stringToIndex(text string) []index {
runes := []rune(text)
indexes := make([]index, len(runes))
for i, r := range runes {
if r < runeSkipEnd {
indexes[i] = index(r)
} else {
indexes[i] = index(r) - (runeSkipEnd - runeSkipStart)
}
}
return indexes
}

func indexesToString(indexes []index) string {
runes := make([]rune, len(indexes))
for i, index := range indexes {
if index < runeSkipStart {
runes[i] = rune(index)
} else {
runes[i] = rune(index + (runeSkipEnd - runeSkipStart))
}
}
return string(runes)
}
16 changes: 16 additions & 0 deletions diffmatchpatch/index_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package diffmatchpatch

import (
"github.com/stretchr/testify/assert"
"testing"
)

func TestIndexConversion(t *testing.T) {
n := runeMax - (runeSkipEnd - runeSkipStart)
indexes := make([]index, n)
for i := 0; i < n; i++ {
indexes[i] = index(i)
}
indexes2 := stringToIndex(indexesToString(indexes))
assert.EqualValues(t, indexes, indexes2)
}
25 changes: 25 additions & 0 deletions diffmatchpatch/patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,28 @@ func TestPatchApply(t *testing.T) {
assert.Equal(t, tc.ExpectedApplies, actualApplies, fmt.Sprintf("Test case #%d, %s", i, tc.Name))
}
}

func TestPatchMakeOutOfRangePanic(t *testing.T) {
text1 := `
1111111111111 000000
------------- ------
xxxxxxxxxxxxx ------
xxxxxxxxxxxxx ------
xxxxxxxxxxxxx xxxxxx
xxxxxxxxxxxxx ......
xxxxxxxxxxxxx 111111
xxxxxxxxxxxxx ??????
xxxxxxxxxxxxx 333333
xxxxxxxxxxxxx 555555
xxxxxxxxxx xxxxx
xxxxxxxxxx xxxxx
xxxxxxxxxx xxxxx
xxxxxxxxxx xxxxx
`
text2 := `
2222222222222 000000
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
dmp := New()
patches := dmp.PatchMake(text1, text2)
assert.Equal(t, 6, len(patches), "TestPatchMakeOutOfRangePanic")
}
18 changes: 0 additions & 18 deletions diffmatchpatch/stringutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
package diffmatchpatch

import (
"strconv"
"strings"
"unicode/utf8"
)
Expand Down Expand Up @@ -87,20 +86,3 @@ func runesIndex(r1, r2 []rune) int {
}
return -1
}

func intArrayToString(ns []uint32) string {
if len(ns) == 0 {
return ""
}

indexSeparator := IndexSeparator[0]

// Appr. 3 chars per num plus the comma.
b := []byte{}
for _, n := range ns {
b = strconv.AppendInt(b, int64(n), 10)
b = append(b, indexSeparator)
}
b = b[:len(b)-1]
return string(b)
}