From 0aa6a5f0ad42debe76a90173de7d10f1ecda3ba0 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Thu, 7 Jan 2016 13:43:09 -0800 Subject: [PATCH] Catch panics in build logic Catch panics in the build logic in order to provide extra data on when the panic occurred, for example which module had GenerateBuildActions called on it. --- context.go | 220 ++++++++++++++++++++++++++++++++++++++--- module_ctx.go | 17 ++-- singleton_ctx.go | 10 +- splice_modules_test.go | 4 - 4 files changed, 223 insertions(+), 28 deletions(-) diff --git a/context.go b/context.go index 31541da..30dc451 100644 --- a/context.go +++ b/context.go @@ -157,6 +157,14 @@ type moduleInfo struct { actionDefs localBuildActions } +func (module *moduleInfo) String() string { + s := fmt.Sprintf("module %q", module.properties.Name) + if module.variantName != "" { + s += fmt.Sprintf(" variant %q", module.variantName) + } + return s +} + // A Variation is a way that a variant of a module differs from other variants of the same module. // For example, two variants of the same module might have Variation{"arch","arm"} and // Variation{"arch","arm64"} @@ -1122,13 +1130,20 @@ func blueprintDepsMutator(ctx BottomUpMutatorContext) { ctx.AddDependency(ctx.Module(), ctx.moduleInfo().properties.Deps...) if dynamicDepender, ok := ctx.Module().(DynamicDependerModule); ok { - dynamicDeps := dynamicDepender.DynamicDependencies(ctx) + func() { + defer func() { + if r := recover(); r != nil { + ctx.error(newPanicErrorf(r, "DynamicDependencies for %s", ctx.moduleInfo())) + } + }() + dynamicDeps := dynamicDepender.DynamicDependencies(ctx) - if ctx.Failed() { - return - } + if ctx.Failed() { + return + } - ctx.AddDependency(ctx.Module(), dynamicDeps...) + ctx.AddDependency(ctx.Module(), dynamicDeps...) + }() } } @@ -1516,7 +1531,20 @@ func (c *Context) runEarlyMutators(config interface{}) (errs []error) { }, name: mutator.name, } - mutator.mutator(mctx) + func() { + defer func() { + if r := recover(); r != nil { + in := fmt.Sprintf("early mutator %q for %s", mutator.name, module) + if err, ok := r.(panicError); ok { + err.addIn(in) + mctx.error(err) + } else { + mctx.error(newPanicErrorf(r, in)) + } + } + }() + mutator.mutator(mctx) + }() if len(mctx.errs) > 0 { errs = append(errs, mctx.errs...) return errs @@ -1576,8 +1604,21 @@ func (c *Context) runTopDownMutator(config interface{}, }, name: name, } + func() { + defer func() { + if r := recover(); r != nil { + in := fmt.Sprintf("top down mutator %q for %s", name, module) + if err, ok := r.(panicError); ok { + err.addIn(in) + mctx.error(err) + } else { + mctx.error(newPanicErrorf(r, in)) + } + } + }() + mutator(mctx) + }() - mutator(mctx) if len(mctx.errs) > 0 { errs = append(errs, mctx.errs...) return errs @@ -1609,7 +1650,20 @@ func (c *Context) runBottomUpMutator(config interface{}, reverseDeps: reverseDeps, } - mutator(mctx) + func() { + defer func() { + if r := recover(); r != nil { + in := fmt.Sprintf("bottom up mutator %q for %s", name, module) + if err, ok := r.(panicError); ok { + err.addIn(in) + mctx.error(err) + } else { + mctx.error(newPanicErrorf(r, in)) + } + } + }() + mutator(mctx) + }() if len(mctx.errs) > 0 { errs = append(errs, mctx.errs...) return errs @@ -1726,7 +1780,20 @@ func (c *Context) generateModuleBuildActions(config interface{}, handledMissingDeps: module.missingDeps == nil, } - mctx.module.logicModule.GenerateBuildActions(mctx) + func() { + defer func() { + if r := recover(); r != nil { + in := fmt.Sprintf("GenerateBuildActions for %s", module) + if err, ok := r.(panicError); ok { + err.addIn(in) + mctx.error(err) + } else { + mctx.error(newPanicErrorf(r, in)) + } + } + }() + mctx.module.logicModule.GenerateBuildActions(mctx) + }() if len(mctx.errs) > 0 { errsCh <- mctx.errs @@ -1781,7 +1848,20 @@ func (c *Context) generateSingletonBuildActions(config interface{}, scope: scope, } - info.singleton.GenerateBuildActions(sctx) + func() { + defer func() { + if r := recover(); r != nil { + in := fmt.Sprintf("GenerateBuildActions for singleton %s", info.name) + if err, ok := r.(panicError); ok { + err.addIn(in) + sctx.error(err) + } else { + sctx.error(newPanicErrorf(r, in)) + } + } + }() + info.singleton.GenerateBuildActions(sctx) + }() if len(sctx.errs) > 0 { errs = append(errs, sctx.errs...) @@ -1849,6 +1929,14 @@ func (c *Context) walkDeps(topModule *moduleInfo, visit func(Module, Module) bool) { visited := make(map[*moduleInfo]bool) + var visiting *moduleInfo + + defer func() { + if r := recover(); r != nil { + panic(newPanicErrorf(r, "WalkDeps(%s, %s) for dependency %s", + topModule, funcName(visit), visiting)) + } + }() var walk func(module *moduleInfo) walk = func(module *moduleInfo) { @@ -1856,6 +1944,7 @@ func (c *Context) walkDeps(topModule *moduleInfo, for _, moduleDep := range module.directDeps { if !visited[moduleDep] { + visiting = moduleDep if visit(moduleDep.logicModule, module.logicModule) { walk(moduleDep) } @@ -1866,8 +1955,18 @@ func (c *Context) walkDeps(topModule *moduleInfo, walk(topModule) } +type innerPanicError error + func (c *Context) visitDepsDepthFirst(topModule *moduleInfo, visit func(Module)) { visited := make(map[*moduleInfo]bool) + var visiting *moduleInfo + + defer func() { + if r := recover(); r != nil { + panic(newPanicErrorf(r, "VisitDepsDepthFirst(%s, %s) for dependency %s", + topModule, funcName(visit), visiting)) + } + }() var walk func(module *moduleInfo) walk = func(module *moduleInfo) { @@ -1879,6 +1978,7 @@ func (c *Context) visitDepsDepthFirst(topModule *moduleInfo, visit func(Module)) } if module != topModule { + visiting = module visit(module.logicModule) } } @@ -1890,6 +1990,14 @@ func (c *Context) visitDepsDepthFirstIf(topModule *moduleInfo, pred func(Module) visit func(Module)) { visited := make(map[*moduleInfo]bool) + var visiting *moduleInfo + + defer func() { + if r := recover(); r != nil { + panic(newPanicErrorf(r, "VisitDepsDepthFirstIf(%s, %s, %s) for dependency %s", + topModule, funcName(pred), funcName(visit), visiting)) + } + }() var walk func(module *moduleInfo) walk = func(module *moduleInfo) { @@ -1902,6 +2010,7 @@ func (c *Context) visitDepsDepthFirstIf(topModule *moduleInfo, pred func(Module) if module != topModule { if pred(module.logicModule) { + visiting = module visit(module.logicModule) } } @@ -1911,7 +2020,16 @@ func (c *Context) visitDepsDepthFirstIf(topModule *moduleInfo, pred func(Module) } func (c *Context) visitDirectDeps(module *moduleInfo, visit func(Module)) { - for _, dep := range module.directDeps { + var dep *moduleInfo + + defer func() { + if r := recover(); r != nil { + panic(newPanicErrorf(r, "VisitDirectDeps(%s, %s) for dependency %s", + module, funcName(visit), dep)) + } + }() + + for _, dep = range module.directDeps { visit(dep.logicModule) } } @@ -1919,7 +2037,16 @@ func (c *Context) visitDirectDeps(module *moduleInfo, visit func(Module)) { func (c *Context) visitDirectDepsIf(module *moduleInfo, pred func(Module) bool, visit func(Module)) { - for _, dep := range module.directDeps { + var dep *moduleInfo + + defer func() { + if r := recover(); r != nil { + panic(newPanicErrorf(r, "VisitDirectDepsIf(%s, %s, %s) for dependency %s", + module, funcName(pred), funcName(visit), dep)) + } + }() + + for _, dep = range module.directDeps { if pred(dep.logicModule) { visit(dep.logicModule) } @@ -1940,9 +2067,18 @@ func (c *Context) sortedModuleNames() []string { } func (c *Context) visitAllModules(visit func(Module)) { + var module *moduleInfo + + defer func() { + if r := recover(); r != nil { + panic(newPanicErrorf(r, "VisitAllModules(%s) for %s", + funcName(visit), module)) + } + }() + for _, moduleName := range c.sortedModuleNames() { group := c.moduleGroups[moduleName] - for _, module := range group.modules { + for _, module = range group.modules { visit(module.logicModule) } } @@ -1951,6 +2087,15 @@ func (c *Context) visitAllModules(visit func(Module)) { func (c *Context) visitAllModulesIf(pred func(Module) bool, visit func(Module)) { + var module *moduleInfo + + defer func() { + if r := recover(); r != nil { + panic(newPanicErrorf(r, "VisitAllModulesIf(%s, %s) for %s", + funcName(pred), funcName(visit), module)) + } + }() + for _, moduleName := range c.sortedModuleNames() { group := c.moduleGroups[moduleName] for _, module := range group.modules { @@ -1961,6 +2106,23 @@ func (c *Context) visitAllModulesIf(pred func(Module) bool, } } +func (c *Context) visitAllModuleVariants(module *moduleInfo, + visit func(Module)) { + + var variant *moduleInfo + + defer func() { + if r := recover(); r != nil { + panic(newPanicErrorf(r, "VisitAllModuleVariants(%s, %s) for %s", + module, funcName(visit), variant)) + } + }() + + for _, variant = range module.group.modules { + visit(variant.logicModule) + } +} + func (c *Context) requireNinjaVersion(major, minor, micro int) { if major != 1 { panic("ninja version with major version != 1 not supported") @@ -2236,9 +2398,7 @@ func (c *Context) FinalModule(module Module) Module { func (c *Context) VisitAllModuleVariants(module Module, visit func(Module)) { - for _, module := range c.moduleInfo[module].group.modules { - visit(module.logicModule) - } + c.visitAllModuleVariants(c.moduleInfo[module], visit) } // WriteBuildFile writes the Ninja manifeset text for the generated build @@ -2739,6 +2899,34 @@ func beforeInModuleList(a, b *moduleInfo, list []*moduleInfo) bool { panic(fmt.Errorf("element %v not found in list %v", missing, list)) } +type panicError struct { + panic interface{} + stack []byte + in string +} + +func newPanicErrorf(panic interface{}, in string, a ...interface{}) error { + buf := make([]byte, 4096) + count := runtime.Stack(buf, false) + return panicError{ + panic: panic, + in: fmt.Sprintf(in, a...), + stack: buf[:count], + } +} + +func (p panicError) Error() string { + return fmt.Sprintf("panic in %s\n%s\n%s\n", p.in, p.panic, p.stack) +} + +func (p *panicError) addIn(in string) { + p.in += " in " + in +} + +func funcName(f interface{}) string { + return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name() +} + var fileHeaderTemplate = `****************************************************************************** *** This file is generated and should not be edited *** ****************************************************************************** diff --git a/module_ctx.go b/module_ctx.go index 754c2d2..381b54a 100644 --- a/module_ctx.go +++ b/module_ctx.go @@ -119,6 +119,7 @@ type BaseModuleContext interface { Failed() bool moduleInfo() *moduleInfo + error(err error) } type DynamicDependerModuleContext BottomUpMutatorContext @@ -180,10 +181,16 @@ func (d *baseModuleContext) Config() interface{} { return d.config } +func (d *baseModuleContext) error(err error) { + if err != nil { + d.errs = append(d.errs, err) + } +} + func (d *baseModuleContext) Errorf(pos scanner.Position, format string, args ...interface{}) { - d.errs = append(d.errs, &Error{ + d.error(&Error{ Err: fmt.Errorf(format, args...), Pos: pos, }) @@ -192,7 +199,7 @@ func (d *baseModuleContext) Errorf(pos scanner.Position, func (d *baseModuleContext) ModuleErrorf(format string, args ...interface{}) { - d.errs = append(d.errs, &Error{ + d.error(&Error{ Err: fmt.Errorf(format, args...), Pos: d.module.pos, }) @@ -209,7 +216,7 @@ func (d *baseModuleContext) PropertyErrorf(property, format string, format = property + ": " + format - d.errs = append(d.errs, &Error{ + d.error(&Error{ Err: fmt.Errorf(format, args...), Pos: pos, }) @@ -320,9 +327,7 @@ func (m *moduleContext) FinalModule() Module { } func (m *moduleContext) VisitAllModuleVariants(visit func(Module)) { - for _, module := range m.module.group.modules { - visit(module.logicModule) - } + m.context.visitAllModuleVariants(m.module, visit) } func (m *moduleContext) GetMissingDependencies() []string { diff --git a/singleton_ctx.go b/singleton_ctx.go index 07e5108..a2d6109 100644 --- a/singleton_ctx.go +++ b/singleton_ctx.go @@ -91,15 +91,21 @@ func (s *singletonContext) BlueprintFile(logicModule Module) string { return s.context.BlueprintFile(logicModule) } +func (s *singletonContext) error(err error) { + if err != nil { + s.errs = append(s.errs, err) + } +} + func (s *singletonContext) ModuleErrorf(logicModule Module, format string, args ...interface{}) { - s.errs = append(s.errs, s.context.ModuleErrorf(logicModule, format, args...)) + s.error(s.context.ModuleErrorf(logicModule, format, args...)) } func (s *singletonContext) Errorf(format string, args ...interface{}) { // TODO: Make this not result in the error being printed as "internal error" - s.errs = append(s.errs, fmt.Errorf(format, args...)) + s.error(fmt.Errorf(format, args...)) } func (s *singletonContext) Failed() bool { diff --git a/splice_modules_test.go b/splice_modules_test.go index 091f5bf..cfe905a 100644 --- a/splice_modules_test.go +++ b/splice_modules_test.go @@ -28,10 +28,6 @@ var ( testModuleF = &moduleInfo{variantName: "testModuleF"} ) -func (m *moduleInfo) String() string { - return m.variantName -} - var spliceModulesTestCases = []struct { in []*moduleInfo replace *moduleInfo