• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 32594674453

22 Aug 2026 07:45PM UTC coverage: 78.842% (+0.03%) from 78.809%
32594674453

push

github

web-flow
Expose the memory reports as named PolCore() methods, and fix what they reported (#947)

* Expose memory diagnostics as named PolCore() methods

Add PolCore().log_memory_usage(), log_script_memory() and
log_script_variables(script) for the reports that were previously only
reachable through internal(2), (5) and (6). The old selectors still work
and log a one-time deprecation notice naming the replacement.

The two per-script reports now write one timestamped file per call under
log/memory/ rather than appending to a single log; memoryusage.log is
unchanged.

* Fix memory reporting defects

log_script_variables() printed each local variable's name next to a
different local's type and size: the block-relative name index was also
used to look up the value, which lives at the frame-wide slot. The
debugger's stacktrace command had the same fault.

It also stopped at the first instance whose debug info could not be
loaded instead of skipping just that one, and never scanned the queue of
scripts held by the debugger, which estimateSize does count.

SizeOf() on a class instance reachable from itself recursed until the
stack overflowed, taking the server down from any script. Guard the walk
with a per-thread set of instances already being measured.

Size estimates were too low for packets (sizeof was omitted), compiled
scripts (the bytecode and symbol pool were not counted at all, nor the
per-block local variable names) and regular expressions.

* Remove redundant work from the memory reports

These reports block the server while they run, so the duplicated passes
are worth dropping. The reported numbers are unchanged.

The object walk counted the object hash with std::distance immediately
before iterating it; since the hash is a std::map that was a second full
traversal of one node per object in the world. Count in the existing
loop instead.

The per-script report called Executor::sizeEstimate() twice per script,
once for the total and again to format the log... (continued)

187 of 203 new or added lines in 11 files covered. (92.12%)

3 existing lines in 2 files now uncovered.

57376 of 72773 relevant lines covered (78.84%)

1730196.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.49
/pol-core/bscript/bclassinstance.cpp
1
#include "bscript/bclassinstance.h"
2

3
#include "bscript/berror.h"
4
#include "bscript/bfuncref.h"
5
#include "bscript/bobject.h"
6
#include "bscript/bspecialjump.h"
7
#include "clib/clib.h"
8
#include "clib/stlutil.h"
9
#include "bscript/executor.h"
10
#include "bscript/objmembers.h"
11
#include "bscript/objmethods.h"
12

13
namespace Pol::Bscript
14
{
15
BClassInstance::BClassInstance( ref_ptr<EScriptProgram> program, int index,
550 ✔
16
                                std::weak_ptr<ValueStackCont> globals, unsigned int pid )
550 ✔
17
    : BStruct( OTClassInstance ),
18
      prog_( std::move( program ) ),
550 ✔
19
      index_( index ),
550 ✔
20
      pid_( pid ),
550 ✔
21
      globals( std::move( globals ) )
1,100 ✔
22
{
23
  passert( index_ < prog_->class_descriptors.size() );
550 ✔
24
}
550 ✔
25

26
BClassInstance::BClassInstance( const BClassInstance& B ) : BStruct( B, OTClassInstance )
×
27
{
28
  prog_ = B.prog_;
×
29
  index_ = B.index_;
×
30
  pid_ = B.pid_;
×
31
  globals = B.globals;
×
32
}
×
33

34
size_t BClassInstance::sizeEstimate() const
183 ✔
35
{
36
  return base::sizeEstimate() + Clib::memsize( constructors_called ) +
183 ✔
37
         sizeof( ref_ptr<EScriptProgram> ) + 2 * sizeof( unsigned int ) +
38
         sizeof( std::weak_ptr<ValueStackCont> );
183 ✔
39
}
40

41
ref_ptr<EScriptProgram> BClassInstance::prog() const
239 ✔
42
{
43
  return prog_;
239 ✔
44
}
45

46
unsigned BClassInstance::index() const
2,452 ✔
47
{
48
  return index_;
2,452 ✔
49
}
50

51
BFunctionRef* BClassInstance::makeMethod( const char* method_name )
337 ✔
52
{
53
  const auto& methods = prog_->class_descriptors[index_].methods;
337 ✔
54
  auto method_itr =
55
      std::find_if( methods.begin(), methods.end(),
337 ✔
56
                    [&]( const auto& it )
1,100 ✔
57
                    {
58
                      if ( it.first < prog_->symbols.length() )
1,100 ✔
59
                      {
60
                        return stricmp( method_name, prog_->symbols.array() + it.first ) == 0;
1,100 ✔
61
                      }
62
                      return false;
×
63
                    } );
64

65
  if ( method_itr == methods.end() )
337 ✔
66
    return nullptr;
6 ✔
67

68
  return new BFunctionRef( prog_, pid_, method_itr->second.function_reference_index, globals,
331 ✔
69
                           ValueStackCont{} );
662 ✔
70
}
71

72
const char* BClassInstance::typetag() const
6 ✔
73
{
74
  // Return the class name as the type tag.
75
  return prog_->symbols.array() + prog_->class_descriptors[index_].name_offset;
6 ✔
76
}
77

78
const char* BClassInstance::typeOf() const
×
79
{
80
  return "Class";
×
81
}
82

83
u8 BClassInstance::typeOfInt() const
×
84
{
85
  return OTClassInstance;
×
86
}
87

88
BObjectImp* BClassInstance::copy() const
×
89
{
90
  passert_always_r( false, "BClassInstance::copy() should never be called" );
×
91
  return nullptr;
92
}
93

94
bool BClassInstance::isTrue() const
×
95
{
96
  return true;
×
97
}
98

99
BObjectImp* BClassInstance::call_method( const char* method_name, Executor& ex )
2,367 ✔
100
{
101
  BFunctionRef* funcr = nullptr;
2,367 ✔
102

103
  BObjectImp* callee{ nullptr };
2,367 ✔
104
  // Prefer members over class methods by checking contents first.
105
  auto member_itr = contents().find( method_name );
2,367 ✔
106

107
  if ( member_itr != contents().end() )
2,367 ✔
108
  {
109
    // If the member exists and is NOT a function reference, we will still try
110
    // to "call" it. This is _intentional_, and will result in a runtime
111
    // BError. This is similar to `var foo := 3; print(foo.bar());`, resulting
112
    // in a "Method 'bar' not found" error.
113
    callee = member_itr->second.get()->impptr();
9 ✔
114

115
    funcr = member_itr->second.get()->impptr_if<BFunctionRef>();
9 ✔
116
  }
117
  else
118
  {
119
    // Have we already looked up this method?
120
    Executor::ClassMethodKey key{ ex.prog_, index(), method_name };
2,358 ✔
121
    auto cache_itr = ex.class_methods.find( key );
2,358 ✔
122
    if ( cache_itr != ex.class_methods.end() )
2,358 ✔
123
    {
124
      // Switch the callee to the function reference: if the
125
      // funcr->validCall fails, we will go into the funcref
126
      // ins_call_method, giving the error about invalid parameter counts.
127
      funcr = cache_itr->second->impptr_if<BFunctionRef>();
2,021 ✔
128
      callee = funcr;
2,021 ✔
129
      method_name = getObjMethod( MTH_CALL_METHOD )->code;
2,021 ✔
130
    }
131
    else
132
    {
133
      // Does the class define this method?
134
      funcr = makeMethod( method_name );
337 ✔
135

136
      if ( funcr != nullptr )
337 ✔
137
      {
138
        // Cache the method for future lookups
139
        ex.class_methods[key] = BObjectRef( funcr );
331 ✔
140

141
        // Switch the callee to the function reference.
142
        callee = funcr;
331 ✔
143
        method_name = getObjMethod( MTH_CALL_METHOD )->code;
331 ✔
144
      }
145
    }
146
  }
2,358 ✔
147

148
  if ( funcr != nullptr )
2,367 ✔
149
  {
150
    Instruction jmp;
2,358 ✔
151
    int id;
152

153
    // Add `this` to the front of the argument list only for class methods,
154
    // skipping eg. an instance member function reference set via
155
    // `this.foo := @(){};`.
156
    if ( funcr->class_method() )
2,358 ✔
157
    {
158
      id = MTH_CALL_METHOD;
2,352 ✔
159
      ex.fparams.insert( ex.fparams.begin(), ex.ValueStack.back() );
2,352 ✔
160
    }
161
    else
162
    {
163
      id = MTH_CALL;
6 ✔
164
    }
165

166
    if ( funcr->validCall( id, ex, &jmp ) )
2,358 ✔
167
    {
168
      BObjectRef funcobj( funcr );  // valuestack gets modified, protect BFunctionRef
2,337 ✔
169
      ex.call_function_reference( funcr, nullptr, jmp );
2,337 ✔
170
      return BSpecialUserFuncJump::get();
2,337 ✔
171
    }
2,337 ✔
172
  }
2,358 ✔
173
  if ( callee )
30 ✔
174
    return callee->call_method( method_name, ex );
24 ✔
175
  return new BError( fmt::format( "Method '{}' not found in class '{}'", method_name, typetag() ) );
12 ✔
176
}
177

178
BObjectImp* BClassInstance::call_method_id( const int id, Executor& ex, bool /*forcebuiltin*/ )
3 ✔
179
{
180
  auto method = getObjMethod( id );
3 ✔
181
  return call_method( method->code, ex );
3 ✔
182
}
183

184
BObjectRef BClassInstance::get_member_id( const int id )
129 ✔
185
{
186
  if ( id == MBR_FUNCTION )
129 ✔
187
  {
188
    const auto funcref_index =
189
        prog_->class_descriptors.at( index_ ).constructor_function_reference_index;
18 ✔
190

191
    return BObjectRef( new BFunctionRef( prog_, pid_, funcref_index, globals, ValueStackCont{} ) );
36 ✔
192
  }
193

194
  return base::get_member_id( id );
111 ✔
195
}
196

197
std::string BClassInstance::getStringRep() const
327 ✔
198
{
199
  auto class_name = prog_->symbols.array() + prog_->class_descriptors[index_].name_offset;
327 ✔
200
  return fmt::format( "<class {}>", class_name );
654 ✔
201
}
202

203
BClassInstanceRef::BClassInstanceRef( BClassInstance* inst )
2,958 ✔
204
    : BObjectImp( BObjectType::OTClassInstanceRef ), class_instance_( inst )
2,958 ✔
205
{
206
}
2,958 ✔
207

208
namespace
209
{
210
// Class instances are reference types, so one can reach itself and the walk below has no
211
// other stop condition. Unguarded it recurses until the stack overflows, from any script
212
// through SizeOf().
213
thread_local std::set<const BClassInstance*> size_estimate_visiting;
214

215
class VisitGuard
216
{
217
public:
218
  explicit VisitGuard( const BClassInstance* inst )
195 ✔
219
      : inst_( inst ), entered_( size_estimate_visiting.insert( inst ).second )
195 ✔
220
  {
221
  }
195 ✔
222
  ~VisitGuard()
195 ✔
223
  {
224
    if ( entered_ )
195 ✔
225
      size_estimate_visiting.erase( inst_ );
183 ✔
226
  }
195 ✔
227
  VisitGuard( const VisitGuard& ) = delete;
228
  VisitGuard& operator=( const VisitGuard& ) = delete;
229

230
  bool entered() const { return entered_; }
195 ✔
231

232
private:
233
  const BClassInstance* inst_;
234
  bool entered_;
235
};
236
}  // namespace
237

238
size_t BClassInstanceRef::sizeEstimate() const
195 ✔
239
{
240
  const BClassInstance* inst = class_instance_.get();
195 ✔
241
  if ( inst == nullptr )
195 ✔
NEW
242
    return sizeof( BClassInstanceRef );
×
243

244
  VisitGuard guard( inst );
195 ✔
245
  if ( !guard.entered() )
195 ✔
246
    return sizeof( BClassInstanceRef );  // already being measured further up: a cycle
12 ✔
247

248
  return sizeof( BClassInstanceRef ) + inst->sizeEstimate();
183 ✔
249
}
195 ✔
250

251
const char* BClassInstanceRef::typeOf() const
7 ✔
252
{
253
  return "ClassInstanceRef";
7 ✔
254
}
255

256
u8 BClassInstanceRef::typeOfInt() const
9 ✔
257
{
258
  return OTClassInstanceRef;
9 ✔
259
}
260

261
BObjectImp* BClassInstanceRef::copy() const
2,408 ✔
262
{
263
  return new BClassInstanceRef( class_instance_.get() );
2,408 ✔
264
}
265

266
bool BClassInstanceRef::isTrue() const
6 ✔
267
{
268
  return true;
6 ✔
269
}
270

271
BObjectImp* BClassInstanceRef::call_method( const char* methodname, Executor& ex )
2,364 ✔
272
{
273
  return class_instance_->call_method( methodname, ex );
2,364 ✔
274
}
275

276
BObjectImp* BClassInstanceRef::call_method_id( const int id, Executor& ex, bool forcebuiltin )
3 ✔
277
{
278
  return class_instance_->call_method_id( id, ex, forcebuiltin );
3 ✔
279
}
280

281
BObjectRef BClassInstanceRef::get_member_id( const int id )
129 ✔
282
{
283
  return class_instance_->get_member_id( id );
129 ✔
284
}
285

286
std::string BClassInstanceRef::getStringRep() const
327 ✔
287
{
288
  return class_instance_->getStringRep();
327 ✔
289
}
290

291
ContIterator* BClassInstanceRef::createIterator( BObject* pIterVal )
6 ✔
292
{
293
  return class_instance_->createIterator( pIterVal );
6 ✔
294
}
295

296
BObjectRef BClassInstanceRef::OperSubscript( const BObject& obj )
9 ✔
297
{
298
  return class_instance_->OperSubscript( obj );
9 ✔
299
}
300

301
BObjectRef BClassInstanceRef::set_member( const char* membername, BObjectImp* value, bool copy )
558 ✔
302
{
303
  return class_instance_->set_member( membername, value, copy );
558 ✔
304
}
305

306
BObjectRef BClassInstanceRef::get_member( const char* membername )
8,321 ✔
307
{
308
  return class_instance_->get_member( membername );
8,321 ✔
309
}
310

311
BObjectRef BClassInstanceRef::operDotPlus( const char* name )
6 ✔
312
{
313
  return class_instance_->operDotPlus( name );
6 ✔
314
}
315

316
BObjectRef BClassInstanceRef::operDotMinus( const char* name )
6 ✔
317
{
318
  return class_instance_->operDotMinus( name );
6 ✔
319
}
320

321
BObjectRef BClassInstanceRef::operDotQMark( const char* name )
6 ✔
322
{
323
  return class_instance_->operDotQMark( name );
6 ✔
324
}
325

326
BObjectImp* BClassInstanceRef::array_assign( BObjectImp* idx, BObjectImp* target, bool copy )
6 ✔
327
{
328
  return class_instance_->array_assign( idx, target, copy );
6 ✔
329
}
330
}  // namespace Pol::Bscript
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc