aboutsummaryrefslogtreecommitdiff
path: root/internal/validate/checks.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/validate/checks.go')
-rw-r--r--internal/validate/checks.go67
1 files changed, 67 insertions, 0 deletions
diff --git a/internal/validate/checks.go b/internal/validate/checks.go
index 9d09eef..5eb4288 100644
--- a/internal/validate/checks.go
+++ b/internal/validate/checks.go
@@ -736,6 +736,7 @@ func validateDropTables(s Source) []Issue {
var issues []Issue
dropIDs := dropTableIDSet(s.DataDir)
itemIDs := s.Items.IDSet()
+ issues = append(issues, validateDropTableCycles(s, dropIDs)...)
for id := range dropIDs {
dt, err := behavior.LoadDropTable(s.DataDir, id)
@@ -838,6 +839,72 @@ func validateDropTables(s Source) []Issue {
return issues
}
+// validateDropTableCycles reports every table that participates in a
+// reference cycle. ResolveDrop recurses at runtime, so a self- or
+// mutual-reference would otherwise overflow the goroutine stack and kill the
+// process; a cycle is therefore a hard startup error.
+func validateDropTableCycles(s Source, dropIDs map[string]bool) []Issue {
+ edges := make(map[string][]string, len(dropIDs))
+ for id := range dropIDs {
+ dt, err := behavior.LoadDropTable(s.DataDir, id)
+ if err != nil {
+ continue // reported by the main pass
+ }
+ for _, d := range dt.Drops {
+ if d.Table != "" && dropIDs[d.Table] {
+ edges[id] = append(edges[id], d.Table)
+ }
+ }
+ }
+ const (
+ white = 0
+ gray = 1
+ black = 2
+ )
+ color := make(map[string]int, len(dropIDs))
+ reported := make(map[string]bool)
+ var stack []string
+ var issues []Issue
+ var visit func(id string)
+ visit = func(id string) {
+ color[id] = gray
+ stack = append(stack, id)
+ for _, next := range edges[id] {
+ switch color[next] {
+ case gray:
+ start := 0
+ for i, m := range stack {
+ if m == next {
+ start = i
+ break
+ }
+ }
+ for _, m := range stack[start:] {
+ if reported[m] {
+ continue
+ }
+ reported[m] = true
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "integrity",
+ Message: fmt.Sprintf("Drop table %q participates in a reference cycle; drop-table recursion would overflow the stack", m),
+ })
+ }
+ case white:
+ visit(next)
+ }
+ }
+ stack = stack[:len(stack)-1]
+ color[id] = black
+ }
+ for id := range dropIDs {
+ if color[id] == white {
+ visit(id)
+ }
+ }
+ return issues
+}
+
func validateCourses(s Source) []Issue {
var issues []Issue
roomIndex := s.World.RoomIndex()