syzkaller/sysparser/lexer.go
2017-01-18 20:08:41 +01:00

314 lines
7.2 KiB
Go

// Copyright 2015/2016 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 sysparser
import (
"fmt"
"io"
"os"
"sort"
"strconv"
"strings"
)
type Description struct {
Includes []string
Defines map[string]string
Syscalls []Syscall
Structs map[string]Struct
Unnamed map[string][]string
Flags map[string][]string
StrFlags map[string][]string
Resources map[string]Resource
}
type Syscall struct {
Name string
CallName string
Args [][]string
Ret []string
}
type Struct struct {
Name string
Flds [][]string
IsUnion bool
Packed bool
Varlen bool
Align int
}
type Resource struct {
Name string
Base string
Values []string
}
func Parse(in io.Reader) *Description {
p := newParser(in)
var includes []string
defines := make(map[string]string)
var syscalls []Syscall
structs := make(map[string]Struct)
unnamed := make(map[string][]string)
flags := make(map[string][]string)
strflags := make(map[string][]string)
resources := make(map[string]Resource)
var str *Struct
for p.Scan() {
if p.EOF() || p.Char() == '#' {
continue
}
if str != nil {
// Parsing a struct.
if p.Char() == '}' || p.Char() == ']' {
p.Parse(p.Char())
for _, attr := range parseType1(p, unnamed, flags, "")[1:] {
if str.IsUnion {
switch attr {
case "varlen":
str.Varlen = true
default:
failf("unknown union %v attribute: %v", str.Name, attr)
}
} else {
switch {
case attr == "packed":
str.Packed = true
case strings.HasPrefix(attr, "align_ptr"):
str.Align = 8 // TODO: this must be target pointer size
case strings.HasPrefix(attr, "align_"):
a, err := strconv.ParseUint(attr[6:], 10, 64)
if err != nil {
failf("bad struct %v alignment %v: %v", str.Name, attr, err)
}
if a&(a-1) != 0 || a == 0 || a > 1<<30 {
failf("bad struct %v alignment %v: must be sane power of 2", str.Name, a)
}
str.Align = int(a)
default:
failf("unknown struct %v attribute: %v", str.Name, attr)
}
}
}
if str.IsUnion {
if len(str.Flds) <= 1 {
failf("union %v has only %v fields, need at least 2", str.Name, len(str.Flds))
}
}
fields := make(map[string]bool)
for _, f := range str.Flds {
if f[0] == "parent" {
failf("struct/union %v contains reserved field 'parent'", str.Name)
}
if fields[f[0]] {
failf("duplicate field %v in struct/union %v", f[0], str.Name)
}
fields[f[0]] = true
}
structs[str.Name] = *str
str = nil
} else {
p.SkipWs()
fld := []string{p.Ident()}
fld = append(fld, parseType(p, unnamed, flags)...)
str.Flds = append(str.Flds, fld)
}
} else {
name := p.Ident()
if name == "include" {
p.Parse('<')
var include []byte
for {
ch := p.Char()
if ch == '>' {
break
}
p.Parse(ch)
include = append(include, ch)
}
p.Parse('>')
includes = append(includes, string(include))
} else if name == "define" {
key := p.Ident()
var val []byte
for !p.EOF() {
ch := p.Char()
p.Parse(ch)
val = append(val, ch)
}
if defines[key] != "" {
failf("%v define is defined multiple times", key)
}
defines[key] = fmt.Sprintf("(%s)", val)
} else if name == "resource" {
p.SkipWs()
id := p.Ident()
p.Parse('[')
base := p.Ident()
p.Parse(']')
var vals []string
if !p.EOF() && p.Char() == ':' {
p.Parse(':')
vals = append(vals, p.Ident())
for !p.EOF() {
p.Parse(',')
vals = append(vals, p.Ident())
}
}
if _, ok := resources[id]; ok {
failf("resource '%v' is defined multiple times", id)
}
if _, ok := structs[id]; ok {
failf("struct '%v' is redefined as resource", name)
}
resources[id] = Resource{id, base, vals}
} else {
switch ch := p.Char(); ch {
case '(':
// syscall
p.Parse('(')
var args [][]string
for p.Char() != ')' {
arg := []string{p.Ident()}
arg = append(arg, parseType(p, unnamed, flags)...)
args = append(args, arg)
if p.Char() != ')' {
p.Parse(',')
}
}
p.Parse(')')
var ret []string
if !p.EOF() {
ret = parseType(p, unnamed, flags)
}
callName := name
if idx := strings.IndexByte(callName, '$'); idx != -1 {
callName = callName[:idx]
}
fields := make(map[string]bool)
for _, a := range args {
if fields[a[0]] {
failf("duplicate arg %v in syscall %v", a[0], name)
}
fields[a[0]] = true
}
syscalls = append(syscalls, Syscall{name, callName, args, ret})
case '=':
// flag
p.Parse('=')
str := p.Char() == '"'
var vals []string
for {
v := p.Ident()
if str {
v = v[1 : len(v)-1]
}
vals = append(vals, v)
if p.EOF() {
break
}
p.Parse(',')
}
if str {
strflags[name] = vals
} else {
flags[name] = vals
}
case '{', '[':
p.Parse(ch)
if _, ok := structs[name]; ok {
failf("struct '%v' is defined multiple times", name)
}
if _, ok := resources[name]; ok {
failf("resource '%v' is redefined as struct", name)
}
str = &Struct{Name: name, IsUnion: ch == '['}
default:
failf("bad line (%v)", p.Str())
}
}
}
if !p.EOF() {
failf("trailing data (%v)", p.Str())
}
}
sort.Sort(syscallArray(syscalls))
return &Description{
Includes: includes,
Defines: defines,
Syscalls: syscalls,
Structs: structs,
Unnamed: unnamed,
Flags: flags,
StrFlags: strflags,
Resources: resources,
}
}
func isIdentifier(s string) bool {
for i, c := range s {
if c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || i > 0 && (c >= '0' && c <= '9') {
continue
}
return false
}
return true
}
func parseType(p *parser, unnamed map[string][]string, flags map[string][]string) []string {
return parseType1(p, unnamed, flags, p.Ident())
}
var (
unnamedSeq int
constSeq int
)
func parseType1(p *parser, unnamed map[string][]string, flags map[string][]string, name string) []string {
typ := []string{name}
if !p.EOF() && p.Char() == '[' {
p.Parse('[')
for {
id := p.Ident()
if p.Char() == '[' {
inner := parseType1(p, unnamed, flags, id)
id = fmt.Sprintf("unnamed%v", unnamedSeq)
unnamedSeq++
unnamed[id] = inner
}
typ = append(typ, id)
if p.Char() == ']' {
break
}
p.Parse(',')
}
p.Parse(']')
}
if name == "const" && len(typ) > 1 {
// Create a fake flag with the const value.
id := fmt.Sprintf("const_flag_%v", constSeq)
constSeq++
flags[id] = typ[1:2]
}
if name == "array" && len(typ) > 2 {
// Create a fake flag with the const value.
id := fmt.Sprintf("const_flag_%v", constSeq)
constSeq++
flags[id] = typ[2:3]
}
return typ
}
type syscallArray []Syscall
func (a syscallArray) Len() int { return len(a) }
func (a syscallArray) Less(i, j int) bool { return a[i].Name < a[j].Name }
func (a syscallArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func failf(msg string, args ...interface{}) {
fmt.Fprintf(os.Stderr, msg+"\n", args...)
os.Exit(1)
}