From 2c4674eb42119147b7094e7314fb9083bcf18329 Mon Sep 17 00:00:00 2001
From: Alexander Medvednikov <alexander@vlang.io>
Date: Tue, 2 Feb 2021 09:14:34 +0100
Subject: [PATCH] cgen: obfuscate functions

---
 cmd/tools/vfmt.v   |  4 ++--
 vlib/v/fmt/fmt.v   |  7 +++++--
 vlib/v/gen/cgen.v  | 43 +++++++++++++++++++++++++++++++++++++++++++
 vlib/v/gen/fn.v    | 30 ++++++++++++++++++++++++++++++
 vlib/v/pref/pref.v |  1 +
 5 files changed, 81 insertions(+), 4 deletions(-)

diff --git a/cmd/tools/vfmt.v b/cmd/tools/vfmt.v
index 90c5a9493b..fcf34ef32d 100644
--- a/cmd/tools/vfmt.v
+++ b/cmd/tools/vfmt.v
@@ -165,7 +165,7 @@ fn (foptions &FormatOptions) format_file(file string) {
 		parent: 0
 	})
 	// checker.check(file_ast)
-	formatted_content := fmt.fmt(file_ast, table, foptions.is_debug)
+	formatted_content := fmt.fmt(file_ast, table, prefs, foptions.is_debug)
 	file_name := os.file_name(file)
 	ulid := rand.ulid()
 	vfmt_output_path := os.join_path(vtmp_folder, 'vfmt_${ulid}_$file_name')
@@ -189,7 +189,7 @@ fn (foptions &FormatOptions) format_pipe() {
 		parent: 0
 	})
 	// checker.check(file_ast)
-	formatted_content := fmt.fmt(file_ast, table, foptions.is_debug)
+	formatted_content := fmt.fmt(file_ast, table, prefs, foptions.is_debug)
 	print(formatted_content)
 	if foptions.is_verbose {
 		eprintln('fmt.fmt worked and $formatted_content.len bytes were written to stdout.')
diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v
index c0abb83edf..9407aa48bf 100644
--- a/vlib/v/fmt/fmt.v
+++ b/vlib/v/fmt/fmt.v
@@ -7,6 +7,7 @@ import v.ast
 import v.table
 import strings
 import v.util
+import v.pref
 
 const (
 	bs      = '\\'
@@ -46,9 +47,10 @@ pub mut:
 	inside_lambda      bool
 	inside_const       bool
 	is_mbranch_expr    bool // math a { x...y { } }
+	pref               &pref.Preferences
 }
 
-pub fn fmt(file ast.File, table &table.Table, is_debug bool) string {
+pub fn fmt(file ast.File, table &table.Table, pref &pref.Preferences, is_debug bool) string {
 	mut f := Fmt{
 		out: strings.new_builder(1000)
 		out_imports: strings.new_builder(200)
@@ -56,6 +58,7 @@ pub fn fmt(file ast.File, table &table.Table, is_debug bool) string {
 		indent: 0
 		file: file
 		is_debug: is_debug
+		pref: pref
 	}
 	f.process_file_imports(file)
 	f.set_current_module_name('main')
@@ -323,7 +326,7 @@ pub fn (mut f Fmt) stmts(stmts []ast.Stmt) {
 	mut prev_stmt := if stmts.len > 0 { stmts[0] } else { ast.Stmt{} }
 	f.indent++
 	for stmt in stmts {
-		if f.should_insert_newline_before_stmt(stmt, prev_stmt) {
+		if !f.pref.building_v && f.should_insert_newline_before_stmt(stmt, prev_stmt) {
 			f.out.writeln('')
 		}
 		f.stmt(stmt)
diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v
index 63c06b9d4e..ecfa9852f5 100644
--- a/vlib/v/gen/cgen.v
+++ b/vlib/v/gen/cgen.v
@@ -148,6 +148,7 @@ mut:
 	timers             &util.Timers = util.new_timers(false)
 	force_main_console bool // true when [console] used on fn main()
 	as_cast_type_names map[string]string // table for type name lookup in runtime (for __as_cast)
+	obf_table          map[string]string
 }
 
 pub fn cgen(files []ast.File, table &table.Table, pref &pref.Preferences) string {
@@ -427,6 +428,29 @@ pub fn (mut g Gen) init() {
 	if g.pref.is_livemain || g.pref.is_liveshared {
 		g.generate_hotcode_reloading_declarations()
 	}
+	// Obfuscate only functions in the main module for now.
+	// Generate the obf_table.
+	if g.pref.obfuscate {
+		mut i := 0
+		// fns
+		for key, f in g.table.fns {
+			if f.mod != 'main' && key != 'main' { // !key.starts_with('main.') {
+				continue
+			}
+			g.obf_table[key] = '_f$i'
+			i++
+		}
+		// methods
+		for type_sym in g.table.types {
+			if type_sym.mod != 'main' {
+				continue
+			}
+			for method in type_sym.methods {
+				g.obf_table[type_sym.name + '.' + method.name] = '_f$i'
+				i++
+			}
+		}
+	}
 }
 
 pub fn (mut g Gen) finish() {
@@ -3816,6 +3840,14 @@ fn (mut g Gen) ident(node ast.Ident) {
 				}
 			}
 		}
+	} else if node_info is ast.IdentFn {
+		if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') {
+			key := node.name
+			g.write('/* obf identfn: $key */')
+			name = g.obf_table[key] or {
+				panic('cgen: obf name "$key" not found, this should never happen')
+			}
+		}
 	}
 	g.write(g.get_ternary_name(name))
 }
@@ -5688,6 +5720,17 @@ fn (mut g Gen) go_stmt(node ast.GoStmt, joinable bool) string {
 		name = fsym.name
 	}
 	name = util.no_dots(name)
+	if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') {
+		mut key := expr.name
+		if expr.is_method {
+			sym := g.table.get_type_symbol(expr.receiver_type)
+			key = sym.name + '.' + expr.name
+		}
+		g.write('/* obf go: $key */')
+		name = g.obf_table[key] or {
+			panic('cgen: obf name "$key" not found, this should never happen')
+		}
+	}
 	g.writeln('// go')
 	wrapper_struct_name := 'thread_arg_' + name
 	wrapper_fn_name := name + '_thread_wrapper'
diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v
index c6a95b2b89..6936cf65c5 100644
--- a/vlib/v/gen/fn.v
+++ b/vlib/v/gen/fn.v
@@ -78,6 +78,18 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl, skip bool) {
 			name += '_' + gen_name
 		}
 	}
