diff --git a/src/fuzz_introspector/frontends/frontend_go.py b/src/fuzz_introspector/frontends/frontend_go.py index be435bc28..4f6ed8ac1 100644 --- a/src/fuzz_introspector/frontends/frontend_go.py +++ b/src/fuzz_introspector/frontends/frontend_go.py @@ -222,6 +222,12 @@ def dump_module_logic(self, for func_def in functions_methods: func_def.extract_local_variable_type( self.functions_methods_map) + # Need a second pass because the processing may out of order + # That could affect some local variable types that are + # relying on other variables + func_def.extract_local_variable_type( + self.functions_methods_map) + func_def.extract_callsites(self.functions_methods_map) func_dict: dict[str, Any] = {} func_dict['functionName'] = func_def.function_name @@ -663,10 +669,15 @@ def _detect_variable_type( if target_name: return target_name - # TODO Handles the following type - # index_expression slice_expression - # type_assertion_expression type_conversion_expression - # type_instantiation_expression + # Index expression / Slice expression + elif child.type in ['index_expression', 'slice_expression']: + op = child.child_by_field_name('operand') + parent_type = self.var_map.get(op.text.decode()) + if parent_type: + if '[' in parent_type and ']' in parent_type: + return parent_type.rsplit(']', 1)[-1] + elif parent_type == 'string': + return 'uint8' # Other expression that need to recursive deeper # unary_expression binary_expression @@ -680,8 +691,6 @@ def extract_local_variable_type(self, all_funcs_meths: dict[str, 'FunctionMethod']): """Gets the local variable types of the function.""" - # TODO The handling of all kind of variable declaration approach is not done. - # There are some requires extensive search to determine a type. query = self.tree_sitter_lang.query('( var_declaration ) @vd') for _, exprs in query.captures(self.root).items(): diff --git a/src/test/data/source-code/go/test-project-4/fuzzer.go b/src/test/data/source-code/go/test-project-4/fuzzer.go new file mode 100644 index 000000000..d3ef7a0f0 --- /dev/null +++ b/src/test/data/source-code/go/test-project-4/fuzzer.go @@ -0,0 +1,109 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package structs + +import ( + "testing" + "fmt" + "strconv" +) + +type Person struct { + Name string + Age int +} + +func (p Person) Greet() string { + return fmt.Sprintf("Hello, my name is %s and I am %d years old.", p.Name, p.Age) +} + +func (p Person) Introduce() string { + return fmt.Sprintf("I am %s, a person of age %d.", p.Name, p.Age) +} + +func (p Person) Describe() string { + return fmt.Sprintf("Person: %s, Age: %d", p.Name, p.Age) +} + +type Dog struct { + Name string +} + +func (d Dog) Greet() string { + return fmt.Sprintf("Hello, my dog's name is %s.", d.Name) +} + +func (d Dog) Introduce() string { + return fmt.Sprintf("This is my dog, %s.", d.Name) +} + +func (d Dog) Describe() string { + return fmt.Sprintf("Dog: %s", d.Name) +} + +func NewDog(name string) Dog { + return Dog{Name: name} +} + +type Robot struct { + Model string +} + +func (r Robot) Greet() string { + return fmt.Sprintf("Hello, I am a robot of model %s.", r.Model) +} + +func (r Robot) Introduce() string { + return fmt.Sprintf("I am %s, a highly advanced robot.", r.Model) +} + +func (r Robot) Describe() string { + return fmt.Sprintf("Robot Model: %s", r.Model) +} + +func FuzzStructs(f *testing.F) { + f.Fuzz(func(t *testing.T, name string, ageString string, model string) { + age, err := strconv.Atoi(ageString) + if err != nil { + return + } + + personMap := map[int]Person{ + 0: {Name: "Default", Age: 0}, + 1: {Name: name, Age: age}, + } + p := personMap[1] + + var dogInterface interface{} = Dog{Name: name} + d, ok := dogInterface.(Dog) + if !ok { + return + } + + d := NewDog(name) + + robots := [3]Robot{ + {Model: "X-1000"}, + {Model: "R2-D2"}, + {Model: model}, + } + r := robots[0] + + _ = p.Greet() + _ = d.Introduce() + _ = r.Describe() + }) +} diff --git a/src/test/test_frontends_go.py b/src/test/test_frontends_go.py index b606ae14f..46ceff6ef 100644 --- a/src/test/test_frontends_go.py +++ b/src/test/test_frontends_go.py @@ -91,7 +91,21 @@ def test_tree_sitter_go_sample4(): # Project check harness = project.get_source_codes_with_harnesses() - assert len(harness) == 0 + assert len(harness) == 1 + + functions_reached = project.get_reachable_functions(harness[0].source_file, harness[0]) + + # Callsite check + assert 'strconv.Atoi' in functions_reached + assert 'Person.Greet' in functions_reached + assert 'Dog.Introduce' in functions_reached + assert 'Robot.Describe' in functions_reached + assert 'Person.Introduce' not in functions_reached + assert 'Person.Describe' not in functions_reached + assert 'Dog.Greet' not in functions_reached + assert 'Dog.Describe' not in functions_reached + assert 'Robot.Greet' not in functions_reached + assert 'Robot.Introduce' not in functions_reached def test_tree_sitter_go_sample5():