mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
acpi: tweak parser and add tests for parser errors
This commit is contained in:
parent
d020045887
commit
61a033e2ad
@ -405,12 +405,15 @@ type namedReference struct {
|
||||
}
|
||||
|
||||
func (ref *namedReference) Resolve(errWriter io.Writer, rootNs ScopeEntity) bool {
|
||||
if ref.target = scopeFind(ref.parent, rootNs, ref.targetName); ref.target == nil {
|
||||
kfmt.Fprintf(errWriter, "could not resolve referenced symbol: %s (parent: %s)\n", ref.targetName, ref.parent.Name())
|
||||
return false
|
||||
if ref.target == nil {
|
||||
ref.target = scopeFind(ref.parent, rootNs, ref.targetName)
|
||||
}
|
||||
|
||||
return true
|
||||
if ref.target == nil {
|
||||
kfmt.Fprintf(errWriter, "could not resolve referenced symbol: %s (parent: %s)\n", ref.targetName, ref.parent.Name())
|
||||
}
|
||||
|
||||
return ref.target != nil
|
||||
}
|
||||
|
||||
// methodInvocationEntity describes an AML method invocation.
|
||||
@ -467,16 +470,17 @@ func (ent *mutexEntity) Parent() ScopeEntity { return ent.parent }
|
||||
func (ent *mutexEntity) setParent(parent ScopeEntity) { ent.parent = parent }
|
||||
func (ent *mutexEntity) getArgs() []interface{} { return nil }
|
||||
func (ent *mutexEntity) setArg(argIndex uint8, arg interface{}) bool {
|
||||
// arg 0 is the mutex name
|
||||
if argIndex == 0 {
|
||||
var ok bool
|
||||
var ok bool
|
||||
switch argIndex {
|
||||
case 0:
|
||||
// arg 0 is the mutex name
|
||||
ent.name, ok = arg.(string)
|
||||
return ok
|
||||
case 1:
|
||||
// arg1 is the sync level (bits 0:3)
|
||||
var syncLevel uint64
|
||||
syncLevel, ok = arg.(uint64)
|
||||
ent.syncLevel = uint8(syncLevel) & 0xf
|
||||
}
|
||||
|
||||
// arg1 is the sync level (bits 0:3)
|
||||
syncLevel, ok := arg.(uint64)
|
||||
ent.syncLevel = uint8(syncLevel) & 0xf
|
||||
return ok
|
||||
}
|
||||
func (ent *mutexEntity) TableHandle() uint8 { return ent.tableHandle }
|
||||
|
143
src/gopheros/device/acpi/aml/entity_test.go
Normal file
143
src/gopheros/device/acpi/aml/entity_test.go
Normal file
@ -0,0 +1,143 @@
|
||||
package aml
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEntityMethods(t *testing.T) {
|
||||
specs := []Entity{
|
||||
&unnamedEntity{},
|
||||
&constEntity{},
|
||||
&scopeEntity{},
|
||||
&bufferEntity{},
|
||||
&fieldUnitEntity{},
|
||||
&indexFieldEntity{},
|
||||
&namedReference{},
|
||||
&methodInvocationEntity{},
|
||||
&Method{},
|
||||
&Device{},
|
||||
&mutexEntity{},
|
||||
&eventEntity{},
|
||||
}
|
||||
|
||||
t.Run("table handle methods", func(t *testing.T) {
|
||||
exp := uint8(42)
|
||||
for specIndex, spec := range specs {
|
||||
spec.setTableHandle(exp)
|
||||
if got := spec.TableHandle(); got != exp {
|
||||
t.Errorf("[spec %d] expected to get back handle %d; got %d", specIndex, exp, got)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("append/remove/get parent methods", func(t *testing.T) {
|
||||
parent := &scopeEntity{name: `\`}
|
||||
|
||||
for specIndex, spec := range specs {
|
||||
parent.Append(spec)
|
||||
if got := spec.Parent(); got != parent {
|
||||
t.Errorf("[spec %d] expected to get back parent %v; got %v", specIndex, parent, got)
|
||||
}
|
||||
|
||||
parent.removeChild(spec)
|
||||
}
|
||||
|
||||
if got := len(parent.Children()); got != 0 {
|
||||
t.Fatalf("expected parent not to have any child nodes; got %d", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEntityArgAssignment(t *testing.T) {
|
||||
specs := []struct {
|
||||
ent Entity
|
||||
argList []interface{}
|
||||
expArgList []interface{}
|
||||
limitedArgs bool
|
||||
}{
|
||||
{
|
||||
&unnamedEntity{},
|
||||
[]interface{}{"foo", 1, "bar"},
|
||||
[]interface{}{"foo", 1, "bar"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
&constEntity{},
|
||||
[]interface{}{"foo"},
|
||||
nil, // constEntity populates its internal state using the 1st arg
|
||||
true,
|
||||
},
|
||||
{
|
||||
&scopeEntity{},
|
||||
[]interface{}{"foo", 1, 2, 3},
|
||||
[]interface{}{1, 2, 3}, // scopeEntity will treat arg0 as the scope name if it is a string
|
||||
false,
|
||||
},
|
||||
{
|
||||
&bufferEntity{},
|
||||
[]interface{}{1, []byte{}},
|
||||
nil, // bufferEntity populates its internal state using the first 2 args
|
||||
true,
|
||||
},
|
||||
{
|
||||
®ionEntity{},
|
||||
[]interface{}{"REG0", uint64(0x4), 0, 10},
|
||||
[]interface{}{0, 10}, // region populates its internal state using the first 2 args
|
||||
true,
|
||||
},
|
||||
{
|
||||
&mutexEntity{},
|
||||
[]interface{}{"MUT0", uint64(1)},
|
||||
nil, // mutexEntity populates its internal state using the first 2 args
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
nextSpec:
|
||||
for specIndex, spec := range specs {
|
||||
for i, arg := range spec.argList {
|
||||
if !spec.ent.setArg(uint8(i), arg) {
|
||||
t.Errorf("[spec %d] error setting arg %d", specIndex, i)
|
||||
continue nextSpec
|
||||
}
|
||||
}
|
||||
|
||||
if spec.limitedArgs {
|
||||
if spec.ent.setArg(uint8(len(spec.argList)), nil) {
|
||||
t.Errorf("[spec %d] expected additional calls to setArg to return false", specIndex)
|
||||
continue nextSpec
|
||||
}
|
||||
}
|
||||
|
||||
if got := spec.ent.getArgs(); !reflect.DeepEqual(got, spec.expArgList) {
|
||||
t.Errorf("[spec %d] expected to get back arg list %v; got %v", specIndex, spec.expArgList, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntityResolveErrors(t *testing.T) {
|
||||
scope := &scopeEntity{name: `\`}
|
||||
|
||||
specs := []resolver{
|
||||
// Unknown connection entity
|
||||
&fieldUnitEntity{connectionName: "CON0"},
|
||||
// Unknown region
|
||||
&fieldUnitEntity{connectionName: `\`, regionName: "REG0"},
|
||||
// Unknown connection entity
|
||||
&indexFieldEntity{connectionName: "CON0"},
|
||||
// Unknown index register
|
||||
&indexFieldEntity{connectionName: `\`, indexRegName: "IND0"},
|
||||
// Unknown data register
|
||||
&indexFieldEntity{connectionName: `\`, indexRegName: `\`, dataRegName: "DAT0"},
|
||||
// Unknown reference
|
||||
&namedReference{unnamedEntity: unnamedEntity{parent: scope}, targetName: "TRG0"},
|
||||
}
|
||||
|
||||
for specIndex, spec := range specs {
|
||||
if spec.Resolve(ioutil.Discard, scope) {
|
||||
t.Errorf("[spec %d] expected Resolve() to fail", specIndex)
|
||||
}
|
||||
}
|
||||
}
|
@ -231,8 +231,8 @@ func (p *Parser) parseNamespacedObj(op opcode, maxReadOffset uint32) bool {
|
||||
case opMethod:
|
||||
m := &Method{scopeEntity: scopeEntity{name: name}}
|
||||
|
||||
flags, ok := p.parseNumConstant(1)
|
||||
if !ok {
|
||||
flags, flagOk := p.parseNumConstant(1)
|
||||
if !flagOk {
|
||||
return false
|
||||
}
|
||||
m.argCount = (uint8(flags) & 0x7) // bits[0:2]
|
||||
@ -281,7 +281,7 @@ func (p *Parser) parseArg(info *opcodeInfo, obj Entity, argIndex uint8, argType
|
||||
// If object is a scoped entity enter it's scope before parsing
|
||||
// the term list. Otherwise, create an unnamed scope, attach it
|
||||
// as the next argument to obj and enter that.
|
||||
if s, ok := obj.(ScopeEntity); ok {
|
||||
if s, isScopeEnt := obj.(ScopeEntity); isScopeEnt {
|
||||
p.scopeEnter(s)
|
||||
} else {
|
||||
ns := &scopeEntity{op: opScope}
|
||||
@ -328,8 +328,6 @@ func (p *Parser) makeObjForOpcode(info *opcodeInfo) Entity {
|
||||
var obj Entity
|
||||
|
||||
switch {
|
||||
case info.objType == objTypeLocalScope:
|
||||
obj = new(scopeEntity)
|
||||
case info.op == opOpRegion:
|
||||
obj = new(regionEntity)
|
||||
case info.op == opBuffer:
|
||||
@ -479,7 +477,7 @@ func (p *Parser) parseFieldList(op opcode, args []interface{}, maxReadOffset uin
|
||||
switch op {
|
||||
case opField: // Field := PkgLength Region AccessFlags FieldList
|
||||
if len(args) != 2 {
|
||||
kfmt.Fprintf(p.errWriter, "[table: %s, offset: %d] unsupported opcode [0x%2x] invalid arg count: %d\n", p.tableName, p.r.Offset(), uint32(op), len(args))
|
||||
kfmt.Fprintf(p.errWriter, "[table: %s, offset: %d, opcode 0x%2x] invalid arg count: %d\n", p.tableName, p.r.Offset(), uint32(op), len(args))
|
||||
return false
|
||||
}
|
||||
|
||||
@ -494,7 +492,7 @@ func (p *Parser) parseFieldList(op opcode, args []interface{}, maxReadOffset uin
|
||||
}
|
||||
case opIndexField: // Field := PkgLength IndexFieldName DataFieldName AccessFlags FieldList
|
||||
if len(args) != 3 {
|
||||
kfmt.Fprintf(p.errWriter, "[table: %s, offset: %d] unsupported opcode [0x%2x] invalid arg count: %d\n", p.tableName, p.r.Offset(), uint32(op), len(args))
|
||||
kfmt.Fprintf(p.errWriter, "[table: %s, offset: %d, opcode 0x%2x] invalid arg count: %d\n", p.tableName, p.r.Offset(), uint32(op), len(args))
|
||||
return false
|
||||
}
|
||||
|
||||
@ -524,7 +522,7 @@ func (p *Parser) parseFieldList(op opcode, args []interface{}, maxReadOffset uin
|
||||
resolvedConnection Entity
|
||||
)
|
||||
|
||||
for p.r.Offset() < maxReadOffset && !p.r.EOF() {
|
||||
for p.r.Offset() < maxReadOffset {
|
||||
next, err := p.r.ReadByte()
|
||||
if err != nil {
|
||||
return false
|
||||
@ -818,11 +816,10 @@ func (p *Parser) parseTarget() (interface{}, bool) {
|
||||
|
||||
if ok {
|
||||
switch {
|
||||
case nextOpcode.op == opZero: // this is actuall a NullName
|
||||
case nextOpcode.op == opZero: // this is actually a NullName
|
||||
p.r.SetOffset(curOffset + 1)
|
||||
return &constEntity{op: opStringPrefix, val: ""}, true
|
||||
case opIsArg(nextOpcode.op): // ArgObj | LocalObj
|
||||
case nextOpcode.op == opRefOf || nextOpcode.op == opDerefOf || nextOpcode.op == opIndex || nextOpcode.op == opDebug: // Type6 | DebugObj
|
||||
case opIsArg(nextOpcode.op) || nextOpcode.op == opRefOf || nextOpcode.op == opDerefOf || nextOpcode.op == opIndex || nextOpcode.op == opDebug: // LocalObj | ArgObj | Type6 | DebugObj
|
||||
default:
|
||||
// Unexpected opcode
|
||||
return nil, false
|
||||
@ -863,6 +860,7 @@ func (p *Parser) parseNameString() (string, bool) {
|
||||
p.r.ReadByte()
|
||||
case '^': // PrefixPath := Nothing | '^' PrefixPath
|
||||
str = append(str, next)
|
||||
p.r.ReadByte()
|
||||
for {
|
||||
next, err = p.r.PeekByte()
|
||||
if err != nil {
|
||||
@ -880,6 +878,9 @@ func (p *Parser) parseNameString() (string, bool) {
|
||||
|
||||
// NamePath := NameSeg | DualNamePath | MultiNamePath | NullName
|
||||
next, err = p.r.ReadByte()
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
var readCount int
|
||||
switch next {
|
||||
case 0x00: // NullName
|
||||
|
@ -32,7 +32,7 @@ func TestParser(t *testing.T) {
|
||||
p := NewParser(ioutil.Discard, rootNS)
|
||||
|
||||
for _, tableName := range spec {
|
||||
tableName := strings.Replace(tableName, ".aml", "", -1)
|
||||
tableName = strings.Replace(tableName, ".aml", "", -1)
|
||||
if err := p.ParseAML(0, tableName, resolver.LookupTable(tableName)); err != nil {
|
||||
t.Errorf("[spec %d] [%s]: %v", specIndex, tableName, err)
|
||||
break
|
||||
@ -81,7 +81,7 @@ func TestTableHandleAssignment(t *testing.T) {
|
||||
scopeVisit(0, p.root, EntityTypeAny, func(_ int, ent Entity) bool {
|
||||
visitedNodes++
|
||||
if ent.TableHandle() == expHandle {
|
||||
t.Errorf("encounted entity that should have been pruned: %#+v", ent)
|
||||
t.Errorf("encountered entity that should have been pruned: %#+v", ent)
|
||||
}
|
||||
return true
|
||||
})
|
||||
@ -91,6 +91,483 @@ func TestTableHandleAssignment(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePkgLength(t *testing.T) {
|
||||
specs := []struct {
|
||||
payload []byte
|
||||
exp uint32
|
||||
}{
|
||||
// lead byte bits (6:7) indicate 1 extra byte for the len. The
|
||||
// parsed length will use bits 0:3 from the lead byte plus
|
||||
// the full 8 bits of the following byte.
|
||||
{
|
||||
[]byte{1<<6 | 7, 255},
|
||||
4087,
|
||||
},
|
||||
// lead byte bits (6:7) indicate 2 extra bytes for the len. The
|
||||
// parsed length will use bits 0:3 from the lead byte plus
|
||||
// the full 8 bits of the following bytes.
|
||||
{
|
||||
[]byte{2<<6 | 8, 255, 128},
|
||||
528376,
|
||||
},
|
||||
// lead byte bits (6:7) indicate 3 extra bytes for the len. The
|
||||
// parsed length will use bits 0:3 from the lead byte plus
|
||||
// the full 8 bits of the following bytes.
|
||||
{
|
||||
[]byte{3<<6 | 6, 255, 128, 42},
|
||||
44568566,
|
||||
},
|
||||
}
|
||||
|
||||
p := &Parser{errWriter: ioutil.Discard}
|
||||
|
||||
for specIndex, spec := range specs {
|
||||
mockParserPayload(p, spec.payload)
|
||||
got, ok := p.parsePkgLength()
|
||||
if !ok {
|
||||
t.Errorf("[spec %d] parsePkgLength returned false", specIndex)
|
||||
continue
|
||||
}
|
||||
|
||||
if got != spec.exp {
|
||||
t.Errorf("[spec %d] expected parsePkgLength to return %d; got %d", specIndex, spec.exp, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserErrorHandling(t *testing.T) {
|
||||
p := &Parser{
|
||||
errWriter: ioutil.Discard,
|
||||
}
|
||||
|
||||
t.Run("ParseAML errors", func(t *testing.T) {
|
||||
t.Run("parseObjList error", func(t *testing.T) {
|
||||
p.root = &scopeEntity{op: opScope, name: `\`}
|
||||
|
||||
// Setup resolver to serve an AML stream containing an invalid opcode
|
||||
header := mockParserPayload(p, []byte{0x5b, 0x00})
|
||||
|
||||
if err := p.ParseAML(uint8(42), "DSDT", header); err == nil {
|
||||
t.Fatal("expected ParseAML to return an error")
|
||||
}
|
||||
|
||||
// Setup resolver to serve an AML stream containing an incomplete extended opcode
|
||||
header = mockParserPayload(p, []byte{0x5b})
|
||||
|
||||
if err := p.ParseAML(uint8(42), "DSDT", header); err == nil {
|
||||
t.Fatal("expected ParseAML to return an error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unresolved entities", func(t *testing.T) {
|
||||
p.root = &scopeEntity{op: opScope, name: `\`}
|
||||
|
||||
// Inject a reference entity to the tree
|
||||
p.root.Append(&namedReference{
|
||||
targetName: "UNKNOWN",
|
||||
})
|
||||
|
||||
// Setup resolver to serve an empty AML stream
|
||||
header := mockParserPayload(p, nil)
|
||||
|
||||
if err := p.ParseAML(uint8(42), "DSDT", header); err != errResolvingEntities {
|
||||
t.Fatalf("expected ParseAML to return errResolvingEntities; got %v", err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("parseObj errors", func(t *testing.T) {
|
||||
t.Run("parsePkgLength error", func(t *testing.T) {
|
||||
p.root = &scopeEntity{op: opScope, name: `\`}
|
||||
|
||||
// Setup resolver to serve an AML stream containing an incomplete
|
||||
// buffer specification
|
||||
header := mockParserPayload(p, []byte{byte(opBuffer)})
|
||||
|
||||
if err := p.ParseAML(uint8(42), "DSDT", header); err == nil {
|
||||
t.Fatal("expected parsePkgLength to return an error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("incomplete object list", func(t *testing.T) {
|
||||
p.root = &scopeEntity{op: opScope, name: `\`}
|
||||
|
||||
// Setup resolver to serve an AML stream containing an incomplete
|
||||
// buffer arglist specification
|
||||
header := mockParserPayload(p, []byte{byte(opBuffer), 0x10})
|
||||
|
||||
if err := p.ParseAML(uint8(42), "DSDT", header); err == nil {
|
||||
t.Fatal("expected parsePkgLength to return an error")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("finalizeObj errors", func(t *testing.T) {
|
||||
t.Run("else without matching if", func(t *testing.T) {
|
||||
p.root = &scopeEntity{op: opScope, name: `\`}
|
||||
p.root.Append(&constEntity{val: 0x42})
|
||||
p.root.Append(&scopeEntity{op: opElse})
|
||||
|
||||
// Setup resolver to serve an AML stream containing an
|
||||
// empty else statement without a matching if
|
||||
header := mockParserPayload(p, []byte{byte(opElse), 0x0})
|
||||
|
||||
if err := p.ParseAML(uint8(42), "DSDT", header); err == nil {
|
||||
t.Fatal("expected finalizeObj to return an error")
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
t.Run("parseScope errors", func(t *testing.T) {
|
||||
t.Run("parseNameString error", func(t *testing.T) {
|
||||
p.root = &scopeEntity{op: opScope, name: `\`}
|
||||
|
||||
header := mockParserPayload(p, []byte{
|
||||
byte(opScope),
|
||||
0x10, // pkglen
|
||||
})
|
||||
|
||||
if err := p.ParseAML(uint8(42), "DSDT", header); err == nil {
|
||||
t.Fatal("expected parseScope to return an error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unknown scope", func(t *testing.T) {
|
||||
p.root = &scopeEntity{op: opScope, name: `\`}
|
||||
|
||||
header := mockParserPayload(p, []byte{
|
||||
byte(opScope),
|
||||
0x10, // pkglen
|
||||
'F', 'O', 'O', 'F',
|
||||
})
|
||||
|
||||
if err := p.ParseAML(uint8(42), "DSDT", header); err == nil {
|
||||
t.Fatal("expected parseScope to return an error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nameless scope", func(t *testing.T) {
|
||||
p.root = &scopeEntity{}
|
||||
|
||||
header := mockParserPayload(p, []byte{
|
||||
byte(opScope),
|
||||
0x02, // pkglen
|
||||
'\\', // scope name: "\" (root scope)
|
||||
0x00, // null string
|
||||
})
|
||||
|
||||
if err := p.ParseAML(uint8(42), "DSDT", header); err == nil {
|
||||
t.Fatal("expected parseScope to return an error")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("parseNamespacedObj errors", func(t *testing.T) {
|
||||
t.Run("parseNameString error", func(t *testing.T) {
|
||||
p.root = &scopeEntity{op: opScope, name: `\`}
|
||||
|
||||
mockParserPayload(p, nil)
|
||||
|
||||
if p.parseNamespacedObj(opDevice, 10) {
|
||||
t.Fatal("expected parseNamespacedObj to return false")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scope lookup error", func(t *testing.T) {
|
||||
p.root = &scopeEntity{op: opScope, name: `\`}
|
||||
|
||||
header := mockParserPayload(p, []byte{'^', 'F', 'A', 'B', 'C'})
|
||||
|
||||
p.scopeEnter(p.root)
|
||||
if p.parseNamespacedObj(opDevice, header.Length) {
|
||||
t.Fatal("expected parseNamespacedObj to return false")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("error parsing method arg count", func(t *testing.T) {
|
||||
p.root = &scopeEntity{op: opScope, name: `\`}
|
||||
|
||||
header := mockParserPayload(p, []byte{'F', 'A', 'B', 'C'})
|
||||
|
||||
p.scopeEnter(p.root)
|
||||
if p.parseNamespacedObj(opMethod, header.Length) {
|
||||
t.Fatal("expected parseNamespacedObj to return false")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("parseArg bytelist errors", func(t *testing.T) {
|
||||
p.root = &scopeEntity{op: opScope, name: `\`}
|
||||
|
||||
mockParserPayload(p, nil)
|
||||
|
||||
if p.parseArg(new(opcodeInfo), new(unnamedEntity), 0, opArgByteList, 42) {
|
||||
t.Fatal("expected parseNamespacedObj to return false")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("parseMethodInvocationOrNameRef errors", func(t *testing.T) {
|
||||
t.Run("missing args", func(t *testing.T) {
|
||||
p.root = &scopeEntity{op: opScope, name: `\`}
|
||||
p.root.Append(&Method{
|
||||
scopeEntity: scopeEntity{name: "MTHD"},
|
||||
argCount: 10,
|
||||
})
|
||||
|
||||
mockParserPayload(p, []byte{
|
||||
'M', 'T', 'H', 'D',
|
||||
byte(opIf), // Incomplete type2 opcode
|
||||
})
|
||||
|
||||
p.scopeEnter(p.root)
|
||||
if p.parseMethodInvocationOrNameRef() {
|
||||
t.Fatal("expected parseMethodInvocationOrNameRef to return false")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("parseFieldList errors", func(t *testing.T) {
|
||||
specs := []struct {
|
||||
op opcode
|
||||
args []interface{}
|
||||
maxReadOffset uint32
|
||||
payload []byte
|
||||
}{
|
||||
// Invalid arg count for opField
|
||||
{
|
||||
opField,
|
||||
nil,
|
||||
0,
|
||||
nil,
|
||||
},
|
||||
// Wrong arg type for opField
|
||||
{
|
||||
opField,
|
||||
[]interface{}{0, uint64(42)},
|
||||
0,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
opField,
|
||||
[]interface{}{"FLD0", uint32(42)},
|
||||
0,
|
||||
nil,
|
||||
},
|
||||
// Invalid arg count for opIndexField
|
||||
{
|
||||
opIndexField,
|
||||
nil,
|
||||
0,
|
||||
nil,
|
||||
},
|
||||
// Wrong arg type for opIndexField
|
||||
{
|
||||
opIndexField,
|
||||
[]interface{}{0, "FLD1", "FLD2"},
|
||||
0,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
opIndexField,
|
||||
[]interface{}{"FLD0", 0, "FLD2"},
|
||||
0,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
opIndexField,
|
||||
[]interface{}{"FLD0", "FLD1", 0},
|
||||
0,
|
||||
nil,
|
||||
},
|
||||
// unexpected EOF parsing fields
|
||||
{
|
||||
opField,
|
||||
[]interface{}{"FLD0", uint64(42)},
|
||||
128,
|
||||
nil,
|
||||
},
|
||||
// reserved field (0x00) with missing pkgLen
|
||||
{
|
||||
opField,
|
||||
[]interface{}{"FLD0", uint64(42)},
|
||||
128,
|
||||
[]byte{0x00},
|
||||
},
|
||||
// access field (0x01) with missing accessType
|
||||
{
|
||||
opField,
|
||||
[]interface{}{"FLD0", uint64(42)},
|
||||
128,
|
||||
[]byte{0x01},
|
||||
},
|
||||
// access field (0x01) with missing attribute byte
|
||||
{
|
||||
opField,
|
||||
[]interface{}{"FLD0", uint64(42)},
|
||||
128,
|
||||
[]byte{0x01, 0x01},
|
||||
},
|
||||
// connect field (0x02) with incomplete TermObject => Buffer arg
|
||||
{
|
||||
opField,
|
||||
[]interface{}{"FLD0", uint64(42)},
|
||||
128,
|
||||
[]byte{0x02, byte(opBuffer)},
|
||||
},
|
||||
// extended access field (0x03) with missing ext. accessType
|
||||
{
|
||||
opField,
|
||||
[]interface{}{"FLD0", uint64(42)},
|
||||
128,
|
||||
[]byte{0x03},
|
||||
},
|
||||
// extended access field (0x03) with missing ext. attribute byte
|
||||
{
|
||||
opField,
|
||||
[]interface{}{"FLD0", uint64(42)},
|
||||
128,
|
||||
[]byte{0x03, 0x01},
|
||||
},
|
||||
// extended access field (0x03) with missing access byte count value
|
||||
{
|
||||
opField,
|
||||
[]interface{}{"FLD0", uint64(42)},
|
||||
128,
|
||||
[]byte{0x03, 0x01, 0x02},
|
||||
},
|
||||
// named field with invalid name
|
||||
{
|
||||
opField,
|
||||
[]interface{}{"FLD0", uint64(42)},
|
||||
128,
|
||||
[]byte{0xff},
|
||||
},
|
||||
// named field with invalid pkgLen
|
||||
{
|
||||
opField,
|
||||
[]interface{}{"FLD0", uint64(42)},
|
||||
128,
|
||||
[]byte{'N', 'A', 'M', 'E'},
|
||||
},
|
||||
}
|
||||
|
||||
for specIndex, spec := range specs {
|
||||
mockParserPayload(p, spec.payload)
|
||||
|
||||
if p.parseFieldList(spec.op, spec.args, spec.maxReadOffset) {
|
||||
t.Errorf("[spec %d] expected parseFieldLis to return false", specIndex)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("parsePkgLength errors", func(t *testing.T) {
|
||||
specs := [][]byte{
|
||||
// lead byte bits (6:7) indicate 1 extra byte that is missing
|
||||
[]byte{1 << 6},
|
||||
// lead byte bits (6:7) indicate 2 extra bytes with the 1st and then 2nd missing
|
||||
[]byte{2 << 6},
|
||||
[]byte{2 << 6, 0x1},
|
||||
// lead byte bits (6:7) indicate 3 extra bytes with the 1st and then 2nd and then 3rd missing
|
||||
[]byte{3 << 6},
|
||||
[]byte{3 << 6, 0x1},
|
||||
[]byte{3 << 6, 0x1, 0x2},
|
||||
}
|
||||
|
||||
for specIndex, spec := range specs {
|
||||
mockParserPayload(p, spec)
|
||||
|
||||
if _, ok := p.parsePkgLength(); ok {
|
||||
t.Errorf("[spec %d] expected parsePkgLength to return false", specIndex)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("parseString errors", func(t *testing.T) {
|
||||
specs := [][]byte{
|
||||
// Unexpected EOF before terminating null byte
|
||||
[]byte{'A'},
|
||||
// Characters outside the allowed [0x01, 0x7f] range
|
||||
[]byte{'A', 0xba, 0xdf, 0x00},
|
||||
}
|
||||
|
||||
for specIndex, spec := range specs {
|
||||
mockParserPayload(p, spec)
|
||||
|
||||
if _, ok := p.parseString(); ok {
|
||||
t.Errorf("[spec %d] expected parseString to return false", specIndex)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("parseTarget errors", func(t *testing.T) {
|
||||
t.Run("unexpected opcode", func(t *testing.T) {
|
||||
// Unexpected opcode
|
||||
mockParserPayload(p, []byte{byte(opAnd)})
|
||||
|
||||
if _, ok := p.parseTarget(); ok {
|
||||
t.Error("expected parseTarget to return false")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("corrupted data", func(t *testing.T) {
|
||||
// Invalid opcode and not a method invocation nor a namestring
|
||||
mockParserPayload(p, []byte{0xba, 0xad})
|
||||
|
||||
if _, ok := p.parseTarget(); ok {
|
||||
t.Error("expected parseTarget to return false")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("parseNameString errors", func(t *testing.T) {
|
||||
t.Run("EOF while parsing path prefix", func(t *testing.T) {
|
||||
mockParserPayload(p, []byte{'^'})
|
||||
|
||||
if _, ok := p.parseNameString(); ok {
|
||||
t.Error("expected parseNameString to return false")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("EOF while parsing multiname path", func(t *testing.T) {
|
||||
specs := [][]byte{
|
||||
// multiname path prefix but no data following
|
||||
[]byte{0x2f},
|
||||
[]byte{
|
||||
0x2f, // multiname path prefix
|
||||
0x0, // no segments (segments must be > 0)
|
||||
},
|
||||
[]byte{
|
||||
0x2f, // multiname path prefix
|
||||
0x1, // 1 expected segment but no more data available
|
||||
},
|
||||
[]byte{
|
||||
'\\', // RootChar and no more data
|
||||
},
|
||||
}
|
||||
|
||||
for specIndex, spec := range specs {
|
||||
mockParserPayload(p, spec)
|
||||
if _, ok := p.parseNameString(); ok {
|
||||
t.Errorf("[spec %d] expected parseNameString to return false", specIndex)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func mockParserPayload(p *Parser, payload []byte) *table.SDTHeader {
|
||||
resolver := fixedPayloadResolver{payload}
|
||||
header := resolver.LookupTable("DSDT")
|
||||
p.r.Init(
|
||||
uintptr(unsafe.Pointer(header)),
|
||||
header.Length,
|
||||
uint32(unsafe.Sizeof(table.SDTHeader{})),
|
||||
)
|
||||
|
||||
return resolver.LookupTable("DSDT")
|
||||
}
|
||||
|
||||
func pkgDir() string {
|
||||
_, f, _, _ := runtime.Caller(1)
|
||||
return filepath.Dir(f)
|
||||
@ -117,3 +594,18 @@ func (m mockResolver) LookupTable(name string) *table.SDTHeader {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type fixedPayloadResolver struct {
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func (f fixedPayloadResolver) LookupTable(name string) *table.SDTHeader {
|
||||
hdrLen := int(unsafe.Sizeof(table.SDTHeader{}))
|
||||
buf := make([]byte, len(f.payload)+hdrLen)
|
||||
copy(buf[hdrLen:], f.payload)
|
||||
|
||||
hdr := (*table.SDTHeader)(unsafe.Pointer(&buf[0]))
|
||||
hdr.Length = uint32(len(buf))
|
||||
|
||||
return hdr
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user