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

eXist-db / exist / 40387836984

26 Oct 2026 07:33PM UTC coverage: 58.173% (+0.005%) from 58.168%
40387836984

push

github

web-flow
Merge pull request #6731 from line-o/bugfix/binary-value-frame-ownership

[bugfix] Release a binary value with the scope that created it

32491 of 61752 branches covered (52.62%)

Branch coverage included in aggregate %.

92 of 129 new or added lines in 8 files covered. (71.32%)

27 existing lines in 6 files now uncovered.

85598 of 141244 relevant lines covered (60.6%)

2.73 hits per line

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

74.57
/exist-core/src/main/java/org/exist/xquery/ModuleContext.java
1
/*
2
 * eXist-db Open Source Native XML Database
3
 * Copyright (C) 2001 The eXist-db Authors
4
 *
5
 * info@exist-db.org
6
 * http://www.exist-db.org
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
 */
22
package org.exist.xquery;
23

24
import org.apache.logging.log4j.LogManager;
25
import org.apache.logging.log4j.Logger;
26
import org.exist.debuggee.DebuggeeJoint;
27
import org.exist.dom.persistent.DocumentSet;
28
import org.exist.dom.QName;
29
import org.exist.dom.memtree.MemTreeBuilder;
30
import org.exist.security.Subject;
31
import org.exist.storage.UpdateListener;
32
import org.exist.util.FileUtils;
33
import org.exist.xmldb.XmldbURI;
34
import org.exist.xquery.value.AnyURIValue;
35
import org.exist.xquery.value.BinaryValue;
36
import org.exist.xquery.value.Sequence;
37

38
import javax.annotation.Nullable;
39
import javax.xml.datatype.XMLGregorianCalendar;
40

41
import java.net.URISyntaxException;
42
import java.util.Iterator;
43
import java.util.List;
44
import java.util.Map;
45

46
import org.exist.xquery.value.StringValue;
47
import org.exist.xquery.value.ValueSequence;
48

49

50
/**
51
 * Subclass of {@link org.exist.xquery.XQueryContext} for imported modules.
52
 *
53
 * @author wolf
54
 * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
55
 */
