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