Add float, int, and boolean literal obfuscation.

Add ast helper functions to reduce ast footprint.

Add binsubfloat and binsubint functions for testing.

Fixes #55.
pull/76/head
lu4p 4 years ago committed by Daniel Martí
parent 3ea6fda837
commit 50d24cdf51

@ -0,0 +1,71 @@
package literals
import (
"go/ast"
"go/token"
"strconv"
)
func ident(name string) *ast.Ident {
return &ast.Ident{Name: name}
}
func intLiteral(value string) *ast.BasicLit {
return &ast.BasicLit{
Kind: token.INT,
Value: value,
}
}
// name[index]
func indexExpr(name string, index ast.Expr) *ast.IndexExpr {
return &ast.IndexExpr{
X: ident(name),
Index: index,
}
}
// fun(arg)
func callExpr(fun ast.Expr, arg ast.Expr) *ast.CallExpr {
var args []ast.Expr
if arg != nil {
args = []ast.Expr{arg}
}
return &ast.CallExpr{
Fun: fun,
Args: args,
}
}
// func() resultType {block}()
func lambdaCall(resultType ast.Expr, block *ast.BlockStmt) *ast.CallExpr {
funcLit := &ast.FuncLit{
Type: &ast.FuncType{
Params: &ast.FieldList{},
Results: &ast.FieldList{
List: []*ast.Field{
{Type: resultType},
},
},
},
Body: block,
}
return callExpr(funcLit, nil)
}
// return result
func returnStmt(results ...ast.Expr) *ast.ReturnStmt {
return &ast.ReturnStmt{
Results: results,
}
}
// _ = data[pos]
func boundsCheckData(pos int) *ast.AssignStmt {
posStr := strconv.Itoa(pos)
return &ast.AssignStmt{
Lhs: []ast.Expr{ident("_")},
Tok: token.ASSIGN,
Rhs: []ast.Expr{indexExpr("data", intLiteral(posStr))},
}
}

