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

polserver / polserver / 16683711876

01 Aug 2025 07:40PM UTC coverage: 59.756% (-0.03%) from 59.79%
16683711876

push

github

web-flow
use c++20 (#799)

* use c++20
* increased used clang version
* compiler report and logfacility use now compile time formatting, which
means that the formatstring gets checked at compile time. (which found 2 errors)
adapted a few places since report only accepts formatting arguments
adapted a few places with logging since char[] is compile time
formatting and string or chr* is runtime formatting
* use std::ranges instead of boost
* disabled pragma_assume vs specific macro (I guess noone cares)
* needed to fix ancient ms exception code
* modernized SpinLock
* removed unused code in ECompile
* replaced std::filesystem::path::u8string with string. It now returns an actual u8string type
* cleanup layers.h added the defines for other layers which where before
  only defined in the pkt
* osmod::OpenConnection and HTTPRequest cleanup: early outs, dont check for pChild which is
  only needed for startscript and placed suspend at the very last
  position

* fix warning

* rebuild cache with new compiler version

* define c++ standard for external libs where possible

* added fixme

156 of 212 new or added lines in 26 files covered. (73.58%)

45 existing lines in 19 files now uncovered.

43621 of 72999 relevant lines covered (59.76%)

409874.73 hits per line

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

74.51
/pol-core/pol/dap/clientthread.cpp
1
#include "clientthread.h"
2

3
#include "../../bscript/bstruct.h"
4
#include "../../bscript/dict.h"
5
#include "../../bscript/eprog.h"
6
#include "../../bscript/impstr.h"
7
#include "../../clib/esignal.h"
8
#include "../../clib/fileutil.h"
9
#include "../../clib/logfacility.h"
10
#include "../../plib/pkg.h"
11
#include "../../plib/systemstate.h"
12
#include "../module/uomod.h"
13
#include "../polsem.h"
14
#include "../scrdef.h"
15
#include "../scrsched.h"
16

17
#include <boost/algorithm/string/predicate.hpp>
18
#include <filesystem>
19
#include <fstream>
20

21
namespace fs = std::filesystem;
22

23
namespace Pol
24
{
25
using namespace Core;
26
using namespace Bscript;
27
using namespace EscriptGrammar;
28
using namespace Compiler;
29

30
namespace Network
31
{
32
namespace DAP
33
{
34
namespace fs = std::filesystem;
35

36
unsigned int DebugClientThread::_instance_counter = 0;
37

38
DebugClientThread::DebugClientThread( const std::shared_ptr<dap::ReaderWriter>& rw )
2✔
39
    : _instance( ++_instance_counter ),
2✔
40
      _rw( rw ),
2✔
41
      _session( dap::Session::create() ),
2✔
42
      _uoexec_wptr( nullptr ),
2✔
43
      _expression_evaluator(),
2✔
44
      _global_scope_handle( 0 ),
2✔
45
      _was_launch_requested( false ),
2✔
46
      _exit_sent( false )
6✔
47
{
48
}
2✔
49

50
void DebugClientThread::on_halt()
9✔
51
{
52
  dap::StoppedEvent event;
9✔
53
  event.reason = "pause";
9✔
54
  event.threadId = 1;
9✔
55
  _session->send( event );
9✔
56
}
9✔
57

58
void DebugClientThread::on_destroy()
4✔
59
{
60
  POLLOG_INFOLN( "Debugger#{} script destroyed.", _instance );
4✔
61
  dap::ExitedEvent event;
4✔
62
  event.exitCode = 0;
4✔
63
  _session->send( event );
4✔
64
  _exit_sent = true;
4✔
65
}
4✔
66

67
void DebugClientThread::on_print( const std::string& output )
1✔
68
{
69
  dap::OutputEvent event;
1✔
70
  event.category = "stdout";
1✔
71
  event.output = output + "\n";
1✔
72

73
  // The `ExecutorDebugListener` methods are called from the script thread
74
  // (which runs inside a PolLock), so no need to lock. Additionally, the
75
  // `UOExecutor` should always exist, as these methods are all called within an
76
  // `Executor` instance method, but check just in case. Not a real performance
77
  // hit, since this is only called when printing with the debugger is attached.
78
  if ( _uoexec_wptr.exists() )
1✔
79
  {
80
    UOExecutor* exec = _uoexec_wptr.get_weakptr();
1✔
81
    dap::Source source;
1✔
82
    source.path = _script->dbg_filenames[_script->dbg_filenum[exec->PC]];
1✔
83
    event.source = source;
1✔
84
    event.line = _script->dbg_linenum[exec->PC];
1✔
85
  }
1✔
86

87
  _session->send( event );
1✔
88
}
1✔
89

90
dap::ConfigurationDoneResponse DebugClientThread::handle_configurationDone(
×
91
    const dap::ConfigurationDoneRequest& )
92
{
93
  return dap::ConfigurationDoneResponse{};
×
94
}
95

96
dap::ResponseOrError<dap::AttachResponse> DebugClientThread::handle_attach(
1✔
97
    const dap::PolAttachRequest& request )
98
{
99
  PolLock lock;
1✔
100
  UOExecutor* uoexec;
101

102
  if ( _uoexec_wptr.exists() )
1✔
103
  {
104
    uoexec = _uoexec_wptr.get_weakptr();
×
105
    if ( uoexec->in_debugger_holdlist() )
×
106
      uoexec->revive_debugged();
×
107

108
    _variable_handles.reset();
×
109
    _global_scope_handle = 0;
×
110
    uoexec->detach_debugger();
×
111
    _uoexec_wptr.clear();
×
112
  }
113

114
  auto pid = static_cast<unsigned int>( request.pid );
1✔
115
  if ( find_uoexec( pid, &uoexec ) )
1✔
116
  {
117
    EScriptProgram* prog = const_cast<EScriptProgram*>( uoexec->prog() );
1✔
118

119
    if ( prog->read_dbg_file() == 0 )
1✔
120
    {
121
      if ( !uoexec->attach_debugger( weak_from_this() ) )
1✔
122
        return dap::Error( "Debugger already attached." );
×
123

124
      _uoexec_wptr = uoexec->weakptr;
1✔
125
      _script.set( prog );
1✔
126

127
      return dap::AttachResponse{};
1✔
128
    }
129
    return dap::Error( "No debug information available." );
×
130
  }
131
  return dap::Error( "Unknown process id '%d'", int( pid ) );
×
132
}
1✔
133

134
dap::ResponseOrError<dap::LaunchResponse> DebugClientThread::handle_launch(
1✔
135
    const dap::PolLaunchRequest& request )
136
{
137
  PolLock lock;
1✔
138
  ScriptDef sd;
1✔
139

140
  fs::path p( request.script );
1✔
141

142
  if ( p.extension().string() == ".src" )
1✔
143
    p.replace_extension( fs::path( ".ecl" ) );
×
144

145
  bool config_result;
146

147
  // A package-path
148
  if ( request.script.find( ':' ) == 0 )
1✔
149
  {
150
    config_result = sd.config_nodie( p.string(), nullptr, "scripts/" );
1✔
151
  }
152
  else
153
  {
NEW
154
    std::string relative = ( p.is_absolute() ? fs::relative( p ) : p ).string();
×
155

156
    if ( relative.find( "scripts/" ) == 0 )
×
157
    {
158
      config_result = sd.config_nodie( relative.substr( 8 ), nullptr, "scripts/" );
×
159
    }
160
    else
161
    {
162
      const Plib::Package* package = nullptr;
×
163
      for ( const auto* pkg : Plib::systemstate.packages )
×
164
      {
165
        if ( relative.find( pkg->dir() ) == 0 )
×
166
        {
167
          package = pkg;
×
168
          break;
×
169
        }
170
      }
171
      if ( package )
×
172
      {
173
        config_result =
174
            sd.config_nodie( relative.substr( package->dir().length() ), package, "scripts/" );
×
175
      }
176
      else
177
      {
178
        config_result = sd.config_nodie( request.script, nullptr, "scripts/" );
×
179
      }
180
    }
181
  }
×
182

183
  if ( !config_result )
1✔
184
    return dap::Error( "Error in script name." );
×
185
  if ( !sd.exists() )
1✔
186
    return dap::Error( "Script " + sd.name() + " does not exist." );
×
187

188
  Module::UOExecutorModule* new_uoemod;
189

190
  if ( request.arg.has_value() && request.arg->length() > 0 )
1✔
191
  {
192
    new_uoemod = Core::start_script( sd, BObjectImp::unpack( request.arg->c_str() ) );
1✔
193
  }
194
  else
195
  {
196
    new_uoemod = Core::start_script( sd, nullptr );
×
197
  }
198

199
  if ( new_uoemod == nullptr )
1✔
200
  {
201
    return dap::Error( "Unable to start script" );
×
202
  }
203

204
  UOExecutor* uoexec = static_cast<UOExecutor*>( &new_uoemod->exec );
1✔
205
  EScriptProgram* prog = const_cast<EScriptProgram*>( uoexec->prog() );
1✔
206

207
  if ( prog->read_dbg_file() == 0 )
1✔
208
  {
209
    bool stopAtEntry = request.stopAtEntry.value( false );
1✔
210
    if ( !uoexec->attach_debugger( weak_from_this(), stopAtEntry ) )
1✔
211
      return dap::Error( "Debugger already attached." );
×
212

213
    _uoexec_wptr = uoexec->weakptr;
1✔
214
    _script.set( prog );
1✔
215

216
    _was_launch_requested = true;
1✔
217

218
    return dap::LaunchResponse{};
1✔
219
  }
220
  return dap::Error( "No debug information available." );
×
221
}
1✔
222

223
dap::ResponseOrError<dap::PolProcessesResponse> DebugClientThread::handle_processes(
1✔
224
    const dap::PolProcessesRequest& request )
225
{
226
  PolLock lock;
1✔
227
  dap::PolProcessesResponse response;
1✔
228

229
  for ( const auto& [pid, uoexec] : scriptScheduler.getPidlist() )
13✔
230
  {
231
    if ( !request.filter.has_value() ||
24✔
232
         boost::icontains( uoexec->scriptname(), request.filter.value() ) )
24✔
233
    {
234
      dap::PolProcess entry;
1✔
235
      entry.id = pid;
1✔
236
      entry.script = uoexec->scriptname();
1✔
237

238
      if ( uoexec->halt() )
1✔
239
        entry.state = 2;  // debugging
1✔
240
      else if ( uoexec->in_hold_list() == NO_LIST )
×
241
        entry.state = 1;  // running
×
242
      else
243
        entry.state = 0;  // sleeping
×
244

245
      response.processes.push_back( entry );
1✔
246
    }
1✔
247
  }
248

249
  return response;
2✔
250
}
1✔
251

252
dap::ResponseOrError<dap::ThreadsResponse> DebugClientThread::handle_threads(
2✔
253
    const dap::ThreadsRequest& )
254
{
255
  PolLock lock;
2✔
256
  if ( !_uoexec_wptr.exists() )
2✔
257
    return dap::Error( "No script attached." );
×
258

259
  UOExecutor* exec = _uoexec_wptr.get_weakptr();
2✔
260
  dap::ThreadsResponse response;
2✔
261
  dap::Thread thread;
2✔
262
  thread.id = 1;
2✔
263
  thread.name = Clib::tostring( exec->pid() );
2✔
264
  response.threads.push_back( thread );
2✔
265
  return response;
2✔
266
}
2✔
267

268
dap::ResponseOrError<dap::SetExceptionBreakpointsResponse>
269
DebugClientThread::handle_setExceptionBreakpoints( const dap::SetExceptionBreakpointsRequest& )
×
270
{
271
  return dap::SetExceptionBreakpointsResponse{};
×
272
}
273

274
dap::ResponseOrError<dap::ContinueResponse> DebugClientThread::handle_continue(
4✔
275
    const dap::ContinueRequest& )
276
{
277
  PolLock lock;
4✔
278
  if ( !_uoexec_wptr.exists() )
4✔
279
  {
280
    return dap::Error( "No script attached." );
×
281
  }
282

283
  UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
4✔
284
  if ( !uoexec->in_debugger_holdlist() )
4✔
285
  {
286
    return dap::Error( "Script not ready to trace." );
×
287
  }
288

289
  _variable_handles.reset();
4✔
290
  _global_scope_handle = 0;
4✔
291
  uoexec->dbg_run();
4✔
292
  uoexec->revive_debugged();
4✔
293
  return dap::ContinueResponse{};
8✔
294
}
4✔
295

296
dap::ResponseOrError<dap::PauseResponse> DebugClientThread::handle_pause( const dap::PauseRequest& )
1✔
297
{
298
  PolLock lock;
1✔
299
  if ( !_uoexec_wptr.exists() )
1✔
300
  {
301
    return dap::Error( "No script attached." );
×
302
  }
303

304
  UOExecutor* exec = _uoexec_wptr.get_weakptr();
1✔
305
  exec->dbg_break();
1✔
306
  return dap::PauseResponse{};
1✔
307
}
1✔
308

309
dap::ResponseOrError<dap::EvaluateResponse> DebugClientThread::handle_evaluate(
12✔
310
    const dap::EvaluateRequest& request )
311
{
312
  PolLock lock;
12✔
313
  if ( !_uoexec_wptr.exists() )
12✔
314
  {
315
    return dap::Error( "No script attached." );
×
316
  }
317

318
  BObjectRef result;
12✔
319
  try
320
  {
321
    result = _expression_evaluator.evaluate( _uoexec_wptr.get_weakptr(), _script.get(),
24✔
322
                                             request.expression );
24✔
323
  }
324
  catch ( std::exception& ex )
×
325
  {
326
    return dap::Error( ex.what() );
×
327
  }
×
328

329
  dap::EvaluateResponse response;
12✔
330
  _variable_handles.set_response_details( result, response );
12✔
331
  return response;
12✔
332
}
12✔
333

334
dap::ResponseOrError<dap::SetVariableResponse> DebugClientThread::handle_setVariable(
2✔
335
    const dap::SetVariableRequest& request )
336
{
337
  PolLock lock;
2✔
338
  if ( !_uoexec_wptr.exists() )
2✔
339
  {
340
    return dap::Error( "No script attached." );
×
341
  }
342

343
  BObjectRef value;
2✔
344
  try
345
  {
346
    value =
347
        _expression_evaluator.evaluate( _uoexec_wptr.get_weakptr(), _script.get(), request.value );
2✔
348
  }
349
  catch ( std::exception& ex )
×
350
  {
351
    return dap::Error( ex.what() );
×
352
  }
×
353

354
  dap::SetVariableResponse response;
2✔
355

356
  auto reference_ptr = _variable_handles.get( static_cast<int>( request.variablesReference ) );
2✔
357

358
  if ( reference_ptr == nullptr )
2✔
359
  {
360
    return dap::Error( "Invalid variablesReference id %d", int( request.variablesReference ) );
×
361
  }
362

363
  std::visit(
2✔
364
      [&]( auto&& arg )
4✔
365
      {
366
        using T = std::decay_t<decltype( arg )>;
367
        if constexpr ( (std::is_same_v<T, GlobalReference> || std::is_same_v<T, FrameReference>))
368
        {
369
          for ( auto& [key, member] : arg.contents )
13✔
370
          {
371
            if ( key == request.name )
11✔
372
            {
373
              *member = value;
2✔
374
              _variable_handles.set_response_details( value, response );
2✔
375
            }
376
          }
377
        }
378
        else if constexpr ( std::is_same_v<T, VariableReference> )
379
        {
380
          auto value_set = _variable_handles.set_index_or_member( arg, request.name, value );
×
381
          _variable_handles.set_response_details( value_set, response );
×
382
        }
×
383
      },
2✔
384
      *reference_ptr );
385

386
  return response;
2✔
387
}
2✔
388

389
dap::ResponseOrError<dap::NextResponse> DebugClientThread::handle_next( const dap::NextRequest& )
2✔
390
{
391
  PolLock lock;
2✔
392
  if ( !_uoexec_wptr.exists() )
2✔
393
  {
394
    return dap::Error( "No script attached." );
×
395
  }
396

397
  UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
2✔
398
  if ( !uoexec->in_debugger_holdlist() )
2✔
399
  {
400
    return dap::Error( "Script not ready to trace." );
×
401
  }
402

403
  _variable_handles.reset();
2✔
404
  _global_scope_handle = 0;
2✔
405
  uoexec->dbg_step_over();
2✔
406
  uoexec->revive_debugged();
2✔
407
  return dap::NextResponse{};
2✔
408
}
2✔
409

410
dap::ResponseOrError<dap::StepInResponse> DebugClientThread::handle_stepIn(
1✔
411
    const dap::StepInRequest& )
412
{
413
  PolLock lock;
1✔
414
  if ( !_uoexec_wptr.exists() )
1✔
415
  {
416
    return dap::Error( "No script attached." );
×
417
  }
418

419
  UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
1✔
420
  if ( !uoexec->in_debugger_holdlist() )
1✔
421
  {
422
    return dap::Error( "Script not ready to trace." );
×
423
  }
424

425
  _variable_handles.reset();
1✔
426
  _global_scope_handle = 0;
1✔
427
  uoexec->dbg_step_into();
1✔
428
  uoexec->revive_debugged();
1✔
429
  return dap::StepInResponse{};
1✔
430
}
1✔
431

432
dap::ResponseOrError<dap::SetBreakpointsResponse> DebugClientThread::handle_setBreakpoints(
1✔
433
    const dap::SetBreakpointsRequest& request )
434
{
435
  PolLock lock;
1✔
436
  if ( !_uoexec_wptr.exists() )
1✔
437
  {
438
    return dap::Error( "No script attached." );
×
439
  }
440

441
  UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
1✔
442

443
  unsigned int filenum;
444

445
  if ( request.source.sourceReference.has_value() )
1✔
446
  {
447
    filenum = static_cast<unsigned int>( request.source.sourceReference.value() );
×
448
  }
449
  else
450
  {
451
    if ( !request.source.path.has_value() )
1✔
452
    {
453
      return dap::Error( "No source location provided." );
×
454
    }
455

456
    auto filename_iter = std::find( _script->dbg_filenames.begin(), _script->dbg_filenames.end(),
1✔
457
                                    request.source.path.value() );
458

459
    if ( filename_iter == _script->dbg_filenames.end() )
1✔
460
    {
461
      return dap::Error( "File not in scope" );
×
462
    }
463

464
    filenum =
1✔
465
        static_cast<unsigned int>( std::distance( _script->dbg_filenames.begin(), filename_iter ) );
1✔
466
  }
467

468
  if ( filenum >= _script->dbg_filenames.size() )
1✔
469
  {
470
    return dap::Error( "File %d out of range", filenum );
×
471
  }
472

473
  std::set<unsigned> remove_bps;
1✔
474
  for ( unsigned i = 0; i < _script->dbg_filenum.size(); ++i )
152✔
475
  {
476
    if ( _script->dbg_filenum[i] == filenum )
151✔
477
    {
478
      remove_bps.insert( i );
150✔
479
    }
480
  }
481
  uoexec->dbg_clrbps( remove_bps );
1✔
482

483
  dap::SetBreakpointsResponse response;
1✔
484

485
  auto breakpoints = request.breakpoints.value( {} );
1✔
486
  response.breakpoints.resize( breakpoints.size() );
1✔
487

488
  for ( size_t breakpoint_index = 0; breakpoint_index < breakpoints.size(); breakpoint_index++ )
3✔
489
  {
490
    // Conditional breakpoints are not supported.
491
    if ( breakpoints[breakpoint_index].condition )
2✔
492
    {
493
      response.breakpoints[breakpoint_index].verified = false;
×
494
      continue;
×
495
    }
496

497
    for ( unsigned i = 0; i < _script->dbg_filenum.size(); ++i )
244✔
498
    {
499
      if ( _script->dbg_filenum[i] == filenum &&
486✔
500
           _script->dbg_linenum[i] == breakpoints[breakpoint_index].line )
242✔
501
      {
502
        response.breakpoints[breakpoint_index].verified = true;
2✔
503
        uoexec->dbg_setbp( i );
2✔
504
        break;
2✔
505
      }
506
    }
507
  }
508

509
  return response;
1✔
510
}
1✔
511

512
dap::ResponseOrError<dap::SourceResponse> DebugClientThread::handle_source(
×
513
    const dap::SourceRequest& request )
514
{
515
  PolLock lock;
×
516
  if ( !_uoexec_wptr.exists() )
×
517
  {
518
    return dap::Error( "No script attached." );
×
519
  }
520

521
  size_t filenum = request.sourceReference;
×
522
  if ( filenum >= _script->dbg_filenames.size() )
×
523
  {
524
    return dap::Error( "File out of range: '%d'", int( filenum ) );
×
525
  }
526

527
  auto filepath = _script->dbg_filenames[filenum];
×
528

529
  std::ifstream ifs( filepath );
×
530
  if ( !ifs.is_open() )
×
531
    return dap::Error( "File # out of range" );
×
532

533
  std::stringstream buffer;
×
534
  buffer << ifs.rdbuf();
×
535

536
  dap::SourceResponse response;
×
537
  response.content = buffer.str();
×
538
  return response;
×
539
}
×
540

541
dap::ResponseOrError<dap::StackTraceResponse> DebugClientThread::handle_stackTrace(
9✔
542
    const dap::StackTraceRequest& )
543
{
544
  PolLock lock;
9✔
545
  if ( !_uoexec_wptr.exists() )
9✔
546
  {
547
    return dap::Error( "No script attached." );
×
548
  }
549

550
  UOExecutor* exec = _uoexec_wptr.get_weakptr();
9✔
551

552
  if ( !exec->halt() )
9✔
553
  {
554
    return dap::Error( "Script must be halted to retrieve stack trace." );
×
555
  }
556

557
  std::vector<ReturnContext> stack = exec->ControlStack;
9✔
558
  dap::StackTraceResponse response;
9✔
559

560
  unsigned int PC;
561

562
  {
563
    ReturnContext rc;
9✔
564
    rc.PC = exec->PC;
9✔
565
    rc.ValueStackDepth = static_cast<unsigned int>( exec->ValueStack.size() );
9✔
566
    stack.push_back( rc );
9✔
567
  }
9✔
568

569
  // Bottom frame starts at index 1
570
  auto frameId = exec->ControlStack.size() + 1;
9✔
571

572
  while ( !stack.empty() )
24✔
573
  {
574
    ReturnContext& rc = stack.back();
15✔
575
    PC = rc.PC;
15✔
576
    stack.pop_back();
15✔
577

578
    dap::Source source;
15✔
579

580
    auto filepath = _script->dbg_filenames[_script->dbg_filenum[PC]];
15✔
581
    fs::path p( filepath );
15✔
582
    std::string abs_path = ( p.is_relative() ? ( fs::current_path() / p ) : p ).string();
15✔
583

584
    source.name = abs_path;
15✔
585
    source.path = abs_path;
15✔
586

587
    dap::StackFrame frame;
15✔
588
    frame.line = _script->dbg_linenum[PC];
15✔
589
    frame.column = 1;
15✔
590
    auto dbgFunction = std::find_if( _script->dbg_functions.begin(), _script->dbg_functions.end(),
15✔
591
                                     [&]( auto& i ) { return i.firstPC <= PC && PC <= i.lastPC; } );
55✔
592

593
    if ( dbgFunction != _script->dbg_functions.end() )
15✔
594
    {
595
      frame.name = dbgFunction->name;
6✔
596
    }
597
    else
598
    {
599
      frame.name = "(program)";
9✔
600
    }
601

602
    frame.id = frameId--;
15✔
603
    frame.source = source;
15✔
604

605
    response.stackFrames.push_back( frame );
15✔
606
  }
15✔
607

608
  return response;
9✔
609
}
9✔
610

611
dap::ResponseOrError<dap::ScopesResponse> DebugClientThread::handle_scopes(
4✔
612
    const dap::ScopesRequest& request )
613
{
614
  PolLock lock;
4✔
615
  if ( !_uoexec_wptr.exists() )
4✔
616
  {
617
    return dap::Error( "No script attached." );
×
618
  }
619

620
  UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
4✔
621
  if ( !uoexec->halt() )
4✔
622
  {
623
    return dap::Error( "Script must be halted to retrieve scopes." );
×
624
  }
625

626
  size_t frameId = request.frameId;
4✔
627

628
  // Frame IDs start a 1
629
  if ( frameId > uoexec->ControlStack.size() + 1 )
4✔
630
  {
631
    return dap::Error( "Unknown frameId '%d'", frameId );
×
632
  }
633

634
  if ( !_global_scope_handle )
4✔
635
  {
636
    _global_scope_handle = _variable_handles.create( GlobalReference( uoexec, _script.get() ) );
4✔
637
  }
638

639
  dap::ScopesResponse response;
4✔
640

641
  {
642
    dap::Scope scope;
4✔
643
    scope.name = "Locals @ " + Clib::tostring( frameId );
4✔
644
    scope.presentationHint = "locals";
4✔
645
    scope.variablesReference =
646
        _variable_handles.create( FrameReference( uoexec, _script.get(), frameId - 1 ) );
4✔
647
    response.scopes.push_back( scope );
4✔
648
  }
4✔
649

650
  {
651
    dap::Scope scope;
4✔
652
    scope.name = "Globals";
4✔
653
    scope.variablesReference = _global_scope_handle;
4✔
654
    response.scopes.push_back( scope );
4✔
655
  }
4✔
656

657
  return response;
4✔
658
}
4✔
659

660
dap::ResponseOrError<dap::VariablesResponse> DebugClientThread::handle_variables(
5✔
661
    const dap::VariablesRequest& request )
662
{
663
  PolLock lock;
5✔
664
  if ( !_uoexec_wptr.exists() )
5✔
665
  {
666
    return dap::Error( "No script attached." );
×
667
  }
668

669
  UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
5✔
670
  if ( !uoexec->halt() )
5✔
671
  {
672
    return dap::Error( "Script must be halted to retrieve variables." );
×
673
  }
674

675
  dap::VariablesResponse response;
5✔
676

677
  auto reference_ptr = _variable_handles.get( static_cast<int>( request.variablesReference ) );
5✔
678

679
  if ( reference_ptr == nullptr )
5✔
680
  {
681
    return dap::Error( "Invalid variablesReference id %d", int( request.variablesReference ) );
×
682
  }
683

684
  std::visit(
5✔
685
      [&]( auto&& arg )
10✔
686
      {
687
        using T = std::decay_t<decltype( arg )>;
688
        if constexpr ( (std::is_same_v<T, GlobalReference>) || (std::is_same_v<T, FrameReference>))
689
        {
690
          for ( auto const& [key, member] : arg.contents )
10✔
691
          {
692
            dap::Variable current_var;
8✔
693
            current_var.name = key;
8✔
694
            _variable_handles.set_response_details( *member, current_var );
11✔
695
            response.variables.push_back( std::move( current_var ) );
11✔
696
          }
697
        }
698
        else if constexpr ( std::is_same_v<T, VariableReference> )
699
        {
700
          response.variables = _variable_handles.to_variables( arg );
3✔
701
        }
702
      },
5✔
703
      *reference_ptr );
704

705
  return response;
5✔
706
}
5✔
707

708
dap::ResponseOrError<dap::InitializeResponse> DebugClientThread::handle_initialize(
2✔
709
    const dap::PolInitializeRequest& request )
710
{
711
  if ( !Plib::systemstate.config.debug_password.empty() &&
2✔
712
       request.password.value( "" ) != Plib::systemstate.config.debug_password )
2✔
713
    return dap::Error( "Password not recognized." );
×
714

715
  dap::InitializeResponse response;
2✔
716
  response.supportsSetVariable = true;
2✔
717
  return response;
2✔
718
}
2✔
719

720
dap::ResponseOrError<dap::StepOutResponse> DebugClientThread::handle_stepOut(
1✔
721
    const dap::StepOutRequest& )
722
{
723
  PolLock lock;
1✔
724
  if ( !_uoexec_wptr.exists() )
1✔
725
  {
726
    return dap::Error( "No script attached." );
×
727
  }
728

729
  UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
1✔
730
  if ( !uoexec->in_debugger_holdlist() )
1✔
731
  {
732
    return dap::Error( "Script not ready to trace." );
×
733
  }
734

735
  _variable_handles.reset();
1✔
736
  _global_scope_handle = 0;
1✔
737
  uoexec->dbg_step_out();
1✔
738
  uoexec->revive_debugged();
1✔
739
  return dap::StepOutResponse{};
1✔
740
}
1✔
741

742
dap::ResponseOrError<dap::DisconnectResponse> DebugClientThread::handle_disconnect(
×
743
    const dap::DisconnectRequest& )
744
{
745
  PolLock lock;
×
746
  if ( _uoexec_wptr.exists() )
×
747
  {
748
    UOExecutor* exec = _uoexec_wptr.get_weakptr();
×
749

750
    if ( _was_launch_requested )
×
751
    {
752
      exec->seterror( true );
×
753
      exec->revive();
×
754
    }
755

756
    if ( exec->in_debugger_holdlist() )
×
757
      exec->revive_debugged();
×
758

759
    _variable_handles.reset();
×
760
    _global_scope_handle = 0;
×
761
    exec->detach_debugger();
×
762
    _uoexec_wptr.clear();
×
763
  }
764

765
  return dap::DisconnectResponse{};
×
766
}
×
767

768
void DebugClientThread::after_pause( const dap::ResponseOrError<dap::PauseResponse>& )
1✔
769
{
770
  PolLock lock;
1✔
771
  if ( !_uoexec_wptr.exists() )
1✔
772
  {
773
    return;
×
774
  }
775

776
  UOExecutor* uoexec = _uoexec_wptr.get_weakptr();
1✔
777
  if ( !uoexec->halt() )
1✔
778
  {
779
    return;
1✔
780
  }
781

782
  on_halt();
×
783
}
1✔
784

785
void DebugClientThread::after_initialize(
2✔
786
    const dap::ResponseOrError<dap::InitializeResponse>& response )
787
{
788
  if ( response.error )
2✔
789
    _rw->close();
×
790
  else
791
    _session->send( dap::InitializedEvent() );
2✔
792
}
2✔
793

794
void DebugClientThread::after_disconnect( const dap::ResponseOrError<dap::DisconnectResponse>& )
×
795
{
796
  _rw->close();
×
797
}
×
798

799
void DebugClientThread::after_attach( const dap::ResponseOrError<dap::AttachResponse>& )
1✔
800
{
801
  PolLock lock;
1✔
802
  if ( _uoexec_wptr.exists() )
1✔
803
    if ( _uoexec_wptr.get_weakptr()->halt() )
1✔
804
    {
805
      on_halt();
1✔
806
    }
807
}
1✔
808

809
void DebugClientThread::on_error( const char* msg )
×
810
{
811
  POLLOG_ERRORLN( "Debugger#{} session error: {}", _instance, msg );
×
812
  _rw->close();
×
813
}
×
814

815
void DebugClientThread::run()
2✔
816
{
817
  POLLOG_INFOLN( "Debugger#{} client thread started.", _instance );
2✔
818

819
  // Session event handlers that are only attached once initialized with the password (if
820
  // required).
821
  auto attach_authorized_handlers = [this]()
80✔
822
  {
823
    _session->registerHandler( [this]( const dap::ConfigurationDoneRequest& request )
2✔
824
                               { return handle_configurationDone( request ); } );
×
825

826
    _session->registerHandler( [this]( const dap::PolAttachRequest& request )
2✔
827
                               { return handle_attach( request ); } );
1✔
828

829
    // After sending an AttachResponse, check if executor is halted. If so, trigger the on_halt()
830
    // event.
831
    _session->registerSentHandler(
2✔
832
        [this]( const dap::ResponseOrError<dap::AttachResponse>& response )
2✔
833
        { after_attach( response ); } );
1✔
834

835
    _session->registerHandler( [this]( const dap::PolLaunchRequest& request )
2✔
836
                               { return handle_launch( request ); } );
1✔
837

838
    _session->registerHandler( [this]( const dap::PolProcessesRequest& request )
2✔
839
                               { return handle_processes( request ); } );
1✔
840

841
    _session->registerHandler( [this]( const dap::ThreadsRequest& request )
2✔
842
                               { return handle_threads( request ); } );
2✔
843

844
    _session->registerHandler( [this]( const dap::SetExceptionBreakpointsRequest& request )
2✔
845
                               { return handle_setExceptionBreakpoints( request ); } );
×
846

847
    _session->registerHandler( [this]( const dap::ContinueRequest& request )
2✔
848
                               { return handle_continue( request ); } );
4✔
849

850
    _session->registerHandler( [this]( const dap::PauseRequest& request )
2✔
851
                               { return handle_pause( request ); } );
1✔
852

853
    _session->registerHandler( [this]( const dap::EvaluateRequest& request )
2✔
854
                               { return handle_evaluate( request ); } );
12✔
855

856
    _session->registerHandler( [this]( const dap::SetVariableRequest& request )
2✔
857
                               { return handle_setVariable( request ); } );
2✔
858

859
    // After sending a PauseResponse, check if the script is paused. If so, send a StoppedEvent.
860
    _session->registerSentHandler(
2✔
861
        [this]( const dap::ResponseOrError<dap::PauseResponse>& response )
2✔
862
        { after_pause( response ); } );
1✔
863

864
    _session->registerHandler( [this]( const dap::NextRequest& request )
2✔
865
                               { return handle_next( request ); } );
2✔
866

867
    _session->registerHandler( [this]( const dap::StepInRequest& request )
2✔
868
                               { return handle_stepIn( request ); } );
1✔
869

870
    _session->registerHandler( [this]( const dap::SetBreakpointsRequest& request )
2✔
871
                               { return handle_setBreakpoints( request ); } );
1✔
872

873
    _session->registerHandler( [this]( const dap::SourceRequest& request )
2✔
874
                               { return handle_source( request ); } );
×
875

876
    _session->registerHandler( [this]( const dap::StackTraceRequest& request )
2✔
877
                               { return handle_stackTrace( request ); } );
9✔
878

879
    _session->registerHandler( [this]( const dap::ScopesRequest& request )
2✔
880
                               { return handle_scopes( request ); } );
4✔
881

882
    _session->registerHandler( [this]( const dap::VariablesRequest& request )
2✔
883
                               { return handle_variables( request ); } );
5✔
884

885
    _session->registerHandler( [this]( const dap::StepOutRequest& request )
2✔
886
                               { return handle_stepOut( request ); } );
1✔
887
  };
2✔
888

889
  // Session event handlers added before initialization
890
  _session->registerHandler(
2✔
891
      [&]( const dap::PolInitializeRequest& request )
2✔
892
      {
893
        auto response = handle_initialize( request );
2✔
894
        if ( !response.error )
2✔
895
        {
896
          attach_authorized_handlers();
2✔
897
        }
898
        return response;
2✔
899
      } );
×
900

901
  _session->registerHandler( [this]( const dap::DisconnectRequest& request )
2✔
902
                             { return handle_disconnect( request ); } );
×
903

904
  _session->registerSentHandler(
2✔
905
      [this]( const dap::ResponseOrError<dap::DisconnectResponse>& response )
2✔
906
      { after_disconnect( response ); } );
×
907

908
  _session->registerSentHandler(
2✔
909
      [this]( const dap::ResponseOrError<dap::InitializeResponse>& response )
2✔
910
      { after_initialize( response ); } );
2✔
911

912
  // Error handler
913
  _session->onError( [this]( const char* msg ) { on_error( msg ); } );
2✔
914

915
  std::atomic_bool socket_closed{ false };
2✔
916
  // Attach the SocketReaderWriter to the Session and begin processing events.
917
  // expects that the close handler is called everytime the socket gets closed
918
  _session->bind( _rw,
4✔
919
                  [&]()
2✔
920
                  {
921
                    socket_closed = true;
2✔
922
                    POLLOG_INFOLN( "Debugger#{} session endpoint closed.", _instance );
2✔
923
                  } );
2✔
924

925
  // MacOS implementation has a race between isOpen and close.
926
  // never call isOpen from non-dap threads
927
  while ( !Clib::exit_signalled && !_exit_sent && !socket_closed )
10✔
928
  {
929
    pol_sleep_ms( 1000 );
8✔
930
  }
931

932
  {
933
    // Detach debugger in case a DisconnectRequest was not sent.
934
    PolLock lock;
2✔
935
    if ( _uoexec_wptr.exists() )
2✔
936
    {
937
      UOExecutor* exec = _uoexec_wptr.get_weakptr();
×
938
      if ( exec->in_debugger_holdlist() )
×
939
        exec->revive_debugged();
×
940

941
      _variable_handles.reset();
×
942
      _global_scope_handle = 0;
×
943
      exec->detach_debugger();
×
944
      _uoexec_wptr.clear();
×
945
    }
946
  }
2✔
947

948
  _rw.reset();
2✔
949
  _session.reset();
2✔
950

951
  POLLOG_INFOLN( "Debugger#{} client thread closing.", _instance );
2✔
952
}
2✔
953
}  // namespace DAP
954
}  // namespace Network
955
}  // namespace Pol
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc