fix: Code review round 2 — race conditions, dead code, hardcoded paths

CRITICAL fixes:
- #1 vm.go: Mutex on libModules/dynamicFuncs global slices
  RegisterLibModule/RegisterDynamicFunc now thread-safe
  RegisterLibModules copies under lock, clears, releases
- #4 shutdown.go: Signal handler goroutine leak fixed
  Uses done channel + select for clean exit on normal shutdown

HIGH fixes:
- #7-8 gobridge.go: Remove dead if/else branches (both identical)
- #13-14 main.go: Remove hardcoded /mnt/d/harbour-core paths
  Use HB_INC env var + standard /usr/local/include/harbour only
- #15 main.go: Remove unused frbModSeq variable

MEDIUM fixes:
- #22 expr.go: Remove unused parts variable in parseInterpolatedString
- #51 macro.go: Remove var _ = fmt.Sprintf import guard
- macroeval.go: Remove unused lexer import and guard

Total fixed this session: 12/53 issues resolved
Remaining: 41 (CRITICAL: 1, HIGH: 9, MEDIUM: 16, LOW: 16)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-01 10:32:09 +09:00
parent 7c61db70c3
commit d7513eeb24
7 changed files with 144 additions and 41 deletions

View File

@@ -101,13 +101,8 @@ func GoCall(receiver Value, method string, args ...Value) []Value {
}
}
// Call
var results []reflect.Value
if mt.IsVariadic() {
results = m.Call(goArgs)
} else {
results = m.Call(goArgs)
}
// Call — m.Call handles both variadic and non-variadic correctly
results := m.Call(goArgs)
// Convert Go results → Harbour Values
hbResults := make([]Value, len(results))
@@ -145,12 +140,7 @@ func GoCallFunc(fn interface{}, args ...Value) []Value {
}
}
var results []reflect.Value
if isVariadic {
results = rv.Call(goArgs) // Call (not CallSlice) handles spreading
} else {
results = rv.Call(goArgs)
}
results := rv.Call(goArgs)
hbResults := make([]Value, len(results))
for i, r := range results {
hbResults[i] = reflectToValue(r)

View File

@@ -22,8 +22,6 @@ import (
"strings"
)
var _ = fmt.Sprintf // ensure import
// MacroCompile compiles and evaluates a macro expression string.
// Returns the result value.
//

View File

@@ -19,7 +19,6 @@ package hbrt
import (
"five/compiler/ast"
"five/compiler/lexer"
"five/compiler/parser"
"five/compiler/token"
"strings"
@@ -304,6 +303,3 @@ func (t *Thread) macroStoreIdent(name string, val Value) {
_ = name
_ = val
}
// suppress import
var _ = lexer.Tokenize

View File

@@ -62,22 +62,44 @@ func runAtExit() {
// --- Signal handling ---
// signalDone closes to stop the signal handler goroutine on normal exit.
var signalDone chan struct{}
// InstallSignalHandlers sets up OS signal handlers for clean shutdown.
// Harbour: hb_vmSetExceptionHandler (SIGINT, SIGTERM, SIGSEGV)
func (vm *VM) InstallSignalHandlers() {
signalSetup.Do(func() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
sigCh := make(chan os.Signal, 1)
signalDone = make(chan struct{})
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-ch
fmt.Fprintf(os.Stderr, "\nFive: received %v, shutting down...\n", sig)
vm.Shutdown()
os.Exit(1)
select {
case sig := <-sigCh:
fmt.Fprintf(os.Stderr, "\nFive: received %v, shutting down...\n", sig)
vm.Shutdown()
os.Exit(1)
case <-signalDone:
// Normal exit — stop goroutine cleanly
signal.Stop(sigCh)
return
}
}()
})
}
// stopSignalHandler stops the signal handler goroutine (called during normal shutdown).
func stopSignalHandler() {
if signalDone != nil {
select {
case <-signalDone:
// already closed
default:
close(signalDone)
}
}
}
// --- Main shutdown sequence ---
// Shutdown executes the full VM shutdown sequence.
@@ -127,6 +149,9 @@ func (vm *VM) doShutdown() {
// Harbour: hb_gcCollectAll(HB_TRUE) × 3
// Go's GC is automatic, but we can hint
// runtime.GC() — not calling explicitly, Go handles it
// Phase 10: Stop signal handler goroutine (prevent leak)
stopSignalHandler()
}
// runExitProcedures executes all EXIT PROCEDURE declarations.

View File

@@ -27,35 +27,47 @@ func (vm *VM) SetOnExit(f func()) {
vm.onExit = f
}
// Library modules registered via init()
var libModules []*Module
var dynamicFuncs []Symbol // from HB_FUNC() in #pragma BEGINDUMP
// Library modules registered via init() — protected by mutex for FRB concurrent loading.
var (
libModules []*Module
dynamicFuncs []Symbol // from HB_FUNC() in #pragma BEGINDUMP
libRegistryMu sync.Mutex
)
// RegisterLibModule registers a module from a library PRG file.
// Called by init() in generated library code.
func RegisterLibModule(m *Module) {
libRegistryMu.Lock()
libModules = append(libModules, m)
libRegistryMu.Unlock()
}
// RegisterDynamicFunc registers a Go function callable from PRG.
// Called from init() in #pragma BEGINDUMP code via HB_FUNC().
func RegisterDynamicFunc(name string, fn func(*Thread)) {
libRegistryMu.Lock()
dynamicFuncs = append(dynamicFuncs, Symbol{
Name: name,
Scope: FsPublic | FsLocal,
Func: fn,
})
libRegistryMu.Unlock()
}
// RegisterLibModules registers any pending lib modules and dynamic functions.
func (vm *VM) RegisterLibModules() {
for _, m := range libModules {
libRegistryMu.Lock()
mods := libModules
libModules = nil
dyns := dynamicFuncs
dynamicFuncs = nil
libRegistryMu.Unlock()
for _, m := range mods {
vm.RegisterModule(m)
}
libModules = nil
// Register HB_FUNC dynamic functions from #pragma BEGINDUMP
for i := range dynamicFuncs {
sym := &dynamicFuncs[i]
for i := range dyns {
sym := &dyns[i]
vm.RegisterSymbol(sym)
}
dynamicFuncs = nil