You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
garble/internal/literals/proxy.go

245 lines
6.4 KiB
Go

package literals
import (
"go/ast"
"go/token"
mathrand "math/rand"
"strconv"
ah "mvdan.cc/garble/internal/asthelper"
)
const (
// minStructCount is the minimum number of proxyStructs initialized in the dispatcher.
minStructCount = 4
// maxStructCount is the maximum number of proxyStructs initialized in the dispatcher.
maxStructCount = 8
//minChildCount defines the minimum number of child elements that can be assigned to proxy structs.
minChildCount = 1
// maxChildCount defines the maximum number of child elements that can be assigned to proxy structs.
maxChildCount = 3
// minJunkValueCount defines the minimum number of junk values that can be added to a structure.
minJunkValueCount = 1
// maxJunkValueCount defines the maximum number of junk values that can be added to a structure.
maxJunkValueCount = 3
// minJunkArraySize defines the minimum size of a randomized byte array for junk data generation.
minJunkArraySize = 1
// maxJunkArraySize defines the maximum size of a randomized byte array for junk data generation.
maxJunkArraySize = 8
)
// proxyValue represents a named field with a type and value used in a proxy structure.
type proxyValue struct {
name string
typ, val ast.Expr
}
type proxyStruct struct {
typeName, name string
isPointer bool
values []*proxyValue
children []*proxyStruct
parent *proxyStruct
}
type proxyDispatcher struct {
rand *mathrand.Rand
nameFunc NameProviderFunc
root *proxyStruct
flattenStructs []*proxyStruct
}
func (d *proxyDispatcher) initialize() {
flattenStructs := make([]*proxyStruct, d.rand.Intn(maxStructCount-minStructCount)+minStructCount)
for i := range flattenStructs {
flattenStructs[i] = &proxyStruct{
typeName: d.nameFunc(d.rand, "proxyStructName"+strconv.Itoa(i)),
name: d.nameFunc(d.rand, "proxyStructFieldName"+strconv.Itoa(i)),
isPointer: d.rand.Intn(2) == 0,
}
}
root := &proxyStruct{
name: d.nameFunc(d.rand, "rootStructName"),
typeName: d.nameFunc(d.rand, "rootStructType"),
parent: nil,
}
d.root = root
unassigned := append([]*proxyStruct(nil), flattenStructs...)
queue := []*proxyStruct{root}
for len(unassigned) > 0 && len(queue) > 0 {
current := queue[0]
queue = queue[1:]
childCount := min(d.rand.Intn(maxChildCount-minChildCount)+minChildCount, len(unassigned))
for i := 0; i < childCount; i++ {
child := unassigned[0]
unassigned = unassigned[1:]
child.parent = current
current.children = append(current.children, child)
queue = append(queue, child)
}
}
d.flattenStructs = append(flattenStructs, root)
}
// buildPath creates an AST expression that represents a field access path
// from the root of a nested struct hierarchy to a specific field.
// Example: root.child.grandchild.fieldName
func buildPath(strct *proxyStruct, valueName string) ast.Expr {
var stack []*proxyStruct
for s := strct; s != nil; s = s.parent {
stack = append(stack, s)
}
var expr ast.Expr = ast.NewIdent(stack[len(stack)-1].name)
for i := len(stack) - 2; i >= 0; i-- {
expr = &ast.SelectorExpr{
X: expr,
Sel: ast.NewIdent(stack[i].name),
}
}
return &ast.SelectorExpr{
X: expr,
Sel: ast.NewIdent(valueName),
}
}
func (d *proxyDispatcher) HideValue(val, typ ast.Expr) ast.Expr {
if d.root == nil {
d.initialize()
}
strct := d.flattenStructs[d.rand.Intn(len(d.flattenStructs))]
valueName := d.nameFunc(d.rand, strct.name+"_"+strct.typeName+"_"+strconv.Itoa(len(strct.values)))
strct.values = append(strct.values, &proxyValue{
name: valueName,
typ: typ,
val: val,
})
return buildPath(strct, valueName)
}
func (d *proxyDispatcher) generateStructLiteral(s *proxyStruct) ast.Expr {
var fields []ast.Expr
for _, child := range s.children {
expr := d.generateStructLiteral(child)
if child.isPointer {
expr = &ast.UnaryExpr{
Op: token.AND,
X: expr,
}
}
fields = append(fields, &ast.KeyValueExpr{
Key: ast.NewIdent(child.name),
Value: expr,
})
}
for _, val := range s.values {
fields = append(fields, &ast.KeyValueExpr{
Key: ast.NewIdent(val.name),
Value: val.val,
})
}
d.rand.Shuffle(len(fields), func(i, j int) {
fields[i], fields[j] = fields[j], fields[i]
})
return &ast.CompositeLit{
Type: ast.NewIdent(s.typeName),
Elts: fields,
}
}
// junkValue generates and returns a proxyValue containing a randomized byte array of variable size
func (d *proxyDispatcher) junkValue() *proxyValue {
size := d.rand.Intn(maxJunkArraySize-minJunkArraySize+1) + minJunkArraySize
data := make([]byte, size)
d.rand.Read(data)
dummyDataExpr := &ast.CompositeLit{
Type: &ast.ArrayType{
Len: ah.IntLit(size),
Elt: ast.NewIdent("byte"),
},
Elts: ah.DataToArray(data).Elts,
}
return &proxyValue{
name: d.nameFunc(d.rand, "junkValue"),
typ: &ast.ArrayType{
Len: ah.IntLit(size),
Elt: ast.NewIdent("byte"),
},
val: dummyDataExpr,
}
}
func (d *proxyDispatcher) AddToFile(file *ast.File) {
if d.root == nil {
return
}
for _, strct := range d.flattenStructs {
dummyCount := d.rand.Intn(maxJunkValueCount-minJunkValueCount+1) + minJunkValueCount
for range dummyCount {
strct.values = append(strct.values, d.junkValue())
}
structType := &ast.StructType{Fields: &ast.FieldList{}}
for _, child := range strct.children {
var typ ast.Expr = ast.NewIdent(child.typeName)
if child.isPointer {
typ = &ast.StarExpr{X: typ}
}
structType.Fields.List = append(structType.Fields.List, &ast.Field{
Names: []*ast.Ident{ast.NewIdent(child.name)},
Type: typ,
})
}
for _, value := range strct.values {
structType.Fields.List = append(structType.Fields.List, &ast.Field{
Names: []*ast.Ident{ast.NewIdent(value.name)},
Type: value.typ,
})
}
d.rand.Shuffle(len(structType.Fields.List), func(i, j int) {
structType.Fields.List[i], structType.Fields.List[j] = structType.Fields.List[j], structType.Fields.List[i]
})
file.Decls = append(file.Decls, &ast.GenDecl{
Tok: token.TYPE,
Specs: []ast.Spec{&ast.TypeSpec{
Name: ast.NewIdent(strct.typeName),
Type: structType,
}},
})
}
file.Decls = append(file.Decls, &ast.GenDecl{
Tok: token.VAR,
Specs: []ast.Spec{&ast.ValueSpec{
Names: []*ast.Ident{ast.NewIdent(d.root.name)},
Values: []ast.Expr{d.generateStructLiteral(d.root)},
}},
})
}
func newProxyDispatcher(rand *mathrand.Rand, nameFunc NameProviderFunc) *proxyDispatcher {
return &proxyDispatcher{
rand: rand,
nameFunc: nameFunc,
}
}