// Copyright 2017 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 compiler
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/google/syzkaller/pkg/ast"
"github.com/google/syzkaller/prog"
"github.com/google/syzkaller/sys/targets"
)
type ConstInfo struct {
Consts []string
Includes []string
Incdirs []string
Defines map[string]string
}
func ExtractConsts(desc *ast.Description, target *targets.Target, eh ast.ErrorHandler) map[string]*ConstInfo {
res := Compile(desc, nil, target, eh)
if res == nil {
return nil
}
return res.fileConsts
}
// extractConsts returns list of literal constants and other info required for const value extraction.
func (comp *compiler) extractConsts() map[string]*ConstInfo {
infos := make(map[string]*constInfo)
for _, decl := range comp.desc.Nodes {
pos, _, _ := decl.Info()
info := getConstInfo(infos, pos)
switch n := decl.(type) {
case *ast.Include:
info.includeArray = append(info.includeArray, n.File.Value)
case *ast.Incdir:
info.incdirArray = append(info.incdirArray, n.Dir.Value)
case *ast.Define:
v := fmt.Sprint(n.Value.Value)
switch {
case n.Value.CExpr != "":
v = n.Value.CExpr
case n.Value.Ident != "":
v = n.Value.Ident
}
name := n.Name.Name
info.defines[name] = v
info.consts[name] = true
case *ast.Call:
if comp.target.SyscallNumbers && !strings.HasPrefix(n.CallName, "syz_") {
info.consts[comp.target.SyscallPrefix+n.CallName] = true
}
}
}
for _, decl := range comp.desc.Nodes {
switch decl.(type) {
case *ast.Call, *ast.Struct, *ast.Resource, *ast.TypeDef:
comp.foreachType(decl, func(t *ast.Type, desc *typeDesc,
args []*ast.Type, _ prog.IntTypeCommon) {
for i, arg := range args {
if desc.Args[i].Type.Kind == kindInt {
if arg.Ident != "" {
info := getConstInfo(infos, arg.Pos)
info.consts[arg.Ident] = true
}
if arg.Ident2 != "" {
info := getConstInfo(infos, arg.Pos2)
info.consts[arg.Ident2] = true
}
}
}
})
}
}
for _, decl := range comp.desc.Nodes {
switch n := decl.(type) {
case *ast.Struct:
for _, attr := range n.Attrs {
if attr.Ident == "size" {
info := getConstInfo(infos, attr.Pos)
info.consts[attr.Args[0].Ident] = true
}
}
}
}
comp.desc.Walk(ast.Recursive(func(n0 ast.Node) {
if n, ok := n0.(*ast.Int); ok {
info := getConstInfo(infos, n.Pos)
info.consts[n.Ident] = true
}
}))
return convertConstInfo(infos)
}
type constInfo struct {
consts map[string]bool
defines map[string]string
includeArray []string
incdirArray []string
}
func getConstInfo(infos map[string]*constInfo, pos ast.Pos) *constInfo {
info := infos[pos.File]
if info == nil {
info = &constInfo{
consts: make(map[string]bool),
defines: make(map[string]string),
}
infos[pos.File] = info
}
return info
}
func convertConstInfo(infos map[string]*constInfo) map[string]*ConstInfo {
res := make(map[string]*ConstInfo)
for file, info := range infos {
res[file] = &ConstInfo{
Consts: toArray(info.consts),
Includes: info.includeArray,
Incdirs: info.incdirArray,
Defines: info.defines,
}
}
return res
}
// assignSyscallNumbers assigns syscall numbers, discards unsupported syscalls.
func (comp *compiler) assignSyscallNumbers(consts map[string]uint64) {
for _, decl := range comp.desc.Nodes {
c, ok := decl.(*ast.Call)
if !ok || strings.HasPrefix(c.CallName, "syz_") {
continue
}
str := comp.target.SyscallPrefix + c.CallName
nr, ok := consts[str]
if ok {
c.NR = nr
continue
}
c.NR = ^uint64(0) // mark as unused to not generate it
name := "syscall " + c.CallName
if !comp.unsupported[name] {
comp.unsupported[name] = true
comp.warning(c.Pos, "unsupported syscall: %v due to missing const %v",
c.CallName, str)
}
}
}
// patchConsts replaces all symbolic consts with their numeric values taken from consts map.
// Updates desc and returns set of unsupported syscalls and flags.
func (comp *compiler) patchConsts(consts map[string]uint64) {
for _, decl := range comp.desc.Nodes {
switch decl.(type) {
case *ast.IntFlags:
// Unsupported flag values are dropped.
n := decl.(*ast.IntFlags)
var values []*ast.Int
for _, v := range n.Values {
if comp.patchIntConst(&v.Value, &v.Ident, consts, nil) {
values = append(values, v)
}
}
n.Values = values
case *ast.Resource, *ast.Struct, *ast.Call, *ast.TypeDef:
// Walk whole tree and replace consts in Type's and Int's.
missing := ""
comp.foreachType(decl, func(_ *ast.Type, desc *typeDesc,
args []*ast.Type, _ prog.IntTypeCommon) {
for i, arg := range args {
if desc.Args[i].Type.Kind == kindInt {
comp.patchIntConst(&arg.Value, &arg.Ident, consts, &missing)
if arg.HasColon {
comp.patchIntConst(&arg.Value2,
&arg.Ident2, consts, &missing)
}
}
}
})
if n, ok := decl.(*ast.Resource); ok {
for _, v := range n.Values {
comp.patchIntConst(&v.Value, &v.Ident, consts, &missing)
}
}
if n, ok := decl.(*ast.Struct); ok {
for _, attr := range n.Attrs {
if attr.Ident == "size" {
sz := attr.Args[0]
comp.patchIntConst(&sz.Value, &sz.Ident, consts, &missing)
}
}
}
if missing == "" {
continue
}
// Produce a warning about unsupported syscall/resource/struct.
// TODO(dvyukov): we should transitively remove everything that
// depends on unsupported things.
// Potentially we still can get, say, a bad int range error
// due to the 0 const value.
pos, typ, name := decl.Info()
if id := typ + " " + name; !comp.unsupported[id] {
comp.unsupported[id] = true
comp.warning(pos, "unsupported %v: %v due to missing const %v",
typ, name, missing)
}
if c, ok := decl.(*ast.Call); ok {
c.NR = ^uint64(0) // mark as unused to not generate it
}
}
}
}
func (comp *compiler) patchIntConst(val *uint64, id *string, consts map[string]uint64, missing *string) bool {
if *id == "" {
return true
}
v, ok := consts[*id]
if !ok {
if missing != nil && *missing == "" {
*missing = *id
}
}
*val = v
return ok
}
func SerializeConsts(consts map[string]uint64, undeclared map[string]bool) []byte {
type nameValuePair struct {
declared bool
name string
val uint64
}
var nv []nameValuePair
for k, v := range consts {
nv = append(nv, nameValuePair{true, k, v})
}
for k := range undeclared {
nv = append(nv, nameValuePair{false, k, 0})
}
sort.Slice(nv, func(i, j int) bool {
return nv[i].name < nv[j].name
})
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "# AUTOGENERATED FILE\n")
for _, x := range nv {
if x.declared {
fmt.Fprintf(buf, "%v = %v\n", x.name, x.val)
} else {
fmt.Fprintf(buf, "# %v is not set\n", x.name)
}
}
return buf.Bytes()
}
func DeserializeConsts(data []byte, file string, eh ast.ErrorHandler) map[string]uint64 {
consts := make(map[string]uint64)
pos := ast.Pos{
File: file,
Line: 1,
}
ok := true
s := bufio.NewScanner(bytes.NewReader(data))
for ; s.Scan(); pos.Line++ {
line := s.Text()
if line == "" || line[0] == '#' {
continue
}
eq := strings.IndexByte(line, '=')
if eq == -1 {
eh(pos, "expect '='")
ok = false
continue
}
name := strings.TrimSpace(line[:eq])
val, err := strconv.ParseUint(strings.TrimSpace(line[eq+1:]), 0, 64)
if err != nil {
eh(pos, fmt.Sprintf("failed to parse int: %v", err))
ok = false
continue
}
if _, dup := consts[name]; dup {
eh(pos, fmt.Sprintf("duplicate const %q", name))
ok = false
continue
}
consts[name] = val
}
if err := s.Err(); err != nil {
eh(pos, fmt.Sprintf("failed to parse: %v", err))
ok = false
}
if !ok {
return nil
}
return consts
}
func DeserializeConstsGlob(glob string, eh ast.ErrorHandler) map[string]uint64 {
if eh == nil {
eh = ast.LoggingHandler
}
files, err := filepath.Glob(glob)
if err != nil {
eh(ast.Pos{}, fmt.Sprintf("failed to find const files: %v", err))
return nil
}
if len(files) == 0 {
eh(ast.Pos{}, fmt.Sprintf("no const files matched by glob %q", glob))
return nil
}
consts := make(map[string]uint64)
for _, f := range files {
data, err := ioutil.ReadFile(f)
if err != nil {
eh(ast.Pos{}, fmt.Sprintf("failed to read const file: %v", err))
return nil
}
consts1 := DeserializeConsts(data, filepath.Base(f), eh)
if consts1 == nil {
consts = nil
}
if consts != nil {
for n, v := range consts1 {
if old, ok := consts[n]; ok && old != v {
eh(ast.Pos{}, fmt.Sprintf(
"different values for const %q: %v vs %v", n, v, old))
return nil
}
consts[n] = v
}
}
}
return consts
}