+	if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__')
+		&& name != 'main__main'&& node.name != 'str' {
+		mut key := node.name
+		if node.is_method {
+			sym := g.table.get_type_symbol(node.receiver.typ)
+			key = sym.name + '.' + node.name
+		}
+		g.writeln('/* obf: $key */')
+		name = g.obf_table[key] or {
+			panic('cgen: fn_decl: obf name "$key" not found, this should never happen')
+		}
+	}
 	// if g.pref.show_cc && it.is_builtin {
 	// println(name)
 	// }
@@ -457,6 +469,16 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
 			name = 'map_keys_1'
 		}
 	}
+	if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__')
+		&& node.name != 'str' {
+		sym := g.table.get_type_symbol(node.receiver_type)
+		// key = g.cc_type2(node.receiver.typ) + '.' + node.name
+		key := sym.name + '.' + node.name
+		g.write('/* obf method call: $key */')
+		name = g.obf_table[key] or {
+			panic('cgen: obf name "$key" not found, this should never happen')
+		}
+	}
 	// Check if expression is: arr[a..b].clone(), arr[a..].clone()
 	// if so, then instead of calling array_clone(&array_slice(...))
 	// call array_clone_static(array_slice(...))
@@ -620,6 +642,14 @@ fn (mut g Gen) fn_call(node ast.CallExpr) {
 	} else {
 		name = c_name(name)
 	}
+	// Obfuscate only functions in the main module for now
+	if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') {
+		key := node.name
+		g.write('/* obf call: $key */')
+		name = g.obf_table[key] or {
+			panic('cgen: obf name "$key" not found, this should never happen')
+		}
+	}
 	for i, generic_type in node.generic_types {
 		// Using _T_ to differentiate between get<string> and get_string
 		// `foo<int>()` => `foo_T_int()`
diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v
index 81beda2619..a15b183f72 100644
--- a/vlib/v/pref/pref.v
+++ b/vlib/v/pref/pref.v
@@ -47,6 +47,7 @@ const (
 		'cflags', 'path']
 )
 
+[ref_only]
 pub struct Preferences {
 pub mut:
 	os          OS // the OS to compile for