Skip to content

Commit 4b27b85

Browse files
authored
Merge pull request #104 from ivajloip/fix-memory-leak-when-parsing-many-modules
Fix memory leak when parsing multiple modules. Do this by creating an internal type dictionary for the `Modules` object, and then making the internal functions modify a type dictionary passed in as parameters, rather than directly modifying the global type dictionary.
2 parents 1c554e3 + 96010f2 commit 4b27b85

File tree

6 files changed

+46
-34
lines changed

6 files changed

+46
-34
lines changed

pkg/yang/ast.go

+23-14
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,6 @@ type meta struct {
6565
Module []*Module `yang:"module"`
6666
}
6767

68-
func init() {
69-
initTypes(reflect.TypeOf(&meta{}))
70-
}
71-
7268
// aliases is a map of "aliased" names, that is, two types of statements
7369
// that parse (nearly) the same.
7470
var aliases = map[string]string{
@@ -78,24 +74,35 @@ var aliases = map[string]string{
7874
// BuildAST builds an abstract syntax tree based on the yang statement s.
7975
// Normally it should return a *Module.
8076
func BuildAST(s *Statement) (Node, error) {
81-
v, err := build(s, nilValue)
77+
return buildASTWithTypeDict(s, &typeDict)
78+
}
79+
80+
// buildASTWithTypeDict creates an AST for the input statement, and returns its
81+
// root node. It also takes as input a type dictionary into which any
82+
// encountered typedefs within the statement are cached.
83+
func buildASTWithTypeDict(s *Statement, d *typeDictionary) (Node, error) {
84+
initTypes(reflect.TypeOf(&meta{}), d)
85+
v, err := build(s, nilValue, d)
8286
if err != nil {
8387
return nil, err
8488
}
8589
return v.Interface().(Node), nil
8690
}
8791

88-
// build builds and returns an AST from the statement s, with parent p, or
89-
// returns an error. The type of value returned depends on the keyword in s.
90-
func build(s *Statement, p reflect.Value) (v reflect.Value, err error) {
92+
// build builds and returns an AST from the statement s and with parent p. It
93+
// also takes as input a type dictionary d into which any encountered typedefs
94+
// within the statement are cached. The type of value returned depends on the
95+
// keyword in s. It returns an error if it cannot build the statement into its
96+
// corresponding Node type.
97+
func build(s *Statement, p reflect.Value, d *typeDictionary) (v reflect.Value, err error) {
9198
defer func() {
9299
// If we are returning a real Node then call addTypedefs
93100
// if the node possibly contains typedefs.
94101
if err != nil || v == nilValue {
95102
return
96103
}
97104
if t, ok := v.Interface().(Typedefer); ok {
98-
addTypedefs(t)
105+
d.addTypedefs(t)
99106
}
100107
}()
101108
kind := s.Keyword
@@ -197,7 +204,9 @@ func build(s *Statement, p reflect.Value) (v reflect.Value, err error) {
197204
}
198205

199206
// initTypes builds up the functions necessary to parse a Statement into the
200-
// type at. at must be a of type pointer to structure and that structure should
207+
// type at. It also builds up the functions to populate the input type
208+
// dictionary d with any encountered typedefs within the statement. at must be
209+
// a of type pointer to structure and that structure should
201210
// implement Node. For each field of the structure with a yang tag (e.g.,
202211
// `yang:"command"`), a function is created and "command" is mapped to it. The
203212
// complete map is then added to the typeMap map with at as the key.
@@ -258,7 +267,7 @@ func build(s *Statement, p reflect.Value) (v reflect.Value, err error) {
258267
// (This is to support merging Module and SubModule).
259268
//
260269
// If at contains substructures, initTypes recurses on the substructures.
261-
func initTypes(at reflect.Type) {
270+
func initTypes(at reflect.Type, d *typeDictionary) {
262271
if typeMap[at] != nil {
263272
return // we already defined this type
264273
}
@@ -323,7 +332,7 @@ func initTypes(at reflect.Type) {
323332
switch nameMap[name] {
324333
case nil:
325334
nameMap[name] = dt
326-
initTypes(dt) // Make sure that structure type is included
335+
initTypes(dt, d) // Make sure that structure type is included
327336
case dt:
328337
default:
329338
panic("redeclared type " + name)
@@ -400,7 +409,7 @@ func initTypes(at reflect.Type) {
400409
}
401410

402411
// Use build to build the value for this field.
403-
sv, err := build(s, v)
412+
sv, err := build(s, v, d)
404413
if err != nil {
405414
return err
406415
}
@@ -423,7 +432,7 @@ func initTypes(at reflect.Type) {
423432
if v.Type() != at {
424433
panic(fmt.Sprintf("given type %s, need type %s", v.Type(), at))
425434
}
426-
sv, err := build(s, v)
435+
sv, err := build(s, v, d)
427436
if err != nil {
428437
return err
429438
}

pkg/yang/ast_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ func TestAST(t *testing.T) {
207207
type meta struct {
208208
MainNode []*MainNode `yang:"main_node"`
209209
}
210-
initTypes(reflect.TypeOf(&meta{}))
210+
initTypes(reflect.TypeOf(&meta{}), &typeDict)
211211

212212
for _, tt := range []struct {
213213
line int

pkg/yang/entry.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ func ToEntry(n Node) (e *Entry) {
535535
Errors: []error{err},
536536
}
537537
}
538+
ms := RootNode(n).modules
538539
if e := entryCache[n]; e != nil {
539540
return e
540541
}
@@ -572,7 +573,7 @@ func ToEntry(n Node) (e *Entry) {
572573
switch s := n.(type) {
573574
case *Leaf:
574575
e := newLeaf(n)
575-
if errs := s.Type.resolve(); errs != nil {
576+
if errs := s.Type.resolve(ms.typeDict); errs != nil {
576577
e.Errors = errs
577578
}
578579
if s.Description != nil {
@@ -862,7 +863,7 @@ func ToEntry(n Node) (e *Entry) {
862863
}
863864

864865
if n.Type != nil {
865-
if errs := n.Type.resolve(); errs != nil {
866+
if errs := n.Type.resolve(ms.typeDict); errs != nil {
866867
e.addError(fmt.Errorf("deviation has unresolvable type, %v", errs))
867868
continue
868869
}
@@ -894,7 +895,7 @@ func ToEntry(n Node) (e *Entry) {
894895

895896
for _, sd := range d.Deviate {
896897
if sd.Type != nil {
897-
sd.Type.resolve()
898+
sd.Type.resolve(ms.typeDict)
898899
}
899900
}
900901
}

pkg/yang/modules.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Modules struct {
2828
includes map[*Module]bool // Modules we have already done include on
2929
byPrefix map[string]*Module // Cache of prefix lookup
3030
byNS map[string]*Module // Cache of namespace lookup
31+
typeDict *typeDictionary // Cache for type definitions.
3132
}
3233

3334
// NewModules returns a newly created and initialized Modules.
@@ -38,6 +39,7 @@ func NewModules() *Modules {
3839
includes: map[*Module]bool{},
3940
byPrefix: map[string]*Module{},
4041
byNS: map[string]*Module{},
42+
typeDict: &typeDictionary{dict: map[Node]map[string]*Typedef{}},
4143
}
4244
}
4345

@@ -61,7 +63,7 @@ func (ms *Modules) Parse(data, name string) error {
6163
return err
6264
}
6365
for _, s := range ss {
64-
n, err := BuildAST(s)
66+
n, err := buildASTWithTypeDict(s, ms.typeDict)
6567
if err != nil {
6668
return err
6769
}
@@ -283,7 +285,7 @@ func (ms *Modules) process() []error {
283285
// has not yet been built.
284286
errs = append(errs, ms.resolveIdentities()...)
285287
// Append any errors found trying to resolve typedefs
286-
errs = append(errs, resolveTypedefs()...)
288+
errs = append(errs, ms.typeDict.resolveTypedefs()...)
287289

288290
return errs
289291
}

pkg/yang/types.go

+13-13
Original file line numberDiff line numberDiff line change
@@ -92,37 +92,37 @@ func (d *typeDictionary) typedefs() []*Typedef {
9292
// addTypedefs is called from BuildAST after each Typedefer is defined. There
9393
// are no error conditions in this process as it is simply used to build up the
9494
// typedef dictionary.
95-
func addTypedefs(t Typedefer) {
95+
func (d *typeDictionary) addTypedefs(t Typedefer) {
9696
for _, td := range t.Typedefs() {
97-
typeDict.add(t, td.Name, td)
97+
d.add(t, td.Name, td)
9898
}
9999
}
100100

101101
// resolveTypedefs is called after all of modules and submodules have been read,
102102
// as well as their imports and includes. It resolves all typedefs found in all
103103
// modules and submodules read in.
104-
func resolveTypedefs() []error {
104+
func (d *typeDictionary) resolveTypedefs() []error {
105105
var errs []error
106106

107107
// When resolve typedefs, we may need to look up other typedefs.
108108
// We gather all typedefs into a slice so we don't deadlock on
109109
// typeDict.
110-
for _, td := range typeDict.typedefs() {
111-
errs = append(errs, td.resolve()...)
110+
for _, td := range d.typedefs() {
111+
errs = append(errs, td.resolve(d)...)
112112
}
113113
return errs
114114
}
115115

116116
// resolve creates a YangType for t, if not already done. Resolving t
117117
// requires resolving the Type that t is based on.
118-
func (t *Typedef) resolve() []error {
118+
func (t *Typedef) resolve(d *typeDictionary) []error {
119119
// If we have no parent we are a base type and
120120
// are already resolved.
121121
if t.Parent == nil || t.YangType != nil {
122122
return nil
123123
}
124124

125-
if errs := t.Type.resolve(); len(errs) != 0 {
125+
if errs := t.Type.resolve(d); len(errs) != 0 {
126126
return errs
127127
}
128128

@@ -158,7 +158,7 @@ func (t *Typedef) resolve() []error {
158158

159159
// resolve resolves Type t, as well as the underlying typedef for t. If t
160160
// cannot be resolved then one or more errors are returned.
161-
func (t *Type) resolve() (errs []error) {
161+
func (t *Type) resolve(d *typeDictionary) (errs []error) {
162162
if t.YangType != nil {
163163
return nil
164164
}
@@ -182,13 +182,13 @@ check:
182182
// If we have no prefix, or the prefix is what we call our own
183183
// root, then we look in our ancestors for a typedef of name.
184184
for n := Node(t); n != nil; n = n.ParentNode() {
185-
if td = typeDict.find(n, name); td != nil {
185+
if td = d.find(n, name); td != nil {
186186
break check
187187
}
188188
}
189189
// We need to check our sub-modules as well
190190
for _, in := range root.Include {
191-
if td = typeDict.find(in.Module, name); td != nil {
191+
if td = d.find(in.Module, name); td != nil {
192192
break check
193193
}
194194
}
@@ -208,12 +208,12 @@ check:
208208
// what module it is part of and if it is defined at the top
209209
// level of that module.
210210
var err error
211-
td, err = typeDict.findExternal(t, prefix, name)
211+
td, err = d.findExternal(t, prefix, name)
212212
if err != nil {
213213
return []error{err}
214214
}
215215
}
216-
if errs := td.resolve(); len(errs) > 0 {
216+
if errs := td.resolve(d); len(errs) > 0 {
217217
return errs
218218
}
219219

@@ -398,7 +398,7 @@ check:
398398
// so we have to check equality the hard way.
399399
looking:
400400
for _, ut := range t.Type {
401-
errs = append(errs, ut.resolve()...)
401+
errs = append(errs, ut.resolve(d)...)
402402
if ut.YangType != nil {
403403
for _, yt := range y.Type {
404404
if ut.YangType.Equal(yt) {

pkg/yang/types_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func TestTypeResolve(t *testing.T) {
8282
// in Type.
8383
} {
8484
// We can initialize a value to ourself, so to it here.
85-
errs := tt.in.resolve()
85+
errs := tt.in.resolve(&typeDict)
8686

8787
// TODO(borman): Do not hack out Root and Base. These
8888
// are hacked out for now because they can be self-referential,

0 commit comments

Comments
 (0)