1// Copyright 2017 Istio Authors
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
15package interpreter
16
17import (
18	"reflect"
19	"time"
20
21	"istio.io/istio/mixer/pkg/il"
22)
23
24// Extern represents an external, native function that is callable from within the interpreter,
25// during program execution.
26type Extern struct {
27	name       string
28	paramTypes []il.Type
29	returnType il.Type
30
31	v reflect.Value
32}
33
34// ExternFromFn creates a new, reflection based Extern, based on the given function. It panics if the
35// function signature is incompatible to be an extern.
36//
37// A function can be extern under the following conditions:
38// - Input parameter types are one of the supported types: string, bool, int64, float64, map[string]string.
39// - The return types can be:
40//   - none                                 (i.e. func (...) {...})
41//   - a supported type                     (i.e. func (...) string {...})
42//   - error                                (i.e. func (...) error {...})
43//   - supported type and error              (i.e. func (...) string, error {...})
44//
45func ExternFromFn(name string, fn interface{}) Extern {
46	t := reflect.TypeOf(fn)
47	if t.Kind() != reflect.Func {
48		panic("interpreter.ExternFromFn: not a function")
49	}
50
51	// Validate and calculate return types.
52	iErr := reflect.TypeOf((*error)(nil)).Elem()
53	returnType := il.Void
54	switch t.NumOut() {
55	case 0:
56
57	case 1:
58		if !t.Out(0).Implements(iErr) {
59			returnType = ilType(t.Out(0))
60		}
61
62	case 2:
63		returnType = ilType(t.Out(0))
64		if !t.Out(1).Implements(iErr) {
65			panic("interpreter.ExternFromFn: the second return value is not an error")
66		}
67
68	default:
69		panic("interpreter.ExternFromFn: more than two return values are not allowed")
70	}
71
72	if returnType == il.Unknown {
73		panic("interpreter.ExternFromFn: incompatible return type")
74	}
75
76	// Calculate parameter types.
77	paramTypes := make([]il.Type, t.NumIn())
78	for i := 0; i < t.NumIn(); i++ {
79		pt := t.In(i)
80		ilt := ilType(pt)
81		if ilt == il.Unknown {
82			panic("interpreter.ExternFromFn: incompatible parameter type")
83		}
84		paramTypes[i] = ilt
85	}
86
87	v := reflect.ValueOf(fn)
88
89	return Extern{
90		name:       name,
91		paramTypes: paramTypes,
92		returnType: returnType,
93		v:          v,
94	}
95}
96
97// ilType maps the Go reflected type to its IL counterpart.
98func ilType(t reflect.Type) il.Type {
99	switch t.Kind() {
100	case reflect.String:
101		return il.String
102	case reflect.Bool:
103		return il.Bool
104	case reflect.Int64:
105		if t.Name() == "Duration" {
106			return il.Duration
107		}
108		return il.Integer
109	case reflect.Float64:
110		return il.Double
111	case reflect.Slice:
112		if t.Elem().Kind() == reflect.Uint8 {
113			return il.Interface
114		}
115	case reflect.Struct:
116		switch t.Name() {
117		case "Time":
118			return il.Interface
119		case "StringMap":
120			return il.Interface
121		}
122	case reflect.Interface:
123		if t.Name() == "StringMap" {
124			return il.Interface
125		}
126	}
127
128	panic("Unmapped go type: " + t.Name() + " " + t.String() + " kind:" + t.Kind().String())
129}
130
131// invoke calls the extern function via reflection, using the interpreter's calling convention.
132// The parameters are read from the stack and get converted to Go values and the extern function
133// gets invoked. When the call completes, the return value, if any, gets converted back to the IL
134// type and pushed on to the stack. If the extern returns an error as one of the return values,
135// then the error is checked and raised in the IL if it is not nil.
136//
137// The function returns two uint32 values in the push order (i.e. first uint32 to be pushed on to
138// the stack first).
139func (e Extern) invoke(_ *il.StringTable, heap []interface{}, hp *uint32, stack []uint32, sp uint32) (uint32, uint32, error) {
140
141	// Convert the parameters on stack to reflect.Values.
142	ins := make([]reflect.Value, len(e.paramTypes))
143
144	// ap is the index to the beginning of the arguments.
145	ap := sp - typesStackAllocSize(e.paramTypes)
146	for i := 0; i < len(e.paramTypes); i++ {
147
148		switch e.paramTypes[i] {
149		case il.String:
150			str := heap[stack[ap]].(string)
151			ins[i] = reflect.ValueOf(str)
152
153		case il.Bool:
154			b := stack[ap] != 0
155			ins[i] = reflect.ValueOf(b)
156
157		case il.Integer:
158			iv := il.ByteCodeToInteger(stack[ap+1], stack[ap])
159			ins[i] = reflect.ValueOf(iv)
160
161		case il.Duration:
162			iv := il.ByteCodeToInteger(stack[ap+1], stack[ap])
163			ins[i] = reflect.ValueOf(time.Duration(iv))
164
165		case il.Double:
166			d := il.ByteCodeToDouble(stack[ap+1], stack[ap])
167			ins[i] = reflect.ValueOf(d)
168
169		case il.Interface:
170			r := heap[stack[ap]]
171			ins[i] = reflect.ValueOf(r)
172
173		default:
174			panic("interpreter.Extern.invoke: unrecognized parameter type")
175		}
176
177		ap += typeStackAllocSize(e.paramTypes[i])
178	}
179
180	// Perform the actual invocation through reflection.
181	outs := e.v.Call(ins)
182
183	// Convert the output values back to IL.
184	var rv reflect.Value
185	switch len(outs) {
186	case 1:
187		if e.returnType != il.Void {
188			rv = outs[0]
189			break
190		}
191		// If there is 1 return value in Go-space, but we expect the return type of the function to be
192		// Void in IL, then interpret the value as error.
193		if i := outs[0].Interface(); i != nil {
194			return 0, 0, i.(error)
195		}
196	case 2:
197		// If there are 2 return values in Go-space, interpret the first one as actual return value,
198		// and the second one as error.
199		rv = outs[0]
200		if i := outs[1].Interface(); i != nil {
201			return 0, 0, i.(error)
202		}
203	}
204
205	// Map the return value back to IL
206	switch e.returnType {
207	case il.String:
208		str := rv.String()
209		heap[*hp] = str
210		*hp++
211		return *hp - 1, 0, nil
212
213	case il.Bool:
214		if rv.Bool() {
215			return 1, 0, nil
216		}
217		return 0, 0, nil
218
219	case il.Integer, il.Duration:
220		i := rv.Int()
221		o1, o2 := il.IntegerToByteCode(i)
222		return o2, o1, nil
223
224	case il.Double:
225		d := rv.Float()
226		o1, o2 := il.DoubleToByteCode(d)
227		return o2, o1, nil
228
229	case il.Interface:
230		// TODO(ozben): We should single-instance the values, as they are prone to mutation.
231		r := rv.Interface()
232		heap[*hp] = r
233		*hp++
234		return *hp - 1, 0, nil
235
236	case il.Void:
237		return 0, 0, nil
238
239	default:
240		panic("interpreter.Extern.invoke: unrecognized return type")
241	}
242}
243