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