56
public class ModuleContext extends XQueryContext {
57

58
    private static final Logger LOG = LogManager.getLogger(ModuleContext.class);
4 ✔
59

60
    private XQueryContext parentContext;
61
    private String moduleNamespace;
62
    private String modulePrefix;
63
    private final String location;
64

65
    public ModuleContext(final XQueryContext parentContext, final String moduleNamespace, final String modulePrefix, final String location) {
66
        super(parentContext != null ? parentContext.db : null,
6 !
67
                parentContext != null ? parentContext.getConfiguration() : null,
5 !
68
                null,
1 ✔
69
                false);
2 ✔
70
        this.moduleNamespace = moduleNamespace;
3 ✔
71
        this.modulePrefix = modulePrefix;
3 ✔
72
        this.location = location;
3 ✔
73

74
        setParentContext(parentContext);
3 ✔
75

76
        loadDefaults(this.configuration);
4 ✔
77
    }
1 ✔
78

79
    @Override
80
    public Subject getRealUser() {
81
        //the real and effective users are set at execution time on the root XQuery Context
82
        return getRootContext().getRealUser();
4 ✔
83
    }
84

85
    String getLocation() {
86
        return location;
3 ✔
87
    }
88

89
    String getModuleNamespace() {
90
        return moduleNamespace;
3 ✔
91
    }
92

93
    public void setModuleNamespace(final String prefix, final String namespaceURI) {
94
        this.modulePrefix = prefix;
3 ✔
95
        this.moduleNamespace = namespaceURI;
3 ✔
96
    }
1 ✔
97

98
    @Override
99
    protected void addModuleVertex(final ModuleVertex moduleVertex) {
100
        getRootContext().addModuleVertex(moduleVertex);
4 ✔
101
    }
1 ✔
102

103
    protected boolean hasModuleVertex(final ModuleVertex moduleVertex) {
104
        return getRootContext().hasModuleVertex(moduleVertex);
5 ✔
105
    }
106

107
    @Override
108
    protected void addModuleEdge(final ModuleVertex source, final ModuleVertex sink) {
109
        getRootContext().addModuleEdge(source, sink);
5 ✔
110
    }
1 ✔
111

112
    @Override
113
    protected boolean hasModulePath(final ModuleVertex source, final ModuleVertex sink) {
114
        return getRootContext().hasModulePath(source, sink);
6 ✔
115
    }
116

117
    @Override
118
    public @Nullable Module[] importModule(@Nullable String namespaceURI, @Nullable String prefix, @Nullable AnyURIValue[] locationHints) throws XPathException {
119
        final ModuleVertex thisModuleVertex = new ModuleVertex(moduleNamespace, location);
8 ✔
120

121
        for (final AnyURIValue locationHint : locationHints) {
16 ✔
122
            final ModuleVertex imporedModuleVertex = new ModuleVertex(namespaceURI, locationHint.toString());
7 ✔
123

124
            if (!hasModuleVertex(imporedModuleVertex)) {
4 ✔
125
                addModuleVertex(imporedModuleVertex);
3 ✔
126
            } else {
1 ✔
127
                // Check if there is already a path from the imported module to this module
128
                if (getXQueryVersion() == 10 && namespaceURI != null && locationHints != null && hasModulePath(imporedModuleVertex, thisModuleVertex)) {
13 !
129
                    throw new XPathException(ErrorCodes.XQST0093, "Detected cyclic import between modules: " + getModuleNamespace() + " at: " + getLocation() + ", and: " + namespaceURI + " at: " + locationHint);
13 ✔
130
                }
131
            }
132

133
            if (!hasModuleVertex(thisModuleVertex)) {
4 ✔
134
                // NOTE(AR) may occur when the actual module has a different namespace from that of the `import module namespace`... will later raise an XQST0047 error
135
                addModuleVertex(thisModuleVertex);
3 ✔
136
            }
137

138
            addModuleEdge(thisModuleVertex, imporedModuleVertex);
4 ✔
139
        }
140

141
        return super.importModule(namespaceURI, prefix, locationHints);
6 ✔
142
    }
143

144
    @Override
145
    protected @Nullable Module importModuleFromLocation(final String namespaceURI, @Nullable final String prefix, final AnyURIValue locationHint) throws XPathException {
146
        // guard against self-recursive import - see: https://github.com/eXist-db/exist/issues/3448
147
        if (moduleNamespace.equals(namespaceURI) && location.equals(locationHint.toString())) {
5 !
148
            final StringBuilder builder = new StringBuilder("The XQuery Library Module '");
×
149
            builder.append(namespaceURI);
×
150
            builder.append("'");
×
151
            if (locationHint != null) {
×
152
                builder.append(" at '");
×
153
                builder.append(location);
×
154
                builder.append("'");
×
155
            }
156
            builder.append(" has invalidly attempted to import itself; this will be skipped!");
×
157
            LOG.warn(builder.toString());
×
158

159
            return null;
×
160
        }
161

162
        return super.importModuleFromLocation(namespaceURI, prefix, locationHint);
6 ✔
163
    }
164

165
    @Override
166
    protected void setModulesChanged() {
167
        parentContext.setModulesChanged();
×
168
    }
×
169

170
    private void setParentContext(final XQueryContext parentContext) {
171
        this.parentContext = parentContext;
3 ✔
172
        //XXX: raise error on null!
173
        if (parentContext != null) {
2 !
174
            this.baseURI = parentContext.baseURI;
4 ✔
175
            try {
176
                if (location.startsWith(XmldbURI.XMLDB_URI_PREFIX) ||
5 ✔
177
                        (location.indexOf(':') < 0 &&
5 ✔
178
                                parentContext.getModuleLoadPath().startsWith(XmldbURI.XMLDB_URI_PREFIX))) {
5 ✔
179
                    // use XmldbURI resolution - unfortunately these are not interpretable as URIs
180
                    // because the scheme xmldb:exist: is not a valid URI scheme
181
                    final XmldbURI locationUri = XmldbURI.xmldbUriFor(FileUtils.dirname(location));
5 ✔
182
                    if (".".equals(parentContext.getModuleLoadPath())) {
5 ✔
183
                        setModuleLoadPath(locationUri.toString());
4 ✔
184
                    } else {
1 ✔
185
                        try {
186
                            final XmldbURI parentLoadUri = XmldbURI.xmldbUriFor(parentContext.getModuleLoadPath());
4 ✔
187
                            final XmldbURI moduleLoadUri = parentLoadUri.resolveCollectionPath(locationUri);
4 ✔
188
                            setModuleLoadPath(moduleLoadUri.toString());
4 ✔
189
                        } catch (final URISyntaxException e) {
2 ✔
190
                            setModuleLoadPath(locationUri.toString());
4 ✔
191
                        }
192
                    }
193
                } else {
1 ✔
194
                    final String dir = FileUtils.dirname(location);
4 ✔
195
                    if (dir.matches("^[A-Za-z]+:.*")) {
4 ✔
196
                        setModuleLoadPath(dir);
3 ✔
197
                    } else if (".".equals(parentContext.moduleLoadPath)) {
6 ✔
198
                        if (!".".equals(dir)) {
4 !
199
                            if (dir.matches("(?:\\/.*)|(?:[a-zA-Z]:\\\\.*)")) {
4 !
200
                                setModuleLoadPath(dir);
3 ✔
201
                            } else {
1 ✔
202
                                setModuleLoadPath("./" + dir);
×
203
                            }
204
                        }
205
                    } else {
×
206
                        if (dir.startsWith("/")) {
4 !
207
                            setModuleLoadPath(dir);
×
208
                        } else {
×
209
                            setModuleLoadPath(FileUtils.addPaths(parentContext.getModuleLoadPath(), dir));
6 ✔
210
                        }
211
                    }
212
                }
213
            } catch (final URISyntaxException e) {
1 ✔
214
                LOG.warn("Failed to derive module load path from parent context", e);
×
215
            }
216
        }
217
    }
1 ✔
218

219
    private XQueryContext getParentContext() {
220
        return parentContext;
×
221
    }
222

223
    @Override
224
    public boolean hasParent() {
225
        return true;
×
226
    }
227

228
    @Override
229
    public XQueryContext getRootContext() {
230
        return parentContext.getRootContext();
4 ✔
231
    }
232

233
    @Override
234
    public void updateContext(final XQueryContext from) {
235
        if (from.hasParent()) {
×
236
            // TODO: shouldn't this call setParentContext ? - sokolov
237
            this.parentContext = ((ModuleContext) from).parentContext;
×
238
        }
239
    }
×
240

241
    @Override
242
    public XQueryContext copyContext() {
243
        final ModuleContext ctx = new ModuleContext(parentContext, moduleNamespace, modulePrefix, location);
12 ✔
244
        copyFields(ctx);
3 ✔
245
        try {
246
            ctx.declareNamespace(modulePrefix, moduleNamespace);
6 ✔
247
        } catch (final XPathException e) {
1 ✔
248
            LOG.error(e);
×
249
        }
250
        return ctx;
2 ✔
251
    }
252

253
    @Override
254
    public void addDynamicOption(final String name, final String value) throws XPathException {
255
        parentContext.addDynamicOption(name, value);
×
256
    }
×
257

258
    @Override
259
    public DocumentSet getStaticallyKnownDocuments() throws XPathException {
260
        return parentContext.getStaticallyKnownDocuments();
4 ✔
261
    }
262

263
    @Override
264
    public Module[] getModules(final String namespaceURI) {
265
        Module[] modules = super.getModules(namespaceURI);
4 ✔
266
        // TODO: I don't think modules should be able to access their parent context's modules,
267
        // since that breaks lexical scoping.  However, it seems that some eXist modules rely on
268
        // this so let's leave it for now.  (pkaminsk2)
269
        if (modules == null) {
2 ✔
270
            modules = parentContext.getModules(namespaceURI);
5 ✔
271
        }
272
        return modules;
2 ✔
273
    }
274

275
    @Override
276
    protected void setRootModules(final String namespaceURI, final Module[] modules) {
277
        parentContext.setRootModules(namespaceURI, modules);
5 ✔
278
    }
1 ✔
279

280
    @Override
281
    protected void addRootModule(final String namespaceURI, final Module module) {
282
        parentContext.addRootModule(namespaceURI, module);
5 ✔
283
    }
1 ✔
284

285
    @Override
286
    public Iterator<Module> getRootModules() {
287
        return parentContext.getRootModules();
4 ✔
288
    }
289

290
    @Override
291
    public Iterator<Module> getAllModules() {
292
        return parentContext.getAllModules();
×
293
    }
294

295
    @Override
296
    public Module[] getRootModules(final String namespaceURI) {
297
        return parentContext.getRootModules(namespaceURI);
5 ✔
298
    }
299

300
    @Override
301
    final protected XPathException moduleLoadException(final String message, final String moduleLocation) throws XPathException {
302
        return moduleLoadException(message, moduleLocation, null);
×
303
    }
304

305
    @Override
306
    final protected XPathException moduleLoadException(final String message, final String moduleLocation, final Exception e) throws XPathException {
307
        String dependantModule;
308
        try {
309
            if (location != null && location.startsWith(XmldbURI.LOCAL_DB)) {
×
310
                dependantModule = location;
×
311
            } else {
×
312
                dependantModule = XmldbURI.create(getParentContext().getModuleLoadPath(), false).append(location).toString();
×
313
            }
314
        } catch (final Exception ex) {
×
315
            dependantModule = location;
×
316
        }
317

318
        if (e == null) {
×
319
            return new XPathException(getRootExpression(), ErrorCodes.XQST0059, message, new ValueSequence(new StringValue(getRootExpression(), moduleLocation), new StringValue(getRootExpression(), dependantModule)));
×
320
        } else {
321
            return new XPathException(getRootExpression(), ErrorCodes.XQST0059, message, new ValueSequence(new StringValue(getRootExpression(), moduleLocation), new StringValue(getRootExpression(), dependantModule)), e);
×
322
        }
323
    }
324

325
    @Override
326
    public XQueryWatchDog getWatchDog() {
327
        return parentContext.getWatchDog();
4 ✔
328
    }
329

330
    @Override
331
    public Profiler getProfiler() {
332
        return parentContext.getProfiler();
4 ✔
333
    }
334

335
    @Override
336
    public XMLGregorianCalendar getCalendar() {
337
        return parentContext.getCalendar();
4 ✔
338
    }
339

340
    @Override
341
    public AnyURIValue getBaseURI() throws XPathException {
342
        return parentContext.getBaseURI();
4 ✔
343
    }
344

345
    @Override
346
    public void setBaseURI(final AnyURIValue uri) {
347
        parentContext.setBaseURI(uri);
×
348
    }
×
349

350
    @Override
351
    public MemTreeBuilder getDocumentBuilder() {
352
        return parentContext.getDocumentBuilder();
4 ✔
353
    }
354

355
    @Override
356
    public MemTreeBuilder getDocumentBuilder(final boolean explicitCreation) {
357
        return parentContext.getDocumentBuilder(explicitCreation);
5 ✔
358
    }
359

360
    @Override
361
    public MemTreeBuilder getCurrentDocumentBuilder() {
362
        return parentContext.getCurrentDocumentBuilder();
4 ✔
363
    }
364

365
    @Override
366
    public void pushDocumentContext() {
367
        parentContext.pushDocumentContext();
3 ✔
368
    }
1 ✔
369

370
    @Override
371
    public LocalVariable markLocalVariables(final boolean newContext) {
372
        return parentContext.markLocalVariables(newContext);
5 ✔
373
    }
374

375
    @Override
376
    public void popLocalVariables(final LocalVariable var) {
377
        parentContext.popLocalVariables(var);
4 ✔
378
    }
1 ✔
379

380
    @Override
381
    public void popLocalVariables(final LocalVariable var, @Nullable final Sequence resultSequence) {
382
        parentContext.popLocalVariables(var, resultSequence);
5 ✔
383
    }
1 ✔
384

385
    @Override
386
    public LocalVariable declareVariableBinding(final LocalVariable var) throws XPathException {
387
        return parentContext.declareVariableBinding(var);
5 ✔
388
    }
389

390
    @Override
391
    protected Variable resolveLocalVariable(final QName qname) throws XPathException {
392
        return parentContext.resolveLocalVariable(qname);
5 ✔
393
    }
394

395
    @Override
396
    public Variable resolveVariable(final QName qname) throws XPathException {
397
        // check if the variable is declared local
398
        Variable var = resolveLocalVariable(qname);
4 ✔
399

400
        // check if the variable is declared in a module
401
        if (var == null) {
2 ✔
402
            Module[] modules;
403
            if (moduleNamespace.equals(qname.getNamespaceURI())) {
6 ✔
404
                modules = getRootModules(moduleNamespace);
5 ✔
405
            } else {
1 ✔
406
                modules = getModules(qname.getNamespaceURI());
5 ✔
407
            }
408
            if (modules != null) {
2 ✔
409
                for (final Module module : modules) {
16 ✔
410
                    var = module.resolveVariable(qname);
4 ✔
411
                    if (var != null) {
2 ✔
412
                        break;
1 ✔
413
                    }
414
                }
415
            }
416
        }
417

418
        // check if the variable is declared global
419
        if (var == null) {
2 ✔
420
            var = globalVariables.get(qname);
6 ✔
421
        }
422
        //if (var == null)
423
        //        throw new XPathException("variable $" + qname + " is not bound");
424
        return var;
2 ✔
425
    }
426

427
    @Override
428
    public Map<QName, Variable> getVariables() {
429
        return parentContext.getVariables();
×
430
    }
431

432
    @Override
433
    public Map<QName, Variable> getLocalVariables() {
434
        return parentContext.getLocalVariables();
×
435
    }
436

437
    @Override
438
    public List<ClosureVariable> getLocalStack() {
439
        return parentContext.getLocalStack();
4 ✔
440
    }
441

442
    @Override
443
    public Map<QName, Variable> getGlobalVariables() {
444
        return parentContext.getGlobalVariables();
×
445
    }
446

447
    @Nullable
448
    @Override
449
    public HttpContext getHttpContext() {
450
        return parentContext.getHttpContext();
4 ✔
451
    }
452

453
    @Override
454
    public void setHttpContext(final HttpContext httpContext) {
455
        parentContext.setHttpContext(httpContext);
×
456
    }
×
457

458
    @Override
459
    public void restoreStack(final List<ClosureVariable> stack) throws XPathException {
460
        parentContext.restoreStack(stack);
4 ✔
461
    }
1 ✔
462

463
    @Override
464
    public int getCurrentStackSize() {
465
        return parentContext.getCurrentStackSize();
4 ✔
466
    }
467

468
    @Override
469
    public void popDocumentContext() {
470
        parentContext.popDocumentContext();
3 ✔
471
    }
1 ✔
472

473
    /**
474
     * First checks the parent context for in-scope namespaces,
475
     * then the module's static context.
476
     *
477
     * @param prefix the prefix to look up
478
     * @return the namespace currently mapped to that prefix
479
     */
480
    @Override
481
    public String getURIForPrefix(final String prefix) {
482
        String uri = getInScopeNamespace(prefix);
4 ✔
483
        if (uri != null) {
2 ✔
484
            return uri;
2 ✔
485
        }
486
        //TODO : test NS inheritance
487
        uri = getInheritedNamespace(prefix);
4 ✔
488
        if (uri != null) {
2 ✔
489
            return uri;
2 ✔
490
        }
491
        // Check global declarations
492
        return staticNamespaces.get(prefix);
6 ✔
493
    }
494

495
    /**
496
     * First checks the parent context for in-scope namespaces,
497
     * then the module's static context.
498
     *
499
     * @param uri the URI to look up
500
     * @return a prefix for the URI
501
     */
502
    @Override
503
    public String getPrefixForURI(final String uri) {
504
        String prefix = getInScopePrefix(uri);
4 ✔
505
        if (prefix != null) {
2 !
506
            return prefix;
×
507
        }
508
        //TODO : test the NS inheritance
509
        prefix = getInheritedPrefix(uri);
4 ✔
510
        if (prefix != null) {
2 !
511
            return prefix;
×
512
        }
513
        return staticPrefixes.get(uri);
6 ✔
514
    }
515

516
    @Override
517
    public String getInScopeNamespace(final String prefix) {
518
        return parentContext.getInScopeNamespace(prefix);
5 ✔
519
    }
520

521
    @Override
522
    public String getInScopePrefix(final String uri) {
523
        return parentContext.getInScopePrefix(uri);
5 ✔
524
    }
525

526
    @Override
527
    public String getInheritedNamespace(final String prefix) {
528
        return parentContext.getInheritedNamespace(prefix);
5 ✔
529
    }
530

531
    @Override
532
    public Map<String, String> getAllInheritedNamespaces() {
533
        return parentContext.getAllInheritedNamespaces();
4 ✔
534
    }
535

536
    @Override
537
    public String getInheritedPrefix(final String uri) {
538
        return parentContext.getInheritedPrefix(uri);
5 ✔
539
    }
540

541
    @Override
542
    public void declareInScopeNamespace(final String prefix, final String uri) {
543
        parentContext.declareInScopeNamespace(prefix, uri);
5 ✔
544
    }
1 ✔
545

546
    @Override
547
    public void pushInScopeNamespaces(final boolean inherit) {
548
        parentContext.pushInScopeNamespaces(inherit);
4 ✔
549
    }
1 ✔
550

551
    @Override
552
    public void pushInScopeNamespaces() {
553
        parentContext.pushInScopeNamespaces();
3 ✔
554
    }
1 ✔
555

556
    @Override
557
    public void popInScopeNamespaces() {
558
        parentContext.popInScopeNamespaces();
3 ✔
559
    }
1 ✔
560

561
    @Override
562
    public void addImportedContext(final XQueryContext importedContext) {
563
        parentContext.addImportedContext(importedContext);
4 ✔
564
    }
1 ✔
565

566
    @Override
567
    public void registerUpdateListener(final UpdateListener listener) {
568
        parentContext.registerUpdateListener(listener);
4 ✔
569
    }
1 ✔
570

571
    @Override
572
    protected void clearUpdateListeners() {
573
        // will be cleared by the parent context
574
    }
1 ✔
575

576
    @Override
577
    public DebuggeeJoint getDebuggeeJoint() {
578
        return parentContext.getDebuggeeJoint();
×
579
    }
580

581
    @Override
582
    public boolean isDebugMode() {
583
        return parentContext.isDebugMode();
×
584
    }
585

586
    @Override
587
    public void expressionStart(final Expression expr) throws TerminatedException {
588
        parentContext.expressionStart(expr);
4 ✔
589
    }
1 ✔
590

591
    @Override
592
    public void expressionEnd(final Expression expr) {
593
        parentContext.expressionEnd(expr);
4 ✔
594
    }
1 ✔
595

596
    @Override
597
    public void stackEnter(final Expression expr) throws TerminatedException {
598
        parentContext.stackEnter(expr);
4 ✔
599
    }
1 ✔
600

601
    @Override
602
    public void stackLeave(final Expression expr) {
603
        parentContext.stackLeave(expr);
4 ✔
604
    }
1 ✔
605

606
    @Override
607
    public void registerBinaryValueInstance(final BinaryValue binaryValue) {
608
        parentContext.registerBinaryValueInstance(binaryValue);
4 ✔
609
    }
1 ✔
610

611
    @Override
612
    public void destroyBinaryValue(final BinaryValue binaryValue) {
NEW
613
        parentContext.destroyBinaryValue(binaryValue);
×
NEW
614
    }
×
615

616
    @Override
617
    public void pushBinaryValueFrame() {
618
        parentContext.pushBinaryValueFrame();
3 ✔
619
    }
1 ✔
620

621
    @Override
622
    public void popBinaryValueFrame(@Nullable final Sequence escaping) {
623
        parentContext.popBinaryValueFrame(escaping);
4 ✔
624
    }
1 ✔
625

626
    @Override
627
    public void promoteBinaryValueFrame() {
NEW
628
        parentContext.promoteBinaryValueFrame();
×
NEW
629
    }
×
630

631
    @Override
632
    public void saveState() {
633
        super.saveState();
2 ✔
634
        parentContext.saveState();
3 ✔
635
    }
1 ✔
636
}
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