From be2bf82af96d463f1a06a3c86bc5b508e93b3505 Mon Sep 17 00:00:00 2001 From: yuin Date: Sat, 5 Mar 2022 17:37:40 +0900 Subject: [PATCH] Fix performance problems related link labels --- extra_test.go | 20 ++++++++++++++++++++ parser/link.go | 27 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/extra_test.go b/extra_test.go index 892848e..762bee8 100644 --- a/extra_test.go +++ b/extra_test.go @@ -2,7 +2,9 @@ package goldmark_test import ( "bytes" + "strings" "testing" + "time" . "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" @@ -85,3 +87,21 @@ func TestAutogeneratedIDs(t *testing.T) { t.Errorf("%s\n---------\n%s", source, b.String()) } } + +func TestDeepNestedLabelPerformance(t *testing.T) { + markdown := New(WithRendererOptions( + html.WithXHTML(), + html.WithUnsafe(), + )) + + started := time.Now().UnixMilli() + n := 50000 + source := []byte(strings.Repeat("[", n) + strings.Repeat("]", n)) + var b bytes.Buffer + _ = markdown.Convert(source, &b) + finished := time.Now().UnixMilli() + println(finished - started) + if (finished - started) > 3000 { + t.Error("Parsing deep nested labels took more 3 secs") + } +} diff --git a/parser/link.go b/parser/link.go index bd96dfb..99583ac 100644 --- a/parser/link.go +++ b/parser/link.go @@ -48,6 +48,13 @@ func (s *linkLabelState) Kind() ast.NodeKind { return kindLinkLabelState } +func linkLabelStateLength(v *linkLabelState) int { + if v == nil || v.Last == nil || v.First == nil { + return 0 + } + return v.Last.Segment.Stop - v.First.Segment.Start +} + func pushLinkLabelState(pc Context, v *linkLabelState) { tlist := pc.Get(linkLabelStateKey) var list *linkLabelState @@ -140,6 +147,13 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N } block.Advance(1) removeLinkLabelState(pc, last) + // CommonMark spec says: + // > A link label can have at most 999 characters inside the square brackets. + if linkLabelStateLength(tlist.(*linkLabelState)) > 998 { + ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment) + return nil + } + if !last.IsImage && s.containsLink(last) { // a link in a link text is not allowed ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment) return nil @@ -164,6 +178,13 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N block.SetPosition(l, pos) ssegment := text.NewSegment(last.Segment.Stop, segment.Start) maybeReference := block.Value(ssegment) + // CommonMark spec says: + // > A link label can have at most 999 characters inside the square brackets. + if len(maybeReference) > 999 { + ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment) + return nil + } + ref, ok := pc.Reference(util.ToLinkReference(maybeReference)) if !ok { ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment) @@ -251,6 +272,11 @@ func (s *linkParser) parseReferenceLink(parent ast.Node, last *linkLabelState, b s := text.NewSegment(last.Segment.Stop, orgpos.Start-1) maybeReference = block.Value(s) } + // CommonMark spec says: + // > A link label can have at most 999 characters inside the square brackets. + if len(maybeReference) > 999 { + return nil, true + } ref, ok := pc.Reference(util.ToLinkReference(maybeReference)) if !ok { @@ -369,6 +395,7 @@ func parseLinkTitle(block text.Reader) ([]byte, bool) { } func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) { + pc.Set(linkBottom, nil) tlist := pc.Get(linkLabelStateKey) if tlist == nil { return