• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// This file implements the logic of bpfix and also provides a programmatic interface
16
17package bpfix
18
19import (
20	"bytes"
21	"fmt"
22	"github.com/google/blueprint/parser"
23)
24
25// A FixRequest specifies the details of which fixes to apply to an individual file
26// A FixRequest doesn't specify whether to do a dry run or where to write the results; that's in cmd/bpfix.go
27type FixRequest struct {
28	simplifyKnownRedundantVariables bool
29	removeEmptyLists                bool
30}
31
32func NewFixRequest() FixRequest {
33	return FixRequest{}
34}
35
36func (r FixRequest) AddAll() (result FixRequest) {
37	result = r
38	result.simplifyKnownRedundantVariables = true
39	result.removeEmptyLists = true
40	return result
41}
42
43// FixTree repeatedly applies the fixes listed in the given FixRequest to the given File
44// until there is no fix that affects the tree
45func FixTree(tree *parser.File, config FixRequest) (fixed *parser.File, err error) {
46	prevIdentifier, err := fingerprint(tree)
47	if err != nil {
48		return nil, err
49	}
50
51	fixed = tree
52	maxNumIterations := 20
53	i := 0
54	for {
55		fixed, err = fixTreeOnce(fixed, config)
56		newIdentifier, err := fingerprint(tree)
57		if err != nil {
58			return nil, err
59		}
60		if bytes.Equal(newIdentifier, prevIdentifier) {
61			break
62		}
63		prevIdentifier = newIdentifier
64		// any errors from a previous iteration generally get thrown away and overwritten by errors on the next iteration
65
66		// detect infinite loop
67		i++
68		if i >= maxNumIterations {
69			return nil, fmt.Errorf("Applied fixes %s times and yet the tree continued to change. Is there an infinite loop?", i)
70			break
71		}
72	}
73	return fixed, err
74}
75
76// returns a unique identifier for the given tree that can be used to determine whether the tree changed
77func fingerprint(tree *parser.File) (fingerprint []byte, err error) {
78	bytes, err := parser.Print(tree)
79	if err != nil {
80		return nil, err
81	}
82	return bytes, nil
83}
84
85func fixTreeOnce(tree *parser.File, config FixRequest) (fixed *parser.File, err error) {
86	if config.simplifyKnownRedundantVariables {
87		tree, err = simplifyKnownPropertiesDuplicatingEachOther(tree)
88		if err != nil {
89			return nil, err
90		}
91	}
92	if config.removeEmptyLists {
93		tree, err = removePropertiesHavingTheirDefaultValues(tree)
94		if err != nil {
95			return nil, err
96		}
97	}
98	return tree, err
99}
100
101func simplifyKnownPropertiesDuplicatingEachOther(tree *parser.File) (fixed *parser.File, err error) {
102	// remove from local_include_dirs anything in export_include_dirs
103	fixed, err = removeMatchingModuleListProperties(tree, "export_include_dirs", "local_include_dirs")
104	return fixed, err
105}
106
107// removes from <items> every item present in <removals>
108func filterExpressionList(items *parser.List, removals *parser.List) {
109	writeIndex := 0
110	for _, item := range items.Values {
111		included := true
112		for _, removal := range removals.Values {
113			equal, err := parser.ExpressionsAreSame(item, removal)
114			if err != nil {
115				continue
116			}
117			if equal {
118				included = false
119				break
120			}
121		}
122		if included {
123			items.Values[writeIndex] = item
124			writeIndex++
125		}
126	}
127	items.Values = items.Values[:writeIndex]
128}
129
130// Remove each modules[i].Properties[<legacyName>][j] that matches a modules[i].Properties[<canonicalName>][k]
131func removeMatchingModuleListProperties(tree *parser.File, canonicalName string, legacyName string) (fixed *parser.File, err error) {
132	for _, def := range tree.Defs {
133		mod, ok := def.(*parser.Module)
134		if !ok {
135			continue
136		}
137		legacy, ok := mod.GetProperty(legacyName)
138		if !ok {
139			continue
140		}
141		legacyList, ok := legacy.Value.(*parser.List)
142		if !ok {
143			continue
144		}
145		canonical, ok := mod.GetProperty(canonicalName)
146		if !ok {
147			continue
148		}
149		canonicalList, ok := canonical.Value.(*parser.List)
150		if !ok {
151			continue
152		}
153		filterExpressionList(legacyList, canonicalList)
154	}
155	return tree, nil
156}
157
158func removePropertiesHavingTheirDefaultValues(tree *parser.File) (fixed *parser.File, err error) {
159	for _, def := range tree.Defs {
160		mod, ok := def.(*parser.Module)
161		if !ok {
162			continue
163		}
164		writeIndex := 0
165		for _, prop := range mod.Properties {
166			val := prop.Value
167			keep := true
168			switch val := val.(type) {
169			case *parser.List:
170				if len(val.Values) == 0 {
171					keep = false
172				}
173				break
174			default:
175				keep = true
176			}
177			if keep {
178				mod.Properties[writeIndex] = prop
179				writeIndex++
180			}
181		}
182		mod.Properties = mod.Properties[:writeIndex]
183	}
184	return tree, nil
185}
186