From 61a033e2ad4a2f38e37b00f9ced1c05c0b2e7f67 Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Sat, 30 Sep 2017 07:38:21 +0100 Subject: [PATCH] acpi: tweak parser and add tests for parser errors --- src/gopheros/device/acpi/aml/entity.go | 28 +- src/gopheros/device/acpi/aml/entity_test.go | 143 ++++++ src/gopheros/device/acpi/aml/parser.go | 23 +- src/gopheros/device/acpi/aml/parser_test.go | 496 +++++++++++++++++++- 4 files changed, 665 insertions(+), 25 deletions(-) create mode 100644 src/gopheros/device/acpi/aml/entity_test.go diff --git a/src/gopheros/device/acpi/aml/entity.go b/src/gopheros/device/acpi/aml/entity.go index 27dc22c..0e0658b 100644 --- a/src/gopheros/device/acpi/aml/entity.go +++ b/src/gopheros/device/acpi/aml/entity.go @@ -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 } diff --git a/src/gopheros/device/acpi/aml/entity_test.go b/src/gopheros/device/acpi/aml/entity_test.go new file mode 100644 index 0000000..7e49e97 --- /dev/null +++ b/src/gopheros/device/acpi/aml/entity_test.go @@ -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) + } + } +} diff --git a/src/gopheros/device/acpi/aml/parser.go b/src/gopheros/device/acpi/aml/parser.go index 75339c2..2bcc3d8 100644 --- a/src/gopheros/device/acpi/aml/parser.go +++ b/src/gopheros/device/acpi/aml/parser.go @@ -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 diff --git a/src/gopheros/device/acpi/aml/parser_test.go b/src/gopheros/device/acpi/aml/parser_test.go index 6c117fe..a7db7ec 100644 --- a/src/gopheros/device/acpi/aml/parser_test.go +++ b/src/gopheros/device/acpi/aml/parser_test.go @@ -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 +}