github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/transform/staticlark/environment.go (about)

     1  package staticlark
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"sort"
     7  	"strings"
     8  
     9  	"go.starlark.net/syntax"
    10  )
    11  
    12  const sensitiveVarName = "$"
    13  
    14  // environment is a collection of variable bindings, and the
    15  // list of other bindings that it derives value from
    16  // TODO(dustmop): Handle lexical scoping
    17  type environment struct {
    18  	vars   map[string]*deriv
    19  	taints map[string]reason
    20  }
    21  
    22  func newEnvironment() *environment {
    23  	return &environment{
    24  		vars:   make(map[string]*deriv),
    25  		taints: make(map[string]reason),
    26  	}
    27  }
    28  
    29  func (e *environment) String() string {
    30  	result := "Env:\n"
    31  	keys := getKeys(e.vars)
    32  	for _, name := range keys {
    33  		der := e.vars[name]
    34  		status := ""
    35  		if _, ok := e.taints[name]; ok {
    36  			status = " tainted"
    37  		}
    38  		result += fmt.Sprintf("  %s -> %s%s\n", name, der, status)
    39  	}
    40  	return result
    41  }
    42  
    43  func (e *environment) clone() *environment {
    44  	vars := make(map[string]*deriv, len(e.vars))
    45  	taints := make(map[string]reason, len(e.taints))
    46  	for name := range e.vars {
    47  		vars[name] = e.vars[name].clone()
    48  	}
    49  	for name := range e.taints {
    50  		taints[name] = e.taints[name].clone()
    51  	}
    52  	return &environment{vars: vars, taints: taints}
    53  }
    54  
    55  func (e *environment) union(other *environment) *environment {
    56  	result := e.clone()
    57  	for name := range other.vars {
    58  		otherDerivs := other.vars[name]
    59  		result.vars[name].merge(otherDerivs)
    60  		// TODO(dustmop): union taints also
    61  	}
    62  	return result
    63  }
    64  
    65  func (e *environment) copyFrom(other *environment) {
    66  	for name := range other.vars {
    67  		e.vars[name] = other.vars[name]
    68  	}
    69  }
    70  
    71  func getKeys(m map[string]*deriv) []string {
    72  	keys := make([]string, 0, len(m))
    73  	for k := range m {
    74  		keys = append(keys, k)
    75  	}
    76  	sort.Strings(keys)
    77  	return keys
    78  }
    79  
    80  func (e *environment) markParams(params []string) {
    81  	for _, p := range params {
    82  		e.vars[p] = &deriv{inputs: []string{p}, param: true}
    83  	}
    84  }
    85  
    86  func (e *environment) assign(dest string, sources []string) {
    87  	result := []string{}
    88  	for _, src := range sources {
    89  		if src == sensitiveVarName {
    90  			// meta variable, don't try to resolve
    91  			result = append(result, src)
    92  			continue
    93  		}
    94  		if lookup, ok := e.vars[src]; ok {
    95  			result = append(result, lookup.inputs...)
    96  		} else {
    97  			result = append(result, src)
    98  		}
    99  	}
   100  	if e.vars[dest] == nil {
   101  		e.vars[dest] = &deriv{}
   102  	}
   103  	e.vars[dest].inputs = result
   104  }
   105  
   106  func (e *environment) isSecret(name string) bool {
   107  	if e.vars[name] == nil {
   108  		return false
   109  	}
   110  	for _, inp := range e.vars[name].inputs {
   111  		if inp == sensitiveVarName {
   112  			return true
   113  		}
   114  	}
   115  	return false
   116  }
   117  
   118  func (e *environment) taint(name string, reason reason) {
   119  	if lookup, ok := e.vars[name]; ok {
   120  		// variable used
   121  		for _, inp := range lookup.inputs {
   122  			e.taints[inp] = reason
   123  		}
   124  	} else {
   125  		// parameter used direcly
   126  		e.taints[name] = reason
   127  	}
   128  }
   129  
   130  func (e *environment) getHighSensitive(params []string) ([]bool, []reason) {
   131  	dangerousParams := make([]bool, len(params))
   132  	reasonParams := make([]reason, len(params))
   133  	for i, p := range params {
   134  		if reason, ok := e.taints[p]; ok {
   135  			dangerousParams[i] = true
   136  			reasonParams[i] = reason
   137  		}
   138  	}
   139  	return dangerousParams, reasonParams
   140  }
   141  
   142  type deriv struct {
   143  	inputs []string
   144  	param  bool
   145  }
   146  
   147  func (d *deriv) String() string {
   148  	return strings.Join(d.inputs, ",")
   149  }
   150  
   151  func (d *deriv) clone() *deriv {
   152  	inputs := make([]string, len(d.inputs))
   153  	for i, inp := range d.inputs {
   154  		inputs[i] = inp
   155  	}
   156  	return &deriv{inputs: inputs, param: d.param}
   157  }
   158  
   159  func (d *deriv) merge(other *deriv) {
   160  	for _, val := range other.inputs {
   161  		if !arrayContains(d.inputs, val) {
   162  			d.inputs = append(d.inputs, val)
   163  		}
   164  	}
   165  	if other.param {
   166  		d.param = true
   167  	}
   168  }
   169  
   170  func arrayContains(arr []string, val string) bool {
   171  	for _, elem := range arr {
   172  		if elem == val {
   173  			return true
   174  		}
   175  	}
   176  	return false
   177  }
   178  
   179  type reason struct {
   180  	lines []string
   181  }
   182  
   183  func (r reason) clone() reason {
   184  	lines := make([]string, len(r.lines))
   185  	for i, ln := range r.lines {
   186  		lines[i] = ln
   187  	}
   188  	return reason{lines: lines}
   189  }
   190  
   191  func makeReason(pos syntax.Position, currFunc, varName, invokeName, paramName string, prev reason) reason {
   192  	baseName := path.Base(pos.Filename())
   193  	text := fmt.Sprintf("%s:%d: %s passes %s to %s argument %s", baseName, pos.Line, currFunc, varName, invokeName, paramName)
   194  	return reason{
   195  		lines: append([]string{text}, prev.lines...),
   196  	}
   197  }
   198  
   199  func (r reason) String() string {
   200  	return strings.Join(r.lines, "\n")
   201  }