aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/aflow/loop.go
blob: 18a7cafa8ac051efd9807da6cdd1a3c8ca788e90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// Copyright 2026 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package aflow

import (
	"fmt"
	"maps"
	"reflect"

	"github.com/google/syzkaller/pkg/aflow/trajectory"
)

// DoWhile represents "do { body } while (cond)" loop.
type DoWhile struct {
	// Dody of the loop.
	Do Action
	// Exit condition. It should be a string state variable.
	// The loop exists when the variable is empty.
	While string
	// Max interations for the loop.
	// Must be specified to avoid unintended effectively infinite loops.
	MaxIterations int

	loopVars map[string]reflect.Type
}

func (dw *DoWhile) execute(ctx *Context) error {
	span := &trajectory.Span{
		Type: trajectory.SpanLoop,
	}
	if err := ctx.startSpan(span); err != nil {
		return err
	}
	err := dw.loop(ctx)
	if err := ctx.finishSpan(span, err); err != nil {
		return err
	}
	return nil
}

func (dw *DoWhile) loop(ctx *Context) error {
	for name, typ := range dw.loopVars {
		if _, ok := ctx.state[name]; ok {
			return fmt.Errorf("loop var %q is already defined", name)
		}
		ctx.state[name] = reflect.Zero(typ).Interface()
	}
	for iter := 0; iter < dw.MaxIterations; iter++ {
		span := &trajectory.Span{
			Type: trajectory.SpanLoopIteration,
			Name: fmt.Sprint(iter),
		}
		if err := ctx.startSpan(span); err != nil {
			return err
		}
		err := dw.Do.execute(ctx)
		if err := ctx.finishSpan(span, err); err != nil {
			return err
		}
		if ctx.state[dw.While].(string) == "" {
			return nil
		}
	}
	return fmt.Errorf("DoWhile loop is going in cycles for %v iterations", dw.MaxIterations)
}

func (dw *DoWhile) verify(ctx *verifyContext) {
	if max := 1000; dw.MaxIterations <= 0 || dw.MaxIterations >= max {
		ctx.errorf("DoWhile", "bad MaxIterations value %v, should be within [1, %v]",
			dw.MaxIterations, max)
	}
	// Verification of loops is a bit tricky.
	// Normally we require each variable to be defined before use, but loops violate
	// the assumption. An action in a loop body may want to use a variable produced
	// by a subsequent action in the body on the previous iteration (otherwise there
	// is no way to provide feedback from one iteration to the next iteration).
	// But on the first iteration that variable is not defined yet. To resolve this,
	// we split verification into 2 parts: first, all body actions provide outputs,
	// and we collect all provided outputs in loopVars; second, we verify their inputs
	// (with all outputs from the whole body already defined). Later, during execution
	// we will define all loopVars to zero values before starting the loop body.
	inputs, outputs := ctx.inputs, ctx.outputs
	defer func() {
		ctx.inputs, ctx.outputs = inputs, outputs
	}()
	if outputs {
		ctx.inputs, ctx.outputs = false, true
		origState := maps.Clone(ctx.state)
		dw.Do.verify(ctx)
		dw.loopVars = make(map[string]reflect.Type)
		for name, desc := range ctx.state {
			if origState[name] == nil {
				dw.loopVars[name] = desc.typ
			}
		}
	}
	if inputs {
		ctx.inputs, ctx.outputs = true, false
		dw.Do.verify(ctx)
		ctx.requireNotEmpty("DoWhile", "While", dw.While)
		ctx.requireInput("DoWhile", dw.While, reflect.TypeFor[string]())
	}
}