@ -11,36 +11,22 @@ import (
"golang.org/x/tools/go/ast/astutil"
)
func callExpr(resultType ast.Expr, block *ast.BlockStmt) *ast.CallExpr {
return &ast.CallExpr{Fun: &ast.FuncLit{
Type: &ast.FuncType{
Params: &ast.FieldList{},
Results: &ast.FieldList{
List: []*ast.Field{
{Type: resultType},
},
},
},
Body: block,
}}
}
var (
usesUnsafe bool
universalTrue = types.Universe.Lookup("true")
universalFalse = types.Universe.Lookup("false")
)
func randObfuscator() obfuscator {
randPos := mathrand.Intn(len(obfuscators))
return obfuscators[randPos]
}
func returnStmt(result ast.Expr) *ast.ReturnStmt {
return &ast.ReturnStmt{
Results: []ast.Expr{result},
}
}
// Obfuscate replace literals with obfuscated lambda functions
func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blacklist map[types.Object]struct{}) []*ast.File {
pre := func(cursor *astutil.Cursor) bool {
switch x := cursor.Node().(type) {
switch x := cursor.Node().(type) {
case *ast.GenDecl:
if x.Tok != token.CONST {
return true
@ -84,6 +70,10 @@ func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blackli
case *ast.CompositeLit:
byteType := types.Universe.Lookup("byte").Type()
if len(x.Elts) == 0 {
return true
}
switch y := info.TypeOf(x.Type).(type) {
case *types.Array:
if y.Elem() != byteType {
@ -112,7 +102,7 @@ func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blackli
return true
}
var data []byte
data := make([]byte, 0, len(x.Elts))
for _, el := range x.Elts {
lit, ok := el.(*ast.BasicLit)
@ -139,6 +129,8 @@ func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blackli
}
switch x.Kind {
case token.FLOAT, token.INT:
obfuscateNumberLiteral(cursor, info)
case token.STRING:
typeInfo := info.TypeOf(x)
if typeInfo != types.Typ[types.String] && typeInfo != types.Typ[types.UntypedString] {
@ -149,15 +141,40 @@ func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blackli
panic(fmt.Sprintf("cannot unquote string: %v", err))
}
if len(value) == 0 {
return true
}
cursor.Replace(obfuscateString(value))
}
case *ast.UnaryExpr:
switch cursor.Name() {
case "Values", "Rhs", "Value", "Args", "X":
default:
return true // we don't want to obfuscate imports etc.
}
obfuscateNumberLiteral(cursor, info)
case *ast.Ident:
obj := info.ObjectOf(x)
if obj == nil {
return true
}
if obj == universalTrue || obj == universalFalse {
cursor.Replace(obfuscateBool(x.Name == "true"))
}
}
return true
}
for i := range files {
usesUnsafe = false
files[i] = astutil.Apply(files[i], pre, post).(*ast.File)
if usesUnsafe {
astutil.AddImport(fset, files[i], "unsafe")
}
}
return files
}
@ -165,23 +182,17 @@ func Obfuscate(files []*ast.File, info *types.Info, fset *token.FileSet, blackli
func obfuscateString(data string) *ast.CallExpr {
obfuscator := randObfuscator()
block := obfuscator.obfuscate([]byte(data))
block.List = append(block.List, &ast.ReturnStmt{
Results: []ast.Expr{&ast.CallExpr{
Fun: &ast.Ident{Name: "string"},
Args: []ast.Expr{&ast.Ident{Name: "data"}},
}},
})
return callExpr(&ast.Ident{Name: "string"}, block)
block.List = append(block.List, returnStmt(callExpr(ident("string"), ident("data"))))
return lambdaCall(ident("string"), block)
}
func obfuscateByteSlice(data []byte) *ast.CallExpr {
obfuscator := randObfuscator()
block := obfuscator.obfuscate(data)
block.List = append(block.List, &ast.ReturnStmt{
Results: []ast.Expr{&ast.Ident{Name: "data"}},
})
return callExpr(&ast.ArrayType{Elt: &ast.Ident{Name: "byte"}}, block)
block.List = append(block.List, returnStmt(ident("data")))
return lambdaCall(&ast.ArrayType{Elt: ident("byte")}, block)
}
func obfuscateByteArray(data []byte, length int64) *ast.CallExpr {
@ -189,11 +200,8 @@ func obfuscateByteArray(data []byte, length int64) *ast.CallExpr {
block := obfuscator.obfuscate(data)
arrayType := &ast.ArrayType{
Len: &ast.BasicLit{
Kind: token.INT,
Value: strconv.Itoa(int(length)),
},
Elt: &ast.Ident{Name: "byte"},
Len: intLiteral(strconv.Itoa(int(length))),
Elt: ident("byte"),
}
sliceToArray := []ast.Stmt{
@ -201,37 +209,44 @@ func obfuscateByteArray(data []byte, length int64) *ast.CallExpr {
Decl: &ast.GenDecl{
Tok: token.VAR,
Specs: []ast.Spec{&ast.ValueSpec{
Names: []*ast.Ident{{Name: "newdata"}},
Names: []*ast.Ident{ident("newdata")},
Type: arrayType,
}},
},
},
&ast.RangeStmt{
Key: &ast.Ident{Name: "i"},
Key: ident("i"),
Tok: token.DEFINE,
X: &ast.Ident{Name: "newdata"},
X: ident("newdata"),
Body: &ast.BlockStmt{List: []ast.Stmt{
&ast.AssignStmt{
Lhs: []ast.Expr{&ast.IndexExpr{
X: &ast.Ident{Name: "newdata"},
Index: &ast.Ident{Name: "i"},
}},
Lhs: []ast.Expr{indexExpr("newdata", ident("i"))},
Tok: token.ASSIGN,
Rhs: []ast.Expr{&ast.IndexExpr{
X: &ast.Ident{Name: "data"},
Index: &ast.Ident{Name: "i"},
}},
Rhs: []ast.Expr{indexExpr("data", ident("i"))},
},
}},
},
&ast.ReturnStmt{Results: []ast.Expr{
&ast.Ident{Name: "newdata"},
}},
returnStmt(ident("newdata")),
}
block.List = append(block.List, sliceToArray...)
return callExpr(arrayType, block)
return lambdaCall(arrayType, block)
}
func obfuscateBool(data bool) *ast.BinaryExpr {
var dataUint64 uint64 = 0
if data {
dataUint64 = 1
}
intType := intTypes[types.Typ[types.Uint8]]
return &ast.BinaryExpr{
X: genObfuscateInt(dataUint64, intType),
Op: token.EQL,
Y: intLiteral("1"),
}
}
// ConstBlacklist blacklist identifieres used in constant expressions

@ -0,0 +1,229 @@
package literals
import (
"encoding/binary"
"errors"
"go/ast"
"go/token"
"go/types"
"math"
"reflect"
"strconv"
"golang.org/x/tools/go/ast/astutil"
)
var intTypes = map[types.Type]reflect.Type{
types.Typ[types.UntypedInt]: reflect.TypeOf(int(0)),
types.Typ[types.Int]: reflect.TypeOf(int(0)),
types.Typ[types.Int8]: reflect.TypeOf(int8(0)),
types.Typ[types.Int16]: reflect.TypeOf(int16(0)),
types.Typ[types.Int32]: reflect.TypeOf(int32(0)),
types.Typ[types.Int64]: reflect.TypeOf(int64(0)),
types.Typ[types.Uint]: reflect.TypeOf(uint(0)),
types.Typ[types.Uint8]: reflect.TypeOf(uint8(0)),
types.Typ[types.Uint16]: reflect.TypeOf(uint16(0)),
types.Typ[types.Uint32]: reflect.TypeOf(uint32(0)),
types.Typ[types.Uint64]: reflect.TypeOf(uint64(0)),
types.Typ[types.Uintptr]: reflect.TypeOf(uintptr(0)),
}
func obfuscateNumberLiteral(cursor *astutil.Cursor, info *types.Info) error {
var (
call *ast.CallExpr
basic *ast.BasicLit
ok bool
typeInfo types.Type
)
sign := ""
node := cursor.Node()
switch x := node.(type) {
case *ast.UnaryExpr:
basic, ok = x.X.(*ast.BasicLit)
if !ok {
return errors.New("UnaryExpr doesn't contain basic literal")
}
typeInfo = info.TypeOf(x)
if x.Op != token.SUB {
return errors.New("UnaryExpr has a non SUB token")
}
sign = "-"
switch y := cursor.Parent().(type) {
case *ast.ValueSpec:
tempInfo := info.TypeOf(y.Type)
if tempInfo != nil {
typeInfo = tempInfo
}
}
case *ast.BasicLit:
basic = x
typeInfo = info.TypeOf(x)
switch typeInfo {
case types.Typ[types.UntypedFloat], types.Typ[types.UntypedInt]:
// The post calls from astutil.Apply can be out of order,
// this guards against the case where the ast.BasicLit is inside an ast.UnaryExpr
// and the BasicLit gets evaluated before the UnaryExpr
if _, ok := cursor.Parent().(*ast.UnaryExpr); ok {
return nil
}
}
default:
return errors.New("Wrong node Type")
}
strValue := sign + basic.Value
switch typeInfo {
case types.Typ[types.Float32]:
fV, err := strconv.ParseFloat(strValue, 32)
if err != nil {
return err
}
call = genObfuscateFloat(float32(fV))
case types.Typ[types.Float64], types.Typ[types.UntypedFloat]:
fV, err := strconv.ParseFloat(strValue, 64)
if err != nil {
return err
}
call = genObfuscateFloat(fV)
}
if call != nil {
cursor.Replace(call)
return nil
}
intValue, err := strconv.ParseInt(strValue, 0, 64)
if err != nil {
return err
}
intType, ok := intTypes[typeInfo]
if !ok {
return errors.New("Wrong type")
}
call = genObfuscateInt(uint64(intValue), intType)
cursor.Replace(call)
return nil
}
func bytesToUint(bits int) ast.Expr {
bytes := bits / 8
bitsStr := strconv.Itoa(bits)
var expr ast.Expr
for i := 0; i < bytes; i++ {
posStr := strconv.Itoa(i)
if i == 0 {
expr = callExpr(ident("uint"+bitsStr), indexExpr("data", intLiteral(posStr)))
continue
}
shiftValue := strconv.Itoa(i * 8)
expr = &ast.BinaryExpr{
X: expr,
Op: token.OR,
Y: &ast.BinaryExpr{
X: callExpr(ident("uint"+bitsStr), indexExpr("data", intLiteral(posStr))),
Op: token.SHL,
Y: intLiteral(shiftValue),
},
}
}
return expr
}
func genObfuscateInt(data uint64, typeInfo reflect.Type) *ast.CallExpr {
obfuscator := randObfuscator()
bitsize := typeInfo.Bits()
bitSizeStr := strconv.Itoa(bitsize)
byteSize := bitsize / 8
b := make([]byte, byteSize)
switch bitsize {
case 8:
b = []byte{uint8(data)}
case 16:
binary.LittleEndian.PutUint16(b, uint16(data))
case 32:
binary.LittleEndian.PutUint32(b, uint32(data))
case 64:
binary.LittleEndian.PutUint64(b, uint64(data))
default:
panic("data has the wrong length " + bitSizeStr)
}
block := obfuscator.obfuscate(b)
convertExpr := bytesToUint(bitsize)
block.List = append(block.List, boundsCheckData(byteSize-1), returnStmt(callExpr(ident(typeInfo.Name()), convertExpr)))
return lambdaCall(ident(typeInfo.Name()), block)
}
func uintToFloat(uintExpr *ast.CallExpr, typeStr string) *ast.CallExpr {
usesUnsafe = true
convert := &ast.StarExpr{
X: callExpr(
&ast.ParenExpr{
X: &ast.StarExpr{X: ident(typeStr)},
},
callExpr(
&ast.SelectorExpr{
X: ident("unsafe"),
Sel: ident("Pointer"),
},
&ast.UnaryExpr{
Op: token.AND,
X: ident("result"),
},
),
),
}
block := &ast.BlockStmt{List: []ast.Stmt{
&ast.AssignStmt{
Lhs: []ast.Expr{&ast.Ident{Name: "result"}},
Tok: token.DEFINE,
Rhs: []ast.Expr{uintExpr},
},
returnStmt(convert),
}}
return lambdaCall(ident(typeStr), block)
}
func genObfuscateFloat(data interface{}) *ast.CallExpr {
var (
b uint64
typeStr string
intType reflect.Type
)
switch x := data.(type) {
case float32:
intType = intTypes[types.Typ[types.Uint32]]
typeStr = "float32"
b = uint64(math.Float32bits(x))
case float64:
intType = intTypes[types.Typ[types.Uint64]]
typeStr = "float64"
b = math.Float64bits(x)
default:
panic("data has the wrong type")
}
return uintToFloat(genObfuscateInt(b, intType), typeStr)
}

@ -20,34 +20,28 @@ func (x xor) obfuscate(data []byte) *ast.BlockStmt {
return &ast.BlockStmt{List: []ast.Stmt{
&ast.AssignStmt{
Lhs: []ast.Expr{&ast.Ident{Name: "key"}},
Lhs: []ast.Expr{ident("key")},
Tok: token.DEFINE,
Rhs: []ast.Expr{dataToByteSlice(key)},
},
&ast.AssignStmt{
Lhs: []ast.Expr{&ast.Ident{Name: "data"}},
Lhs: []ast.Expr{ident("data")},
Tok: token.DEFINE,
Rhs: []ast.Expr{dataToByteSlice(data)},
},
&ast.RangeStmt{
Key: &ast.Ident{Name: "i"},
Value: &ast.Ident{Name: "b"},
Key: ident("i"),
Value: ident("b"),
Tok: token.DEFINE,
X: &ast.Ident{Name: "key"},
X: ident("key"),
Body: &ast.BlockStmt{List: []ast.Stmt{
&ast.AssignStmt{
Lhs: []ast.Expr{&ast.IndexExpr{
X: &ast.Ident{Name: "data"},
Index: &ast.Ident{Name: "i"},
}},
Lhs: []ast.Expr{indexExpr("data", ident("i"))},
Tok: token.ASSIGN,
Rhs: []ast.Expr{&ast.BinaryExpr{
X: &ast.IndexExpr{
X: &ast.Ident{Name: "data"},
Index: &ast.Ident{Name: "i"},
},
X: indexExpr("data", ident("i")),
Op: token.XOR,
Y: &ast.Ident{Name: "b"},
Y: ident("b"),
}},
},
}},

@ -4,15 +4,19 @@
package main
import (
"encoding/binary"
"flag"
"fmt"
"io"
"math"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
"time"
"github.com/rogpeppe/go-internal/goproxytest"
"github.com/rogpeppe/go-internal/gotooltest"
@ -73,8 +77,10 @@ func TestScripts(t *testing.T) {
return nil
},
Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){
"binsubstr": binsubstr,
"bincmp": bincmp,
"binsubstr": binsubstr,
"bincmp": bincmp,
"binsubint": binsubint,
"binsubfloat": binsubfloat,
},
UpdateScripts: *update,
}
@ -101,11 +107,35 @@ func copyFile(from, to string) error {
return err
}
type binaryCache struct {
name string
modtime time.Time
content string
}
var cachedBinary binaryCache
func readFile(ts *testscript.TestScript, file string) string {
file = ts.MkAbs(file)
info, err := os.Stat(file)
if err != nil {
ts.Fatalf("%v", err)
}
if cachedBinary.modtime == info.ModTime() && cachedBinary.name == file {
return cachedBinary.content
}
cachedBinary.name = file
cachedBinary.modtime = info.ModTime()
cachedBinary.content = ts.ReadFile(file)
return cachedBinary.content
}
func binsubstr(ts *testscript.TestScript, neg bool, args []string) {
if len(args) < 2 {
ts.Fatalf("usage: binsubstr file substr...")
}
data := ts.ReadFile(args[0])
data := readFile(ts, args[0])
var failed []string
for _, substr := range args[1:] {
match := strings.Contains(data, substr)
@ -122,6 +152,73 @@ func binsubstr(ts *testscript.TestScript, neg bool, args []string) {
}
}
func binsubint(ts *testscript.TestScript, neg bool, args []string) {
if len(args) < 2 {
ts.Fatalf("usage: binsubint file subint...")
}
data := readFile(ts, args[0])
var failed []string
for _, subIntStr := range args[1:] {
subInt, err := strconv.Atoi(subIntStr)
if err != nil {
ts.Fatalf("%v", err)
}
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(subInt))
match := strings.Contains(data, string(b))
if !match {
binary.BigEndian.PutUint64(b, uint64(subInt))
match = strings.Contains(data, string(b))
}
if match && neg {
failed = append(failed, subIntStr)
} else if !match && !neg {
failed = append(failed, subIntStr)
}
}
if len(failed) > 0 && neg {
ts.Fatalf("unexpected match for %s in %s", failed, args[0])
} else if len(failed) > 0 {
ts.Fatalf("expected match for %s in %s", failed, args[0])
}
}
func binsubfloat(ts *testscript.TestScript, neg bool, args []string) {
if len(args) < 2 {
ts.Fatalf("usage: binsubint file binsubfloat...")
}
data := readFile(ts, args[0])
var failed []string
for _, subFloatStr := range args[1:] {
subFloat, err := strconv.ParseFloat(subFloatStr, 64)
if err != nil {
ts.Fatalf("%v", err)
}
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, math.Float64bits(subFloat))
match := strings.Contains(data, string(b))
if !match {
binary.BigEndian.PutUint64(b, math.Float64bits(subFloat))
match = strings.Contains(data, string(b))
}
if match && neg {
failed = append(failed, subFloatStr)
} else if !match && !neg {
failed = append(failed, subFloatStr)
}
}
if len(failed) > 0 && neg {
ts.Fatalf("unexpected match for %s in %s", failed, args[0])
} else if len(failed) > 0 {
ts.Fatalf("expected match for %s in %s", failed, args[0])
}
}
func bincmp(ts *testscript.TestScript, neg bool, args []string) {
if len(args) != 2 {
ts.Fatalf("usage: bincmp file1 file2")

@ -1,13 +1,21 @@
go run .
go build
exec ./main$exe
binsubstr main$exe 'Lorem' 'dolor' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate' 'stringTypeField String'
binsubint main$exe '-7081390804778629748' '-301627827188279046' '7679634459002713443'
binsubfloat main$exe '3684433217126772357.33' '-9015867427900753906'
cmp stderr main.stderr
cp stderr normal.stderr
garble -literals build
exec ./main$exe
cmp stderr main.stderr
cmp stderr normal.stderr
! binsubstr main$exe 'garbleDecrypt' 'Lorem' 'ipsum' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate' 'stringTypeField String' 'stringTypeStruct'
! binsubstr main$exe 'garbleDecrypt' 'Lorem' 'dolor' 'first assign' 'second assign' 'First Line' 'Second Line' 'map value' 'to obfuscate' 'also obfuscate' 'stringTypeField String'
binsubstr main$exe 'also skip this' 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap1 key' 'testMap2 key' 'testMap3 key' 'testMap1 value' 'testMap3 value' 'testMap1 new value' 'testMap3 new value' 'stringType func param' 'stringType return' 'skip untyped const'
binsubstr main$exe 'Skip this block' 'also skip this' 'skip typed const' 'skip typed var' 'skip typed var assign' 'stringTypeField strType' 'stringType lambda func return' 'testMap1 key' 'testMap2 key' 'testMap3 key' 'testMap1 value' 'testMap3 value' 'testMap1 new value' 'testMap3 new value' 'stringType func param' 'stringType return' 'skip untyped const'
! binsubint main$exe '-7081390804778629748' '-301627827188279046' '7679634459002713443'
! binsubfloat main$exe '3684433217126772357.33' '-9015867427900753906'
[short] stop # checking that the build is reproducible is slow
# Also check that the binary is reproducible.
@ -35,6 +43,8 @@ Second Line`
const (
i = 1
boolean = true
skip1 = "Skip this block"
)
const (
@ -82,11 +92,13 @@ func main() {
testMap["map key"] = "new value"
println(testMap["map key"])
println("another literal")
println(skip2)
println(skip1, skip2)
println(i, foo, bar)
typedTest()
constantTest()
byteTest()
numTest()
boolTest()
}
type stringType string
@ -196,6 +208,80 @@ func stringTypeFunc(s stringType) stringType {
return "stringType return" // skip
}
func numTest() {
const a = 1 // skip
const b = a + 2 // skip
const c = 2824583991413579605
d := 4
var e = 5
var f int
f = 3735714531481032066
println(a, b, c, d, e, f)
var (
untypedInt = -7081390804778629760 + 12
intVar int = -301627827188279046
int8Var int8 = -122.0
int16Var int16 = 3534
int32Var int32 = 333453534
int64Var int64 = 4568766098255857483
uintVar uint = 7679634459002713443
uint8Var uint8 = 34
uint16Var uint16 = 3534
uint32Var uint32 = 333453534
uint64Var uint64 = 5490982829161518439
uintptrVar uintptr = 7364326871810921708
untypedFloat = 3684433217126772357.33
floatVar float64 = -9015867427900753906
floatVar32 float32 = 6338507605633
complexVar64 complex64 = -435453453534 // skip
complexVar128 complex128 = 1 + 4i // skip
underscoreInt = 1_3_3_7
underscoreFloat = 1_3_3_7.0
hexInt = 0x1337 // skip
hexFloat = 0x1337p0 // skip
octalInt = 0o1337 // skip
octalFloat = 0o1337 // skip
)
floatShort := -435453453534.0
println(untypedInt, intVar, int8Var, int16Var, int32Var, int64Var)
println(uintVar, uint8Var, uint16Var, uint32Var, uint64Var, uintptrVar)
println(untypedFloat, floatVar, floatShort, floatVar32)
println(complexVar64, complexVar128)
println(underscoreInt, underscoreFloat, hexInt, hexFloat, octalInt, octalFloat)
}
func boolTest() {
const a = true // skip
const b = false == a // skip
const c bool = false
d := true
var e = true
var f bool
f = false
println(a, b, c, d, e, f)
}
-- main.stderr --
Lorem true
First Line
@ -207,7 +293,7 @@ second assign
to obfuscate also obfuscate
new value
another literal
also skip this
Skip this block also skip this
1 0 1
skip untyped const
skip typed const skip typed var skip typed var assign
@ -222,3 +308,10 @@ foo
12, 13,
12, 13,
12, 13, 0, 0,
1 3 2824583991413579605 4 5 3735714531481032066
-7081390804778629748 -301627827188279046 -122 3534 333453534 4568766098255857483
7679634459002713443 34 3534 333453534 5490982829161518439 7364326871810921708
+3.684433e+018 -9.015867e+018 -4.354535e+011 +6.338508e+012
(-4.354535e+011+0.000000e+000i) (+1.000000e+000+4.000000e+000i)
1337 +1.337000e+003 4919 +4.919000e+003 735 735
true false false true true false

Loading…
Cancel
Save