diff --git a/pkg/cli/sqlfmt.go b/pkg/cli/sqlfmt.go index bf8e7e7f14df..b5aa31425f81 100644 --- a/pkg/cli/sqlfmt.go +++ b/pkg/cli/sqlfmt.go @@ -76,7 +76,11 @@ func runSQLFmt(cmd *cobra.Command, args []string) error { } for i := range sl { - fmt.Print(cfg.Pretty(sl[i].AST)) + p, err := cfg.Pretty(sl[i].AST) + if err != nil { + return err + } + fmt.Print(p) if len(sl) > 1 { fmt.Print(";") } diff --git a/pkg/cmd/reduce/reduce/reducesql/reducesql.go b/pkg/cmd/reduce/reduce/reducesql/reducesql.go index 690a9d713b79..257eba640e9c 100644 --- a/pkg/cmd/reduce/reduce/reducesql/reducesql.go +++ b/pkg/cmd/reduce/reduce/reducesql/reducesql.go @@ -427,6 +427,7 @@ func collectASTs(stmts statements.Statements) []tree.NodeFormatter { func joinASTs(stmts []tree.NodeFormatter) string { var sb strings.Builder + var fmtCtx *tree.FmtCtx for i, stmt := range stmts { if i > 0 { sb.WriteString("\n\n") @@ -438,7 +439,16 @@ func joinASTs(stmts []tree.NodeFormatter) string { UseTabs: false, Simplify: true, } - sb.WriteString(cfg.Pretty(stmt)) + p, err := cfg.Pretty(stmt) + if err != nil { + // Use simple printing if pretty-printing fails. + if fmtCtx == nil { + fmtCtx = tree.NewFmtCtx(tree.FmtParsable) + } + stmt.Format(fmtCtx) + p = fmtCtx.CloseAndGetString() + } + sb.WriteString(p) sb.WriteString(";") } return sb.String() diff --git a/pkg/internal/sqlsmith/setup_test.go b/pkg/internal/sqlsmith/setup_test.go index a440af48df0f..47bc35562193 100644 --- a/pkg/internal/sqlsmith/setup_test.go +++ b/pkg/internal/sqlsmith/setup_test.go @@ -115,7 +115,10 @@ func TestGenerateParse(t *testing.T) { if err != nil { t.Fatalf("%v: %v", stmt, err) } - stmt = sqlsmith.TestingPrettyCfg.Pretty(parsed.AST) + stmt, err = sqlsmith.TestingPrettyCfg.Pretty(parsed.AST) + if err != nil { + t.Fatal(err) + } fmt.Print("STMT: ", i, "\n", stmt, ";\n\n") if *flagExec { db.Exec(t, `SET statement_timeout = '9s'`) diff --git a/pkg/internal/sqlsmith/sqlsmith.go b/pkg/internal/sqlsmith/sqlsmith.go index ec12fed83a92..3dbd2c55256f 100644 --- a/pkg/internal/sqlsmith/sqlsmith.go +++ b/pkg/internal/sqlsmith/sqlsmith.go @@ -190,7 +190,12 @@ func (s *Smither) Generate() string { continue } i = 0 - return prettyCfg.Pretty(stmt) + p, err := prettyCfg.Pretty(stmt) + if err != nil { + // Use simple printing if pretty-printing fails. + p = tree.AsStringWithFlags(stmt, tree.FmtParsable) + } + return p } } diff --git a/pkg/kv/kvpb/errors.go b/pkg/kv/kvpb/errors.go index ea5dbd197c16..a141eaca21aa 100644 --- a/pkg/kv/kvpb/errors.go +++ b/pkg/kv/kvpb/errors.go @@ -1630,8 +1630,7 @@ func (e *MissingRecordError) SafeFormatError(p errors.Printer) (next error) { // DescNotFoundError is reported when a descriptor is missing. type DescNotFoundError struct { - storeID roachpb.StoreID - nodeID roachpb.NodeID + id int32 isStore bool } @@ -1639,7 +1638,7 @@ type DescNotFoundError struct { // store descriptor. func NewStoreDescNotFoundError(storeID roachpb.StoreID) *DescNotFoundError { return &DescNotFoundError{ - storeID: storeID, + id: int32(storeID), isStore: true, } } @@ -1648,7 +1647,7 @@ func NewStoreDescNotFoundError(storeID roachpb.StoreID) *DescNotFoundError { // node descriptor. func NewNodeDescNotFoundError(nodeID roachpb.NodeID) *DescNotFoundError { return &DescNotFoundError{ - nodeID: nodeID, + id: int32(nodeID), isStore: false, } } @@ -1658,11 +1657,11 @@ func (e *DescNotFoundError) Error() string { } func (e *DescNotFoundError) SafeFormatError(p errors.Printer) (next error) { + s := redact.SafeString("node") if e.isStore { - p.Printf("store descriptor with store ID %d was not found", e.storeID) - } else { - p.Printf("node descriptor with node ID %d was not found", e.nodeID) + s = "store" } + p.Printf("%s descriptor with %s ID %d was not found", s, s, e.id) return nil } diff --git a/pkg/sql/BUILD.bazel b/pkg/sql/BUILD.bazel index 70e91c05f24e..b5194b30431a 100644 --- a/pkg/sql/BUILD.bazel +++ b/pkg/sql/BUILD.bazel @@ -558,6 +558,7 @@ go_library( "//pkg/util/metric", "//pkg/util/mon", "//pkg/util/optional", + "//pkg/util/pretty", "//pkg/util/protoutil", "//pkg/util/quotapool", "//pkg/util/randutil", diff --git a/pkg/sql/explain_bundle.go b/pkg/sql/explain_bundle.go index 301413552a7e..2626ee6d024f 100644 --- a/pkg/sql/explain_bundle.go +++ b/pkg/sql/explain_bundle.go @@ -34,6 +34,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/duration" "github.com/cockroachdb/cockroach/pkg/util/log" "github.com/cockroachdb/cockroach/pkg/util/memzipper" + "github.com/cockroachdb/cockroach/pkg/util/pretty" "github.com/cockroachdb/cockroach/pkg/util/tracing" "github.com/cockroachdb/cockroach/pkg/util/tracing/tracingpb" "github.com/cockroachdb/errors" @@ -144,11 +145,14 @@ func buildStatementBundle( queryErr, payloadErr, commErr error, sv *settings.Values, c inFlightTraceCollector, -) diagnosticsBundle { +) (diagnosticsBundle, error) { if plan == nil { - return diagnosticsBundle{collectionErr: errors.AssertionFailedf("execution terminated early")} + return diagnosticsBundle{collectionErr: errors.AssertionFailedf("execution terminated early")}, nil + } + b, err := makeStmtBundleBuilder(explainFlags, db, ie, stmtRawSQL, plan, trace, placeholders, sv) + if err != nil { + return diagnosticsBundle{}, err } - b := makeStmtBundleBuilder(explainFlags, db, ie, stmtRawSQL, plan, trace, placeholders, sv) b.addStatement() b.addOptPlans(ctx) @@ -162,9 +166,9 @@ func buildStatementBundle( buf, err := b.finalize() if err != nil { - return diagnosticsBundle{collectionErr: err, errorStrings: b.errorStrings} + return diagnosticsBundle{collectionErr: err, errorStrings: b.errorStrings}, nil } - return diagnosticsBundle{zip: buf.Bytes(), errorStrings: b.errorStrings} + return diagnosticsBundle{zip: buf.Bytes(), errorStrings: b.errorStrings}, nil } // insert the bundle in statement diagnostics. Sets bundle.diagID and (in error @@ -226,18 +230,21 @@ func makeStmtBundleBuilder( trace tracingpb.Recording, placeholders *tree.PlaceholderInfo, sv *settings.Values, -) stmtBundleBuilder { +) (stmtBundleBuilder, error) { b := stmtBundleBuilder{ flags: flags, db: db, ie: ie, plan: plan, trace: trace, placeholders: placeholders, sv: sv, } - b.buildPrettyStatement(stmtRawSQL) + err := b.buildPrettyStatement(stmtRawSQL) + if err != nil { + return stmtBundleBuilder{}, err + } b.z.Init() - return b + return b, nil } // buildPrettyStatement saves the pretty-printed statement (without any // placeholder arguments). -func (b *stmtBundleBuilder) buildPrettyStatement(stmtRawSQL string) { +func (b *stmtBundleBuilder) buildPrettyStatement(stmtRawSQL string) error { // If we hit an early error, stmt or stmt.AST might not be initialized yet. In // this case use the original raw SQL. if b.plan.stmt == nil || b.plan.stmt.AST == nil { @@ -255,7 +262,19 @@ func (b *stmtBundleBuilder) buildPrettyStatement(stmtRawSQL string) { cfg.Align = tree.PrettyNoAlign cfg.JSONFmt = true cfg.ValueRedaction = b.flags.RedactValues - b.stmt = cfg.Pretty(b.plan.stmt.AST) + var err error + b.stmt, err = cfg.Pretty(b.plan.stmt.AST) + if errors.Is(err, pretty.ErrPrettyMaxRecursionDepthExceeded) { + // Use the raw statement string if pretty-printing fails. + b.stmt = stmtRawSQL + // If we're collecting a redacted bundle, redact the raw SQL + // completely. + if b.flags.RedactValues && b.stmt != "" { + b.stmt = string(redact.RedactedMarker()) + } + } else if err != nil { + return err + } // If we had ValueRedaction set, Pretty surrounded all constants with // redaction markers. We must call Redact to fully redact them. @@ -266,6 +285,7 @@ func (b *stmtBundleBuilder) buildPrettyStatement(stmtRawSQL string) { if b.stmt == "" { b.stmt = "-- no statement" } + return nil } // addStatement adds the pretty-printed statement in b.stmt as file diff --git a/pkg/sql/instrumentation.go b/pkg/sql/instrumentation.go index 3bf1e5b28e4c..8b836cd72712 100644 --- a/pkg/sql/instrumentation.go +++ b/pkg/sql/instrumentation.go @@ -661,11 +661,15 @@ func (ih *instrumentationHelper) Finish( // collection was enabled _after_ the optimizer was done. planString = "-- plan elided due to gist matching" } - bundle = buildStatementBundle( + var err error + bundle, err = buildStatementBundle( bundleCtx, ih.explainFlags, cfg.DB, ie.(*InternalExecutor), stmtRawSQL, &p.curPlan, planString, trace, placeholders, res.Err(), payloadErr, retErr, &p.extendedEvalCtx.Settings.SV, ih.inFlightTraceCollector, ) + if err != nil { + return err + } // Include all non-critical errors as warnings. Note that these // error strings might contain PII, but the warnings are only shown // to the current user and aren't included into the bundle. diff --git a/pkg/sql/logictest/logic.go b/pkg/sql/logictest/logic.go index 77d841476dab..60a8e869e55c 100644 --- a/pkg/sql/logictest/logic.go +++ b/pkg/sql/logictest/logic.go @@ -728,7 +728,11 @@ func (ls *logicStatement) readSQL( if i > 0 { fmt.Fprintln(&newSyntax, ";") } - fmt.Fprint(&newSyntax, pcfg.Pretty(stmtList[i].AST)) + p, err := pcfg.Pretty(stmtList[i].AST) + if err != nil { + return "", errors.Wrapf(err, "error while pretty printing") + } + fmt.Fprint(&newSyntax, p) } return newSyntax.String(), nil }(ls.sql) diff --git a/pkg/sql/opt/optgen/cmd/optfmt/main.go b/pkg/sql/opt/optgen/cmd/optfmt/main.go index ad4bfbcdf7ea..bd52d2b7a7c6 100644 --- a/pkg/sql/opt/optgen/cmd/optfmt/main.go +++ b/pkg/sql/opt/optgen/cmd/optfmt/main.go @@ -173,7 +173,10 @@ func prettyify(r io.Reader, n int, exprgen bool) (string, error) { exprs = parser.Exprs() } d := p.toDoc(exprs) - s := pretty.Pretty(d, n, false, 4, nil) + s, err := pretty.Pretty(d, n, false, 4, nil) + if err != nil { + return "", err + } // Remove any whitespace at EOL. This can happen in define rules where // we always insert a blank line above comments which are nested with diff --git a/pkg/sql/sem/builtins/BUILD.bazel b/pkg/sql/sem/builtins/BUILD.bazel index ffe2655a7721..cc28e74db577 100644 --- a/pkg/sql/sem/builtins/BUILD.bazel +++ b/pkg/sql/sem/builtins/BUILD.bazel @@ -116,6 +116,7 @@ go_library( "//pkg/util/json", "//pkg/util/log", "//pkg/util/mon", + "//pkg/util/pretty", "//pkg/util/protoutil", "//pkg/util/randident", "//pkg/util/randident/randidentcfg", diff --git a/pkg/sql/sem/builtins/builtins.go b/pkg/sql/sem/builtins/builtins.go index 2a2bfead9b51..e82e42ff6eba 100644 --- a/pkg/sql/sem/builtins/builtins.go +++ b/pkg/sql/sem/builtins/builtins.go @@ -86,6 +86,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/ipaddr" "github.com/cockroachdb/cockroach/pkg/util/json" "github.com/cockroachdb/cockroach/pkg/util/log" + "github.com/cockroachdb/cockroach/pkg/util/pretty" "github.com/cockroachdb/cockroach/pkg/util/protoutil" "github.com/cockroachdb/cockroach/pkg/util/syncutil" "github.com/cockroachdb/cockroach/pkg/util/timeofday" @@ -11157,7 +11158,15 @@ func prettyStatement(p tree.PrettyCfg, stmt string) (string, error) { } var formattedStmt strings.Builder for idx := range stmts { - formattedStmt.WriteString(p.Pretty(stmts[idx].AST)) + p, err := p.Pretty(stmts[idx].AST) + if errors.Is(err, pretty.ErrPrettyMaxRecursionDepthExceeded) { + // If pretty-printing the statement fails, use the original + // statement. + p = stmt + } else if err != nil { + return "", err + } + formattedStmt.WriteString(p) if len(stmts) > 1 { formattedStmt.WriteString(";") } diff --git a/pkg/sql/sem/tree/pretty.go b/pkg/sql/sem/tree/pretty.go index 21e210d22e19..7d1586b8c1c7 100644 --- a/pkg/sql/sem/tree/pretty.go +++ b/pkg/sql/sem/tree/pretty.go @@ -157,13 +157,13 @@ func (p *PrettyCfg) bracketKeyword( } // Pretty pretty prints stmt with default options. -func Pretty(stmt NodeFormatter) string { +func Pretty(stmt NodeFormatter) (string, error) { cfg := DefaultPrettyCfg() return cfg.Pretty(stmt) } // Pretty pretty prints stmt with specified options. -func (p *PrettyCfg) Pretty(stmt NodeFormatter) string { +func (p *PrettyCfg) Pretty(stmt NodeFormatter) (string, error) { doc := p.Doc(stmt) return pretty.Pretty(doc, p.LineWidth, p.UseTabs, p.TabWidth, p.Case) } diff --git a/pkg/sql/sem/tree/pretty_test.go b/pkg/sql/sem/tree/pretty_test.go index e51c353d7c1f..8ac3deb4a807 100644 --- a/pkg/sql/sem/tree/pretty_test.go +++ b/pkg/sql/sem/tree/pretty_test.go @@ -18,6 +18,7 @@ import ( "os" "path/filepath" "runtime" + "strconv" "strings" "testing" @@ -30,6 +31,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/leaktest" "github.com/cockroachdb/cockroach/pkg/util/log" "github.com/cockroachdb/cockroach/pkg/util/pretty" + "github.com/stretchr/testify/assert" "golang.org/x/sync/errgroup" ) @@ -112,7 +114,10 @@ func runTestPrettyData( for p := range work { thisCfg := cfg thisCfg.LineWidth = p.numCols - res[p.idx] = thisCfg.Pretty(stmt.AST) + res[p.idx], err = thisCfg.Pretty(stmt.AST) + if err != nil { + t.Fatal(err) + } } return nil } @@ -178,7 +183,10 @@ func TestPrettyVerify(t *testing.T) { if err != nil { t.Fatal(err) } - got := tree.Pretty(stmt.AST) + got, err := tree.Pretty(stmt.AST) + if err != nil { + t.Fatal(err) + } if pretty != got { t.Fatalf("got: %s\nexpected: %s", got, pretty) } @@ -186,6 +194,32 @@ func TestPrettyVerify(t *testing.T) { } } +func TestPrettyBigStatement(t *testing.T) { + defer leaktest.AfterTest(t)() + defer log.Scope(t).Close(t) + + // Create a SELECT statement with a 1 million item IN expression. Without + // mitigation, this can cause stack overflows - see #91197. + var sb strings.Builder + sb.WriteString("SELECT * FROM foo WHERE id IN (") + for i := 0; i < 1_000_000; i++ { + if i != 0 { + sb.WriteByte(',') + } + sb.WriteString(strconv.Itoa(i)) + } + sb.WriteString(");") + + stmt, err := parser.ParseOne(sb.String()) + if err != nil { + t.Fatal(err) + } + + cfg := tree.DefaultPrettyCfg() + _, err = cfg.Pretty(stmt.AST) + assert.Errorf(t, err, "max call stack depth of be exceeded") +} + func BenchmarkPrettyData(b *testing.B) { matches, err := filepath.Glob(datapathutils.TestDataPath(b, "pretty", "*.sql")) if err != nil { @@ -226,7 +260,10 @@ func TestPrettyExprs(t *testing.T) { } for expr, pretty := range tests { - got := tree.Pretty(expr) + got, err := tree.Pretty(expr) + if err != nil { + t.Fatal(err) + } if pretty != got { t.Fatalf("got: %s\nexpected: %s", got, pretty) } diff --git a/pkg/sql/show_create_clauses.go b/pkg/sql/show_create_clauses.go index 9b742de89780..2a10b6ba697e 100644 --- a/pkg/sql/show_create_clauses.go +++ b/pkg/sql/show_create_clauses.go @@ -32,6 +32,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/sessiondata" "github.com/cockroachdb/cockroach/pkg/sql/types" "github.com/cockroachdb/cockroach/pkg/util/log" + "github.com/cockroachdb/cockroach/pkg/util/pretty" "github.com/cockroachdb/errors" ) @@ -141,7 +142,10 @@ func ShowCreateView( cfg.UseTabs = true cfg.LineWidth = 100 - cfg.TabWidth cfg.ValueRedaction = redactableValues - q := formatViewQueryForDisplay(ctx, semaCtx, sessionData, desc, cfg) + q, err := formatViewQueryForDisplay(ctx, semaCtx, sessionData, desc, cfg) + if err != nil { + return "", err + } for i, line := range strings.Split(q, "\n") { if i > 0 { f.WriteString("\n\t") @@ -161,7 +165,7 @@ func formatViewQueryForDisplay( sessionData *sessiondata.SessionData, desc catalog.TableDescriptor, cfg tree.PrettyCfg, -) (query string) { +) (query string, err error) { defer func() { parsed, err := parser.ParseOne(query) if err != nil { @@ -169,14 +173,23 @@ func formatViewQueryForDisplay( desc.GetName(), desc.GetID(), err) return } - query = cfg.Pretty(parsed.AST) + var prettyErr error + query, prettyErr = cfg.Pretty(parsed.AST) + if errors.Is(prettyErr, pretty.ErrPrettyMaxRecursionDepthExceeded) { + // Use simple printing if pretty-printing fails. + query = tree.AsStringWithFlags(parsed.AST, tree.FmtParsable) + return + } else if prettyErr != nil { + err = prettyErr + return + } }() typeReplacedViewQuery, err := formatViewQueryTypesForDisplay(ctx, semaCtx, sessionData, desc) if err != nil { log.Warningf(ctx, "error deserializing user defined types for view %s (%v): %+v", desc.GetName(), desc.GetID(), err) - return desc.GetViewQuery() + return desc.GetViewQuery(), nil } // Convert sequences referenced by ID in the view back to their names. @@ -184,10 +197,10 @@ func formatViewQueryForDisplay( if err != nil { log.Warningf(ctx, "error converting sequence IDs to names for view %s (%v): %+v", desc.GetName(), desc.GetID(), err) - return typeReplacedViewQuery + return typeReplacedViewQuery, nil } - return sequenceReplacedViewQuery + return sequenceReplacedViewQuery, nil } // formatQuerySequencesForDisplay walks the view query and diff --git a/pkg/testutils/sqlutils/pretty.go b/pkg/testutils/sqlutils/pretty.go index 38351fa3330a..c6e075c795f5 100644 --- a/pkg/testutils/sqlutils/pretty.go +++ b/pkg/testutils/sqlutils/pretty.go @@ -66,7 +66,10 @@ func VerifyStatementPrettyRoundtrip(t *testing.T, sql string) { // origStmt := stmts[i].AST // Be careful to not simplify otherwise the tests won't round trip. - prettyStmt := cfg.Pretty(origStmt) + prettyStmt, err := cfg.Pretty(origStmt) + if err != nil { + t.Fatalf("%s: %s", err, prettyStmt) + } parsedPretty, err := parser.ParseOne(prettyStmt) if err != nil { t.Fatalf("%s: %s", err, prettyStmt) diff --git a/pkg/util/pretty/BUILD.bazel b/pkg/util/pretty/BUILD.bazel index a37f969901e1..07b8b6b6160a 100644 --- a/pkg/util/pretty/BUILD.bazel +++ b/pkg/util/pretty/BUILD.bazel @@ -9,6 +9,10 @@ go_library( ], importpath = "github.com/cockroachdb/cockroach/pkg/util/pretty", visibility = ["//visibility:public"], + deps = [ + "//pkg/util/errorutil", + "@com_github_cockroachdb_errors//:errors", + ], ) go_test( diff --git a/pkg/util/pretty/pretty.go b/pkg/util/pretty/pretty.go index 210223e3592b..f02ef461319e 100644 --- a/pkg/util/pretty/pretty.go +++ b/pkg/util/pretty/pretty.go @@ -13,6 +13,9 @@ package pretty import ( "fmt" "strings" + + "github.com/cockroachdb/cockroach/pkg/util/errorutil" + "github.com/cockroachdb/errors" ) // See the referenced paper in the package documentation for explanations @@ -45,7 +48,25 @@ const ( // if not nil. keywordTransform must not change the visible length of its // argument. It can, for example, add invisible characters like control codes // (colors, etc.). -func Pretty(d Doc, n int, useTabs bool, tabWidth int, keywordTransform func(string) string) string { +func Pretty( + d Doc, n int, useTabs bool, tabWidth int, keywordTransform func(string) string, +) (_ string, err error) { + defer func() { + if r := recover(); r != nil { + // This code allows us to propagate internal errors without having + // to add error checks everywhere throughout the code. This is only + // possible because the code does not update shared state and does + // not manipulate locks. + if ok, e := errorutil.ShouldCatch(r); ok { + err = e + } else { + // Other panic objects can't be considered "safe" and thus are + // propagated as panics. + panic(r) + } + } + }() + var sb strings.Builder b := beExec{ w: int16(n), @@ -56,7 +77,7 @@ func Pretty(d Doc, n int, useTabs bool, tabWidth int, keywordTransform func(stri } ldoc := b.best(d) b.layout(&sb, useTabs, ldoc) - return sb.String() + return sb.String(), nil } // w is the max line width. @@ -103,9 +124,27 @@ type beExec struct { // keywordTransform filters keywords if not nil. keywordTransform func(string) string + + // beDepth is the depth of recursive calls of be. It is used to detect deep + // call stacks before a stack overflow occurs. + beDepth int } +// maxBeDepth is the maximum allowed recursive call depth of be. If the depth +// exceeds this value, be will panic. +const maxBeDepth = 100_000 + +// ErrPrettyMaxRecursionDepthExceeded is returned from Pretty when the maximum +// recursion depth of function invoked by Pretty is exceeded. +var ErrPrettyMaxRecursionDepthExceeded = errors.AssertionFailedf("max recursion depth exceeded") + func (b *beExec) be(k docPos, xlist *iDoc) *docBest { + b.beDepth++ + defer func() { b.beDepth-- }() + if b.beDepth > maxBeDepth { + panic(ErrPrettyMaxRecursionDepthExceeded) + } + // Shortcut: be k [] = Nil if xlist == nil { return nil diff --git a/pkg/util/pretty/pretty_test.go b/pkg/util/pretty/pretty_test.go index a21ad1b6b13f..729aff29c5a0 100644 --- a/pkg/util/pretty/pretty_test.go +++ b/pkg/util/pretty/pretty_test.go @@ -60,7 +60,10 @@ func Example_align() { for _, n := range []int{1, 15, 30, 80} { fmt.Printf("%d:\n", n) for _, doc := range testData { - p := pretty.Pretty(doc, n, true /*useTabs*/, 4 /*tabWidth*/, nil /*keywordTransform*/) + p, err := pretty.Pretty(doc, n, true /*useTabs*/, 4 /*tabWidth*/, nil /*keywordTransform*/) + if err != nil { + panic(err) + } fmt.Printf("%s\n\n", p) } } @@ -216,7 +219,10 @@ func Example_tree() { )) } for _, n := range []int{1, 30, 80} { - p := pretty.Pretty(showTree(tree), n, false /*useTabs*/, 4 /*tabWidth*/, nil /*keywordTransform*/) + p, err := pretty.Pretty(showTree(tree), n, false /*useTabs*/, 4 /*tabWidth*/, nil /*keywordTransform*/) + if err != nil { + panic(err) + } fmt.Printf("%d:\n%s\n\n", n, p) } // Output: