pkg/ast: support char constants

Frequently it's useful to do something like:

int8['a':'z']
punctuation = ',', '-', ':'
This commit is contained in:
Dmitry Vyukov 2018-04-29 10:56:48 +02:00
parent c7f6891ca7
commit 1e85f7b9af
10 changed files with 80 additions and 22 deletions

View File

@ -247,6 +247,21 @@ The `proc[20000, 4, int16be]` type means that we want to generate an `int16be`
integer starting from `20000` and assign `4` values for each process.
As a result the executor number `n` will get values in the `[20000 + n * 4, 20000 + (n + 1) * 4)` range.
## Integer Constants
Integer constants can be specified as decimal literals, as `0x`-prefixed
hex literals, as `'`-surrounded char literals, or as symbolic constants
extracted from kernel headers or defined by `define` directives. For example:
```
foo(a const[10])
foo(a const[0xabcd])
foo(a int8['a':'z'])
foo(a const[PATH_MAX])
foo(a ptr[in, array[int8, MY_PATH_MAX]])
define MY_PATH_MAX PATH_MAX + 2
```
## Misc
Description files also contain `include` directives that refer to Linux kernel header files,

View File

@ -169,11 +169,19 @@ func (n *String) Info() (Pos, string, string) {
return n.Pos, tok2str[tokString], ""
}
type intFmt int
const (
intFmtDec intFmt = iota
intFmtHex
intFmtChar
)
type Int struct {
Pos Pos
// Only one of Value, Ident, CExpr is filled.
Value uint64
ValueHex bool // says if value was in hex (for formatting)
valueFmt intFmt
Ident string
CExpr string
}
@ -186,7 +194,7 @@ type Type struct {
Pos Pos
// Only one of Value, Ident, String is filled.
Value uint64
ValueHex bool
valueFmt intFmt
Ident string
String string
HasString bool
@ -194,7 +202,7 @@ type Type struct {
HasColon bool
Pos2 Pos
Value2 uint64
Value2Hex bool
value2Fmt intFmt
Ident2 string
Args []*Type
}

View File

@ -165,7 +165,7 @@ func (n *Int) Clone() Node {
return &Int{
Pos: n.Pos,
Value: n.Value,
ValueHex: n.ValueHex,
valueFmt: n.valueFmt,
Ident: n.Ident,
CExpr: n.CExpr,
}
@ -179,14 +179,14 @@ func (n *Type) Clone() Node {
return &Type{
Pos: n.Pos,
Value: n.Value,
ValueHex: n.ValueHex,
valueFmt: n.valueFmt,
Ident: n.Ident,
String: n.String,
HasString: n.HasString,
HasColon: n.HasColon,
Pos2: n.Pos2,
Value2: n.Value2,
Value2Hex: n.Value2Hex,
value2Fmt: n.value2Fmt,
Ident2: n.Ident2,
Args: args,
}

View File

@ -159,14 +159,14 @@ func fmtType(t *Type) string {
case t.HasString:
v = fmt.Sprintf("\"%v\"", t.String)
default:
v = fmtIntValue(t.Value, t.ValueHex)
v = fmtIntValue(t.Value, t.valueFmt)
}
if t.HasColon {
switch {
case t.Ident2 != "":
v += fmt.Sprintf(":%v", t.Ident2)
default:
v += fmt.Sprintf(":%v", fmtIntValue(t.Value2, t.Value2Hex))
v += fmt.Sprintf(":%v", fmtIntValue(t.Value2, t.value2Fmt))
}
}
v += fmtTypeList(t.Args)
@ -206,15 +206,21 @@ func fmtInt(i *Int) string {
case i.CExpr != "":
return fmt.Sprintf("%v", i.CExpr)
default:
return fmtIntValue(i.Value, i.ValueHex)
return fmtIntValue(i.Value, i.valueFmt)
}
}
func fmtIntValue(v uint64, hex bool) string {
if hex {
func fmtIntValue(v uint64, format intFmt) string {
switch format {
case intFmtDec:
return fmt.Sprint(v)
case intFmtHex:
return fmt.Sprintf("0x%x", v)
case intFmtChar:
return fmt.Sprintf("'%c'", v)
default:
panic(fmt.Sprintf("unknown int format %v", format))
}
return fmt.Sprint(v)
}
func comma(i int, or string) string {

View File

@ -413,7 +413,7 @@ func (p *parser) parseType() *Type {
switch p.tok {
case tokInt:
allowColon = true
arg.Value, arg.ValueHex = p.parseIntValue()
arg.Value, arg.valueFmt = p.parseIntValue()
case tokIdent:
allowColon = true
arg.Ident = p.lit
@ -429,7 +429,7 @@ func (p *parser) parseType() *Type {
arg.Pos2 = p.pos
switch p.tok {
case tokInt:
arg.Value2, arg.Value2Hex = p.parseIntValue()
arg.Value2, arg.value2Fmt = p.parseIntValue()
case tokIdent:
arg.Ident2 = p.lit
default:
@ -479,7 +479,7 @@ func (p *parser) parseInt() *Int {
}
switch p.tok {
case tokInt:
i.Value, i.ValueHex = p.parseIntValue()
i.Value, i.valueFmt = p.parseIntValue()
case tokIdent:
i.Ident = p.lit
default:
@ -489,13 +489,16 @@ func (p *parser) parseInt() *Int {
return i
}
func (p *parser) parseIntValue() (uint64, bool) {
func (p *parser) parseIntValue() (uint64, intFmt) {
if p.lit[0] == '\'' {
return uint64(p.lit[1]), intFmtChar
}
if v, err := strconv.ParseUint(p.lit, 10, 64); err == nil {
return v, false
return v, intFmtDec
}
if len(p.lit) > 2 && p.lit[0] == '0' && p.lit[1] == 'x' {
if v, err := strconv.ParseUint(p.lit[2:], 16, 64); err == nil {
return v, true
return v, intFmtHex
}
}
panic(fmt.Sprintf("scanner returned bad integer %q", p.lit))

View File

@ -200,6 +200,17 @@ func (s *scanner) Scan() (tok token, lit string, pos Pos) {
s.Error(pos, fmt.Sprintf("bad integer %q", lit))
lit = "0"
}
case s.ch == '\'':
tok = tokInt
lit = "0"
s.next()
s.next()
if s.ch != '\'' {
s.Error(pos, "char literal is not terminated")
return
}
s.next()
lit = string(s.data[pos.Off : pos.Off+3])
case s.ch == '_' || s.ch >= 'a' && s.ch <= 'z' || s.ch >= 'A' && s.ch <= 'Z':
tok = tokIdent
for s.ch == '_' || s.ch == '$' ||

View File

@ -9,6 +9,9 @@ int_flags0 = 0, 0x1, 0xab
int_flags1 = 123ab0x ### bad integer "123ab0x"
int_flags1 == 0, 1 ### unexpected '=', expecting int, identifier, string
int_flags = 0, "foo" ### unexpected string, expecting int, identifier
int_flags2 = ' ### char literal is not terminated
int_flags3 = 'a ### char literal is not terminated
int_flags3 = 'a, 1 ### char literal is not terminated
str_flags0 = "foo", "bar"
str_flags1 = "non terminated ### string literal is not terminated

View File

@ -5,6 +5,7 @@ package compiler
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"path/filepath"
@ -15,6 +16,8 @@ import (
"github.com/google/syzkaller/sys/targets"
)
var flagUpdate = flag.Bool("update", false, "reformat all.txt")
func TestCompileAll(t *testing.T) {
for os, arches := range targets.List {
os, arches := os, arches
@ -63,7 +66,8 @@ func TestNoErrors(t *testing.T) {
eh := func(pos ast.Pos, msg string) {
t.Logf("%v: %v", pos, msg)
}
data, err := ioutil.ReadFile(filepath.Join("testdata", name))
fileName := filepath.Join("testdata", name)
data, err := ioutil.ReadFile(fileName)
if err != nil {
t.Fatal(err)
}
@ -71,6 +75,13 @@ func TestNoErrors(t *testing.T) {
if astDesc == nil {
t.Fatalf("parsing failed")
}
formatted := ast.Format(astDesc)
if !bytes.Equal(data, formatted) {
if *flagUpdate {
ioutil.WriteFile(fileName, formatted, 0644)
}
t.Fatalf("description is not formatted")
}
constInfo := ExtractConsts(astDesc, target, eh)
if constInfo == nil {
t.Fatalf("const extraction failed")

View File

@ -6,6 +6,7 @@ foo$1(a int8[C1:C2])
foo$2(a ptr[out, array[int32]])
foo$3(a union_arg)
foo$4() r0
foo$5(a int8['a':'z'])
resource r0[intptr]
@ -139,8 +140,8 @@ type templ_struct1[C] {
}
union_with_templ_struct [
f1 templ_struct0[C1, type0]
f2 templ_struct0[C2, struct0]
f1 templ_struct0[C1, type0]
f2 templ_struct0[C2, struct0]
] [varlen]
struct0 {
@ -183,7 +184,6 @@ foo$templ4(a ptr[in, templ_struct1[3]])
foo$templ5(a ptr[in, templ_struct1[3]])
foo$templ6(a ptr[in, templ_struct4])
# Structs.
s0 {

View File

@ -183,5 +183,6 @@ foo$505(a proc[20, 0]) ### proc per-process values must not be 0
foo$506(a ptr[in, array[int32, 0]]) ### arrays of size 0 are not supported
foo$507(a ptr[in, array[int32, 0:0]]) ### arrays of size 0 are not supported
foo$508(a ptr[in, string["foo", 3]]) ### string value "foo\x00" exceeds buffer length 3
foo$509(a int8['b':'a']) ### bad int range [98:97]
type type500 proc[C1, 8, int8] ### values starting from 1 with step 8 overflow base type for 32 procs