github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/compiler/const_file.go (about)

     1  // Copyright 2020 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package compiler
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"regexp"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/google/syzkaller/pkg/ast"
    18  )
    19  
    20  // ConstFile serializes/deserializes .const files.
    21  type ConstFile struct {
    22  	arches map[string]bool
    23  	m      map[string]constVal
    24  }
    25  
    26  type constVal struct {
    27  	name string
    28  	vals map[string]uint64 // arch -> value
    29  }
    30  
    31  const undefined = "???"
    32  
    33  func NewConstFile() *ConstFile {
    34  	return &ConstFile{
    35  		arches: make(map[string]bool),
    36  		m:      make(map[string]constVal),
    37  	}
    38  }
    39  
    40  func (cf *ConstFile) AddArch(arch string, consts map[string]uint64, undeclared map[string]bool) error {
    41  	cf.arches[arch] = true
    42  	for name, val := range consts {
    43  		if err := cf.addConst(arch, name, val, true); err != nil {
    44  			return err
    45  		}
    46  	}
    47  	for name := range undeclared {
    48  		if err := cf.addConst(arch, name, 0, false); err != nil {
    49  			return err
    50  		}
    51  	}
    52  	return nil
    53  }
    54  
    55  func (cf *ConstFile) addConst(arch, name string, val uint64, declared bool) error {
    56  	cv := cf.m[name]
    57  	if cv.vals == nil {
    58  		cv.name = name
    59  		cv.vals = make(map[string]uint64)
    60  	}
    61  	if val0, declared0 := cv.vals[arch]; declared && declared0 && val != val0 {
    62  		return fmt.Errorf("const=%v arch=%v has different values: %v[%v] vs %v[%v]",
    63  			name, arch, val, declared, val0, declared0)
    64  	}
    65  	if declared {
    66  		cv.vals[arch] = val
    67  	}
    68  	cf.m[name] = cv
    69  	return nil
    70  }
    71  
    72  func (cf *ConstFile) Arch(arch string) map[string]uint64 {
    73  	if cf == nil {
    74  		return nil
    75  	}
    76  	m := make(map[string]uint64)
    77  	for name, cv := range cf.m {
    78  		if v, ok := cv.vals[arch]; ok {
    79  			m[name] = v
    80  		}
    81  	}
    82  	return m
    83  }
    84  
    85  func (cf *ConstFile) ExistsAny(constName string) bool {
    86  	return len(cf.m[constName].vals) > 0
    87  }
    88  
    89  func (cf *ConstFile) Serialize() []byte {
    90  	if len(cf.arches) == 0 {
    91  		return nil
    92  	}
    93  	var arches []string
    94  	for arch := range cf.arches {
    95  		arches = append(arches, arch)
    96  	}
    97  
    98  	sort.Strings(arches)
    99  	var consts []constVal
   100  	for _, cv := range cf.m {
   101  		consts = append(consts, cv)
   102  	}
   103  	sort.Slice(consts, func(i, j int) bool {
   104  		return consts[i].name < consts[j].name
   105  	})
   106  	buf := new(bytes.Buffer)
   107  	fmt.Fprintf(buf, "# Code generated by syz-sysgen. DO NOT EDIT.\n")
   108  	fmt.Fprintf(buf, "arches = %v\n", strings.Join(arches, ", "))
   109  	for _, cv := range consts {
   110  		fmt.Fprintf(buf, "%v = ", cv.name)
   111  		if len(cv.vals) == 0 {
   112  			// Undefined for all arches.
   113  			fmt.Fprintf(buf, "%v\n", undefined)
   114  			continue
   115  		}
   116  		count := make(map[uint64]int)
   117  		max, dflt := 0, uint64(0)
   118  		for _, val := range cv.vals {
   119  			count[val]++
   120  			if count[val] > 1 && (count[val] > max || count[val] == max && val < dflt) {
   121  				max, dflt = count[val], val
   122  			}
   123  		}
   124  		if max != 0 {
   125  			// Have a default value.
   126  			fmt.Fprintf(buf, "%v", dflt)
   127  		}
   128  		handled := make([]bool, len(arches))
   129  		for i, arch := range arches {
   130  			val, ok := cv.vals[arch]
   131  			if ok && (max != 0 && val == dflt) || handled[i] {
   132  				// Default value or serialized on a previous iteration.
   133  				continue
   134  			}
   135  			if i != 0 || max != 0 {
   136  				fmt.Fprintf(buf, ", ")
   137  			}
   138  			fmt.Fprintf(buf, "%v:", arch)
   139  			for j := i + 1; j < len(arches); j++ {
   140  				// Add more arches with the same value.
   141  				arch1 := arches[j]
   142  				val1, ok1 := cv.vals[arch1]
   143  				if ok1 == ok && val1 == val {
   144  					fmt.Fprintf(buf, "%v:", arch1)
   145  					handled[j] = true
   146  				}
   147  			}
   148  			if ok {
   149  				fmt.Fprintf(buf, "%v", val)
   150  			} else {
   151  				fmt.Fprint(buf, undefined)
   152  			}
   153  		}
   154  		fmt.Fprintf(buf, "\n")
   155  	}
   156  	return buf.Bytes()
   157  }
   158  
   159  func DeserializeConstFile(glob string, eh ast.ErrorHandler) *ConstFile {
   160  	if eh == nil {
   161  		eh = ast.LoggingHandler
   162  	}
   163  	files, err := filepath.Glob(glob)
   164  	if err != nil {
   165  		eh(ast.Pos{}, fmt.Sprintf("failed to find const files: %v", err))
   166  		return nil
   167  	}
   168  	if len(files) == 0 {
   169  		eh(ast.Pos{}, fmt.Sprintf("no const files matched by glob %q", glob))
   170  		return nil
   171  	}
   172  	cf := NewConstFile()
   173  	oldFormat := regexp.MustCompile(`_([a-z0-9]+)\.const$`)
   174  	for _, f := range files {
   175  		data, err := os.ReadFile(f)
   176  		if err != nil {
   177  			eh(ast.Pos{}, fmt.Sprintf("failed to read const file: %v", err))
   178  			return nil
   179  		}
   180  		// Support for old per-arch format.
   181  		// Remove it once we don't have any *_arch.const files anymore.
   182  		arch := ""
   183  		if match := oldFormat.FindStringSubmatch(f); match != nil {
   184  			arch = match[1]
   185  		}
   186  		if !cf.deserializeFile(data, filepath.Base(f), arch, eh) {
   187  			return nil
   188  		}
   189  	}
   190  	return cf
   191  }
   192  
   193  func (cf *ConstFile) deserializeFile(data []byte, file, arch string, eh ast.ErrorHandler) bool {
   194  	pos := ast.Pos{File: file, Line: 1}
   195  	errf := func(msg string, args ...interface{}) bool {
   196  		eh(pos, fmt.Sprintf(msg, args...))
   197  		return false
   198  	}
   199  	s := bufio.NewScanner(bytes.NewReader(data))
   200  	var arches []string
   201  	for ; s.Scan(); pos.Line++ {
   202  		line := s.Text()
   203  		if line == "" || line[0] == '#' {
   204  			continue
   205  		}
   206  		eq := strings.IndexByte(line, '=')
   207  		if eq == -1 {
   208  			return errf("expect '='")
   209  		}
   210  		name, val := strings.TrimSpace(line[:eq]), strings.TrimSpace(line[eq+1:])
   211  		if arch != "" {
   212  			// Old format.
   213  			if !cf.parseOldConst(arch, name, val, errf) {
   214  				return false
   215  			}
   216  			continue
   217  		}
   218  		if arch == "" && len(arches) == 0 {
   219  			if name != "arches" {
   220  				return errf("missing arches header")
   221  			}
   222  			for _, arch := range strings.Split(val, ",") {
   223  				arches = append(arches, strings.TrimSpace(arch))
   224  			}
   225  			continue
   226  		}
   227  		if !cf.parseConst(arches, name, val, errf) {
   228  			return false
   229  		}
   230  	}
   231  	if err := s.Err(); err != nil {
   232  		return errf("failed to parse: %v", err)
   233  	}
   234  	return true
   235  }
   236  
   237  type errft func(msg string, args ...interface{}) bool
   238  
   239  func (cf *ConstFile) parseConst(arches []string, name, line string, errf errft) bool {
   240  	var dflt map[string]uint64
   241  	for _, pair := range strings.Split(line, ",") {
   242  		fields := strings.Split(pair, ":")
   243  		if len(fields) == 1 {
   244  			// Default value.
   245  			if dflt != nil {
   246  				return errf("duplicate default value")
   247  			}
   248  			dflt = make(map[string]uint64)
   249  			valStr := strings.TrimSpace(fields[0])
   250  			if valStr == undefined {
   251  				continue
   252  			}
   253  			val, err := strconv.ParseUint(valStr, 0, 64)
   254  			if err != nil {
   255  				return errf("failed to parse int: %v", err)
   256  			}
   257  			for _, arch := range arches {
   258  				dflt[arch] = val
   259  			}
   260  			continue
   261  		}
   262  		if len(fields) < 2 {
   263  			return errf("bad value: %v", pair)
   264  		}
   265  		valStr := strings.TrimSpace(fields[len(fields)-1])
   266  		defined := valStr != undefined
   267  		var val uint64
   268  		if defined {
   269  			var err error
   270  			if val, err = strconv.ParseUint(valStr, 0, 64); err != nil {
   271  				return errf("failed to parse int: %v", err)
   272  			}
   273  		}
   274  		for _, arch := range fields[:len(fields)-1] {
   275  			arch = strings.TrimSpace(arch)
   276  			delete(dflt, arch)
   277  			if err := cf.addConst(arch, name, val, defined); err != nil {
   278  				return errf("%v", err)
   279  			}
   280  		}
   281  	}
   282  	for arch, val := range dflt {
   283  		if err := cf.addConst(arch, name, val, true); err != nil {
   284  			return errf("%v", err)
   285  		}
   286  	}
   287  	return true
   288  }
   289  
   290  func (cf *ConstFile) parseOldConst(arch, name, line string, errf errft) bool {
   291  	val, err := strconv.ParseUint(strings.TrimSpace(line), 0, 64)
   292  	if err != nil {
   293  		return errf("failed to parse int: %v", err)
   294  	}
   295  	if err := cf.addConst(arch, name, val, true); err != nil {
   296  		return errf("%v", err)
   297  	}
   298  	return true
   299  }