fix: Phase 1 Step 0 cleanup + CRITICAL #3, MEDIUM #36-37, LOW #50

Files modified (5):
  hbrt/macro.go — Replace hand-rolled parseFloat/parseInt64 with strconv (#50)
                   Remove stale TODO, redundant TrimSpace
  hbrt/macroeval.go — Use strconv for literal parsing (was using removed functions)
  hbrt/class.go — CRITICAL #3: Change RWMutex to Mutex on classList
                   Prevents slice reallocation race on concurrent GetClass
  hbrt/goroutine.go — #36: Channel double-close protection (sync.Once)
                       #37: Send on closed channel recovery (defer/recover)
                       Add IsClosed(), safe Receive (handles closed channel)
  hbrt/gobridge.go — Already clean (confirmed)
  hbrt/hbfunc.go — Already clean (confirmed)

Issues resolved: #3 (CRITICAL), #36, #37 (MEDIUM), #50 (LOW)
Total fixed: 16/53

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-01 10:51:20 +09:00
parent d7513eeb24
commit 207fa9f7dd
5 changed files with 107 additions and 77 deletions

View File

@@ -76,9 +76,9 @@ const (
// --- Class Registry ---
var (
classRegMu sync.RWMutex
classRegMu sync.Mutex // full mutex (not RW) — prevents slice reallocation race on classList
classReg = map[string]*ClassDef{}
classList []*ClassDef // index = classID - 1
classList []*ClassDef // index = classID - 1
)
// RegisterClass registers a class definition.
@@ -102,15 +102,15 @@ func RegisterClass(cls *ClassDef) uint16 {
// FindClass looks up a class by name.
func FindClass(name string) *ClassDef {
classRegMu.RLock()
defer classRegMu.RUnlock()
classRegMu.Lock()
defer classRegMu.Unlock()
return classReg[strings.ToUpper(name)]
}
// GetClass looks up a class by ID.
func GetClass(id uint16) *ClassDef {
classRegMu.RLock()
defer classRegMu.RUnlock()
classRegMu.Lock()
defer classRegMu.Unlock()
if int(id) > 0 && int(id) <= len(classList) {
return classList[id-1]
}

View File

@@ -17,7 +17,9 @@ import (
// HbChannel wraps Go's chan Value for use in PRG code.
type HbChannel struct {
Ch chan Value
Ch chan Value
closeOnce sync.Once
closed bool
}
// MakeChannel creates a channel Value with optional buffer size.
@@ -36,29 +38,49 @@ func (v Value) AsChannel() *HbChannel {
return nil
}
// Send sends a value into the channel.
// Send sends a value into the channel. Safe if channel is closed (recovers panic).
func (ch *HbChannel) Send(val Value) {
defer func() {
if r := recover(); r != nil {
// send on closed channel — silently ignore
}
}()
ch.Ch <- val
}
// Receive receives a value from the channel.
func (ch *HbChannel) Receive() Value {
return <-ch.Ch
v, ok := <-ch.Ch
if !ok {
return MakeNil() // channel closed
}
return v
}
// TryReceive attempts non-blocking receive. Returns (value, true) or (nil, false).
func (ch *HbChannel) TryReceive() (Value, bool) {
select {
case v := <-ch.Ch:
case v, ok := <-ch.Ch:
if !ok {
return MakeNil(), false
}
return v, true
default:
return MakeNil(), false
}
}
// Close closes the channel.
// Close closes the channel. Safe to call multiple times.
func (ch *HbChannel) Close() {
close(ch.Ch)
ch.closeOnce.Do(func() {
ch.closed = true
close(ch.Ch)
})
}
// IsClosed returns true if the channel has been closed.
func (ch *HbChannel) IsClosed() bool {
return ch.closed
}
// --- WaitGroup ---

View File

@@ -18,7 +18,7 @@
package hbrt
import (
"fmt"
"strconv"
"strings"
)
@@ -39,9 +39,7 @@ func (t *Thread) MacroCompile(expr string) Value {
// Simple case: expression is a variable name
// Look up in memvars first, then try as function call
if isSimpleIdent(expr) {
// Try PUBLIC/PRIVATE memvar
// TODO: full memvar system
// For now, try calling it as a function
// Try calling as a function (memvar lookup deferred to MacroEval)
sym := t.vm.FindSymbol(strings.ToUpper(expr))
if sym != nil && sym.Func != nil {
t.PushSymbol(sym)
@@ -56,15 +54,14 @@ func (t *Thread) MacroCompile(expr string) Value {
// Full runtime expression parser would be needed for complete macro support.
// This handles common patterns: &("literal"), &(numericExpr)
// Try numeric
expr = strings.TrimSpace(expr)
// Try numeric (use stdlib strconv)
if len(expr) > 0 && (expr[0] >= '0' && expr[0] <= '9' || expr[0] == '-' || expr[0] == '+') {
if strings.Contains(expr, ".") {
if f, err := parseFloat(expr); err == nil {
if f, err := strconv.ParseFloat(expr, 64); err == nil {
return MakeDoubleAuto(f)
}
} else {
if n, err := parseInt64(expr); err == nil {
if n, err := strconv.ParseInt(expr, 10, 64); err == nil {
return MakeNumInt(n)
}
}
@@ -96,57 +93,7 @@ func (t *Thread) MacroPush() {
t.push(result)
}
func parseFloat(s string) (float64, error) {
var result float64
var sign float64 = 1
i := 0
if i < len(s) && s[i] == '-' {
sign = -1
i++
} else if i < len(s) && s[i] == '+' {
i++
}
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
result = result*10 + float64(s[i]-'0')
i++
}
if i < len(s) && s[i] == '.' {
i++
frac := 0.1
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
result += float64(s[i]-'0') * frac
frac /= 10
i++
}
}
if i != len(s) {
return 0, fmt.Errorf("invalid float")
}
return sign * result, nil
}
func parseInt64(s string) (int64, error) {
var result int64
var sign int64 = 1
i := 0
if i < len(s) && s[i] == '-' {
sign = -1
i++
} else if i < len(s) && s[i] == '+' {
i++
}
if i >= len(s) {
return 0, fmt.Errorf("empty")
}
for i < len(s) {
if s[i] < '0' || s[i] > '9' {
return 0, fmt.Errorf("invalid int")
}
result = result*10 + int64(s[i]-'0')
i++
}
return sign * result, nil
}
// parseFloat and parseInt64 removed — using strconv.ParseFloat/ParseInt instead.
// isSimpleIdent checks if string is a valid simple identifier.
func isSimpleIdent(s string) bool {

View File

@@ -21,6 +21,7 @@ import (
"five/compiler/ast"
"five/compiler/parser"
"five/compiler/token"
"strconv"
"strings"
)
@@ -176,13 +177,13 @@ func (t *Thread) evalLiteral(e *ast.LiteralExpr) Value {
case token.FALSE:
return MakeBool(false)
case token.INT:
n, _ := parseInt64(e.Value)
n, _ := strconv.ParseInt(e.Value, 10, 64)
return MakeNumInt(n)
case token.LONG:
n, _ := parseInt64(e.Value)
n, _ := strconv.ParseInt(e.Value, 10, 64)
return MakeLong(n)
case token.DOUBLE:
f, _ := parseFloat(e.Value)
f, _ := strconv.ParseFloat(e.Value, 64)
return MakeDoubleAuto(f)
case token.STRING:
return MakeString(e.Value)