Merge branch 'master' into PSI
This commit is contained in:
commit
8a5a6fcd27
19 files changed
+333
-130
No files matched your search
+17
-16
@@ -1,5 +1,5 @@
|
||||
# .appveyor.yml for use with EPICS Base ci-scripts
|
||||
# (see: https://github.com/epics-base/ci-scripts)
|
||||
# Appveyor configuration for EPICS 7 submodules
|
||||
# (see also https://github.com/epics-base/ci-scripts)
|
||||
|
||||
# This is YAML - indentation levels are crucial
|
||||
|
||||
@@ -33,24 +33,28 @@ configuration:
|
||||
- dynamic-debug
|
||||
- static-debug
|
||||
|
||||
# Environment variables: compiler toolchain, base version, setup file, ...
|
||||
# Default build worker image
|
||||
# Despite the Appveyor doc's, don't use this image for vs2015
|
||||
image: Visual Studio 2019
|
||||
|
||||
# Environment variables: base version, compiler toolchain, etc.
|
||||
environment:
|
||||
# common / default variables for all jobs
|
||||
SETUP_PATH: .ci-local
|
||||
SETUP_PATH: .ci-local:.ci
|
||||
EPICS_TEST_IMPRECISE_TIMING: YES
|
||||
BASE: 7.0
|
||||
# Verbose builds don't seem to fail, and might be faster!
|
||||
VV: 1
|
||||
|
||||
matrix:
|
||||
- CMP: vs2019
|
||||
BASE: 3.15
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
- CMP: vs2019
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
- BASE: 3.15
|
||||
CMP: vs2019
|
||||
- CMP: vs2017
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
- CMP: vs2015
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||
- CMP: gcc
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
# TODO: static linking w/ readline isn't working. Bypass auto-detect
|
||||
COMMANDLINE_LIBRARY: EPICS
|
||||
|
||||
@@ -71,8 +75,9 @@ matrix:
|
||||
#---------------------------------#
|
||||
|
||||
install:
|
||||
- cmd: set PATH=C:\Python38-x64;%PATH%
|
||||
- cmd: git submodule update --init --recursive
|
||||
- cmd: pip install git+https://github.com/mdavidsaver/ci-core-dumper#egg=ci-core-dumper
|
||||
- cmd: python -m pip install git+https://github.com/mdavidsaver/ci-core-dumper#egg=ci-core-dumper
|
||||
- cmd: python .ci/cue.py prepare
|
||||
|
||||
build_script:
|
||||
@@ -93,9 +98,9 @@ on_failure:
|
||||
# debugging #
|
||||
#---------------------------------#
|
||||
|
||||
## To connect by remote desktop to a failed build, uncomment the lines below.
|
||||
## To connect by remote desktop to a failing build, uncomment the lines below.
|
||||
## You must connect within the usual build timeout limit (60 minutes),
|
||||
## so adjust the build matrix above to just build the config of interest.
|
||||
## so adjust the build matrix above to build only the config of interest.
|
||||
|
||||
#on_failure:
|
||||
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
@@ -106,8 +111,4 @@ on_failure:
|
||||
#---------------------------------#
|
||||
|
||||
notifications:
|
||||
- provider: Email
|
||||
to:
|
||||
- core-talk@aps.anl.gov
|
||||
on_build_success: false
|
||||
- provider: GitHubPullRequest
|
||||
+1
-1
Submodule .ci updated: 20f8e05393...fbbfced900.
+1
-2
@@ -7,11 +7,10 @@
|
||||
/include/
|
||||
/templates/
|
||||
/configure/*.local
|
||||
/configure/RELEASE.*
|
||||
/configure/CONFIG_SITE.*
|
||||
O.*/
|
||||
/QtC-*
|
||||
envPaths
|
||||
*.orig
|
||||
*.log
|
||||
.*.swp
|
||||
.iocsh_history
|
||||
+31
-3
@@ -1,3 +1,7 @@
|
||||
# SPDX-FileCopyrightText: 1997 Argonne National Laboratory
|
||||
#
|
||||
# SPDX-License-Identifier: EPICS
|
||||
|
||||
# CONFIG - Load build configuration data
|
||||
#
|
||||
# Do not make changes to this file!
|
||||
@@ -8,9 +12,19 @@ RULES = $(EPICS_BASE)
|
||||
# RELEASE files point to other application tops
|
||||
include $(TOP)/configure/RELEASE
|
||||
-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).Common
|
||||
|
||||
ifdef T_A
|
||||
-include $(TOP)/configure/RELEASE.Common.$(T_A)
|
||||
-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).$(T_A)
|
||||
-include $(TOP)/configure/RELEASE.Common.$(T_A)
|
||||
-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).$(T_A)
|
||||
endif
|
||||
|
||||
# Check EPICS_BASE is set properly
|
||||
ifneq (file,$(origin EPICS_BASE))
|
||||
$(error EPICS_BASE must be set in a configure/RELEASE file)
|
||||
else
|
||||
ifeq ($(wildcard $(EPICS_BASE)/configure/CONFIG_BASE),)
|
||||
$(error EPICS_BASE does not point to an EPICS installation)
|
||||
endif
|
||||
endif
|
||||
|
||||
CONFIG = $(RULES)/configure
|
||||
@@ -19,11 +33,25 @@ include $(CONFIG)/CONFIG
|
||||
# Override the Base definition:
|
||||
INSTALL_LOCATION = $(TOP)
|
||||
|
||||
# CONFIG_SITE files contain other build configuration settings
|
||||
# CONFIG_SITE files contain local build configuration settings
|
||||
include $(TOP)/configure/CONFIG_SITE
|
||||
|
||||
# Host-arch specific settings
|
||||
-include $(TOP)/configure/CONFIG_SITE.$(EPICS_HOST_ARCH).Common
|
||||
|
||||
ifdef T_A
|
||||
# Target-arch specific settings
|
||||
-include $(TOP)/configure/CONFIG_SITE.Common.$(T_A)
|
||||
|
||||
# Host & target specific settings
|
||||
-include $(TOP)/configure/CONFIG_SITE.$(EPICS_HOST_ARCH).$(T_A)
|
||||
endif
|
||||
|
||||
# Load our cfg/CONFIG* files if installing elsewhere
|
||||
ifneq ($(INSTALL_LOCATION),$(TOP))
|
||||
TOP_CFG_CONFIGS := $(wildcard $(INSTALL_CFG)/CONFIG*)
|
||||
ifneq ($(TOP_CFG_CONFIGS),)
|
||||
include $(TOP_CFG_CONFIGS)
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include <pv/thread.h>
|
||||
#include <pv/pvData.h>
|
||||
|
||||
#include "factoryPvt.h"
|
||||
|
||||
using std::tr1::static_pointer_cast;
|
||||
using std::size_t;
|
||||
using std::string;
|
||||
@@ -36,7 +38,6 @@ namespace epics { namespace pvData {
|
||||
|
||||
size_t Field::num_instances;
|
||||
|
||||
|
||||
struct Field::Helper {
|
||||
static unsigned hash(Field *fld) {
|
||||
std::ostringstream key;
|
||||
@@ -1488,6 +1489,7 @@ static int decodeScalar(int8 code)
|
||||
|
||||
FieldConstPtr FieldCreate::deserialize(ByteBuffer* buffer, DeserializableControl* control) const
|
||||
{
|
||||
RecurseGuard RG;
|
||||
control->ensureData(1);
|
||||
int8 code = buffer->getByte();
|
||||
if (code == -1)
|
||||
@@ -1576,7 +1578,9 @@ FieldConstPtr FieldCreate::deserialize(ByteBuffer* buffer, DeserializableControl
|
||||
throw std::invalid_argument("fixed and bounded structure array not supported");
|
||||
|
||||
// Type type = Type.structureArray;
|
||||
StructureConstPtr elementStructure = std::tr1::static_pointer_cast<const Structure>(control->cachedDeserialize(buffer));
|
||||
StructureConstPtr elementStructure = std::tr1::dynamic_pointer_cast<const Structure>(control->cachedDeserialize(buffer));
|
||||
if(!elementStructure)
|
||||
throw std::invalid_argument("Expected Structure");
|
||||
// TODO use std::make_shared
|
||||
std::tr1::shared_ptr<Field> sp(new StructureArray(elementStructure));
|
||||
Helper::cache(this, sp);
|
||||
@@ -1589,7 +1593,9 @@ FieldConstPtr FieldCreate::deserialize(ByteBuffer* buffer, DeserializableControl
|
||||
throw std::invalid_argument("fixed and bounded structure array not supported");
|
||||
|
||||
// Type type = Type.unionArray;
|
||||
UnionConstPtr elementUnion = std::tr1::static_pointer_cast<const Union>(control->cachedDeserialize(buffer));
|
||||
UnionConstPtr elementUnion = std::tr1::dynamic_pointer_cast<const Union>(control->cachedDeserialize(buffer));
|
||||
if(!elementUnion)
|
||||
throw std::invalid_argument("Expected Union");
|
||||
// TODO use std::make_shared
|
||||
std::tr1::shared_ptr<Field> sp(new UnionArray(elementUnion));
|
||||
Helper::cache(this, sp);
|
||||
@@ -1612,6 +1618,7 @@ FieldConstPtr FieldCreate::deserialize(ByteBuffer* buffer, DeserializableControl
|
||||
namespace detail {
|
||||
struct field_factory {
|
||||
FieldCreatePtr fieldCreate;
|
||||
epicsThreadPrivate<unsigned> deserDepth;
|
||||
field_factory() :fieldCreate(new FieldCreate()) {
|
||||
registerRefCounter("Field", &Field::num_instances);
|
||||
registerRefCounter("Thread", &Thread::num_instances);
|
||||
@@ -1622,6 +1629,23 @@ struct field_factory {
|
||||
static detail::field_factory* field_factory_s;
|
||||
static epicsThreadOnceId field_factory_once = EPICS_THREAD_ONCE_INIT;
|
||||
|
||||
#if defined(__rtems__) || defined(vxWorks)
|
||||
// lower limit on embedded targets
|
||||
const unsigned maxDepth = 10;
|
||||
#else
|
||||
const unsigned maxDepth = 20;
|
||||
#endif
|
||||
RecurseGuard::RecurseGuard() {
|
||||
size_t current = (size_t)field_factory_s->deserDepth.get();
|
||||
if(current>=maxDepth)
|
||||
throw std::logic_error("FieldCreateFactory recursion depth exceeded");
|
||||
field_factory_s->deserDepth.set((unsigned*)(current+1));
|
||||
}
|
||||
RecurseGuard::~RecurseGuard() {
|
||||
size_t current = (size_t)field_factory_s->deserDepth.get();
|
||||
field_factory_s->deserDepth.set((unsigned*)(current-1));
|
||||
}
|
||||
|
||||
static void field_factory_init(void*)
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include <pv/factory.h>
|
||||
#include <pv/bitSet.h>
|
||||
|
||||
#include "factoryPvt.h"
|
||||
|
||||
using std::tr1::static_pointer_cast;
|
||||
using std::size_t;
|
||||
using std::string;
|
||||
@@ -224,6 +226,7 @@ void PVStructure::serialize(ByteBuffer *pbuffer,
|
||||
|
||||
void PVStructure::deserialize(ByteBuffer *pbuffer,
|
||||
DeserializableControl *pcontrol) {
|
||||
RecurseGuard G;
|
||||
size_t fieldsSize = pvFields.size();
|
||||
for(size_t i = 0; i<fieldsSize; i++)
|
||||
pvFields[i]->deserialize(pbuffer, pcontrol);
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include <pv/factory.h>
|
||||
#include <pv/serializeHelper.h>
|
||||
|
||||
#include "factoryPvt.h"
|
||||
|
||||
using std::tr1::static_pointer_cast;
|
||||
using std::size_t;
|
||||
|
||||
@@ -158,6 +160,7 @@ void PVStructureArray::serialize(ByteBuffer *pbuffer,
|
||||
|
||||
void PVStructureArray::deserialize(ByteBuffer *pbuffer,
|
||||
DeserializableControl *pcontrol) {
|
||||
RecurseGuard G;
|
||||
svector data(reuse());
|
||||
|
||||
size_t size = this->getArray()->getArraySizeType() == Array::fixed ?
|
||||
|
||||
+15
-7
@@ -18,6 +18,8 @@
|
||||
#include <pv/factory.h>
|
||||
#include <pv/serializeHelper.h>
|
||||
|
||||
#include "factoryPvt.h"
|
||||
|
||||
using std::tr1::static_pointer_cast;
|
||||
using std::size_t;
|
||||
using std::string;
|
||||
@@ -143,6 +145,7 @@ void PVUnion::serialize(ByteBuffer *pbuffer, SerializableControl *pflusher) cons
|
||||
|
||||
void PVUnion::deserialize(ByteBuffer *pbuffer, DeserializableControl *pcontrol)
|
||||
{
|
||||
RecurseGuard G;
|
||||
if (variant)
|
||||
{
|
||||
FieldConstPtr field = pcontrol->cachedDeserialize(pbuffer);
|
||||
@@ -158,21 +161,26 @@ void PVUnion::deserialize(ByteBuffer *pbuffer, DeserializableControl *pcontrol)
|
||||
}
|
||||
else
|
||||
{
|
||||
int32 previousSelector = selector;
|
||||
selector = static_cast<int32>(SerializeHelper::readSize(pbuffer, pcontrol));
|
||||
if (selector != UNDEFINED_INDEX)
|
||||
int32 index = static_cast<int32>(SerializeHelper::readSize(pbuffer, pcontrol));
|
||||
if (index == UNDEFINED_INDEX)
|
||||
{
|
||||
if (selector != previousSelector)
|
||||
selector = index;
|
||||
value.reset();
|
||||
}
|
||||
else if (index < 0 || size_t(index) >= unionPtr->getFields().size())
|
||||
throw std::runtime_error("union index out of bounds");
|
||||
else
|
||||
{
|
||||
if (selector != index)
|
||||
{
|
||||
FieldConstPtr field = unionPtr->getField(selector);
|
||||
selector = index;
|
||||
FieldConstPtr field = unionPtr->getField(index);
|
||||
// try to reuse existing field instance
|
||||
if (!value.get() || *value->getField() != *field)
|
||||
value = pvDataCreate->createPVField(field);
|
||||
}
|
||||
value->deserialize(pbuffer, pcontrol);
|
||||
}
|
||||
else
|
||||
value.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include <pv/factory.h>
|
||||
#include <pv/serializeHelper.h>
|
||||
|
||||
#include "factoryPvt.h"
|
||||
|
||||
using std::tr1::static_pointer_cast;
|
||||
using std::size_t;
|
||||
|
||||
@@ -157,6 +159,7 @@ void PVUnionArray::serialize(ByteBuffer *pbuffer,
|
||||
|
||||
void PVUnionArray::deserialize(ByteBuffer *pbuffer,
|
||||
DeserializableControl *pcontrol) {
|
||||
RecurseGuard G;
|
||||
svector data(reuse());
|
||||
|
||||
size_t size = this->getArray()->getArraySizeType() == Array::fixed ?
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef FACTORYPVT_H
|
||||
#define FACTORYPVT_H
|
||||
|
||||
namespace epics { namespace pvData {
|
||||
|
||||
// use to bound recursion in FieldCreate and PVDataCreator singletons
|
||||
struct RecurseGuard {
|
||||
RecurseGuard();
|
||||
~RecurseGuard();
|
||||
};
|
||||
|
||||
}} // namespace epics::pvData
|
||||
|
||||
#endif // FACTORYPVT_H
|
||||
+23
-2
@@ -434,12 +434,33 @@ std::ostream& operator<<(std::ostream& strm, const PVStructure::Formatter& forma
|
||||
return strm;
|
||||
|
||||
case scalarArray:
|
||||
{
|
||||
strm<<format::indent();
|
||||
printTimeT(strm, format.xtop);
|
||||
printAlarmT(strm, format.xtop);
|
||||
strm<<std::setprecision(6)<<*static_cast<const PVScalarArray*>(value.get())<<'\n';
|
||||
return strm;
|
||||
|
||||
const PVScalarArray* pvArray = static_cast<const PVScalarArray*>(value.get());
|
||||
ScalarArrayConstPtr scalarArray = pvArray->getScalarArray();
|
||||
|
||||
// Stringify byte arrays if requested
|
||||
if (format.xarrAsStr && scalarArray && scalarArray->getElementType() == pvByte) {
|
||||
shared_vector<const int8_t> byteArray;
|
||||
pvArray->getAs(byteArray);
|
||||
|
||||
if (!byteArray.empty()) {
|
||||
const char* data = reinterpret_cast<const char*>(byteArray.data());
|
||||
size_t slen = byteArray.size();
|
||||
if (!data[slen-1])
|
||||
--slen; // Trim trailing null so we don't end up with "mystring\0"
|
||||
strm<<escape(std::string(data, slen));
|
||||
}
|
||||
} else {
|
||||
strm<<std::setprecision(6);
|
||||
strm<<*pvArray;
|
||||
}
|
||||
strm<<'\n';
|
||||
return strm;
|
||||
}
|
||||
case structure:
|
||||
if(printEnumT(strm, format.xtop, true)) {
|
||||
strm<<'\n';
|
||||
|
||||
+4
-6
@@ -337,17 +337,15 @@ namespace epics { namespace pvData {
|
||||
}
|
||||
|
||||
void BitSet::deserialize(ByteBuffer* buffer, DeserializableControl* control) {
|
||||
size_t size = SerializeHelper::readSize(buffer, control);
|
||||
uint32 bytes = (size == (size_t) -1) ? 0 : static_cast<uint32>(size);
|
||||
|
||||
uint32 bytes = static_cast<uint32>(SerializeHelper::readSize(buffer, control)); // in bytes
|
||||
// Validate availability before allocating storage
|
||||
control->ensureData(bytes);
|
||||
|
||||
size_t wordsInUse = (bytes + 7) / BYTES_PER_WORD;
|
||||
words.resize(wordsInUse);
|
||||
|
||||
if (wordsInUse == 0)
|
||||
return;
|
||||
|
||||
control->ensureData(bytes);
|
||||
|
||||
uint32 i = 0;
|
||||
uint32 longs = bytes / 8;
|
||||
while (i < longs)
|
||||
|
||||
+40
-42
@@ -69,8 +69,6 @@ inline testPassx testNotEqualx(const char *nLHS, const char *nRHS, const LHS& l,
|
||||
*
|
||||
* Helper functions for writing unit tests.
|
||||
*
|
||||
@include unittest.cpp
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
@@ -78,34 +76,34 @@ inline testPassx testNotEqualx(const char *nLHS, const char *nRHS, const LHS& l,
|
||||
*
|
||||
* Each invocation of TEST_METHOD() constructs a new instance of 'klass' on the stack.
|
||||
* Thus constructor and destructor can be used for common test setup and tear down.
|
||||
@code
|
||||
namespace { // anon
|
||||
struct MyTest {
|
||||
MyTest() { } // setup
|
||||
~MyTest() { } // tear down
|
||||
void test1() {}
|
||||
void test2() {}
|
||||
};
|
||||
} // namespace anon
|
||||
MAIN(somename) {
|
||||
testPlan(0);
|
||||
TEST_METHOD(MyTest, test1)
|
||||
TEST_METHOD(MyTest, test2)
|
||||
return testDone();
|
||||
}
|
||||
@endcode
|
||||
* @code
|
||||
* namespace { // anon
|
||||
* struct MyTest {
|
||||
* MyTest() { } // setup
|
||||
* ~MyTest() { } // tear down
|
||||
* void test1() {}
|
||||
* void test2() {}
|
||||
* };
|
||||
* } // namespace anon
|
||||
* MAIN(somename) {
|
||||
* testPlan(0);
|
||||
* TEST_METHOD(MyTest, test1)
|
||||
* TEST_METHOD(MyTest, test2)
|
||||
* return testDone();
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
#define TEST_METHOD(klass, method) ::detail::test_method<klass, &klass::method>(#klass, #method)
|
||||
|
||||
/** Compare equality. print left and right hand values and expression strings
|
||||
*
|
||||
@code
|
||||
int x=5;
|
||||
testEqual(x, 5);
|
||||
// prints "ok 1 - x (5) == 5 (5)\n"
|
||||
testEqual(x, 6)<<" oops";
|
||||
// prints "not ok 1 - x (5) == 6 (6) oops\n"
|
||||
@endcode
|
||||
* @code
|
||||
* int x=5;
|
||||
* testEqual(x, 5);
|
||||
* // prints "ok 1 - x (5) == 5 (5)\n"
|
||||
* testEqual(x, 6)<<" oops";
|
||||
* // prints "not ok 1 - x (5) == 6 (6) oops\n"
|
||||
* @endcode
|
||||
*/
|
||||
#define testEqual(LHS, RHS) ::detail::testEqualx(#LHS, #RHS, LHS, RHS)
|
||||
|
||||
@@ -113,38 +111,38 @@ inline testPassx testNotEqualx(const char *nLHS, const char *nRHS, const LHS& l,
|
||||
|
||||
/** Pass/fail from boolean
|
||||
*
|
||||
@code
|
||||
bool y=true;
|
||||
testTrue(y);
|
||||
// prints "ok 1 - y\n"
|
||||
testTrue(!y)<<" oops";
|
||||
// prints "not ok 1 - !y oops\n"
|
||||
@endcode
|
||||
* @code
|
||||
* bool y=true;
|
||||
* testTrue(y);
|
||||
* // prints "ok 1 - y\n"
|
||||
* testTrue(!y)<<" oops";
|
||||
* // prints "not ok 1 - !y oops\n"
|
||||
* @endcode
|
||||
*/
|
||||
#define testTrue(B) ::detail::testPassx(!!(B))<<#B
|
||||
|
||||
/** Test that a given block throws an exception
|
||||
*
|
||||
@code
|
||||
testThrows(std::runtime_error, somefunc(5))
|
||||
@endcode
|
||||
* @code
|
||||
* testThrows(std::runtime_error, somefunc(5))
|
||||
* @endcode
|
||||
*/
|
||||
#define testThrows(EXC, CODE) try{ CODE; testFail("unexpected success of " #CODE); }catch(EXC& e){testPass("catch expected exception: %s", e.what());}
|
||||
|
||||
/** Print test output w/o testing
|
||||
*
|
||||
@code
|
||||
testShow()<<"Foo";
|
||||
@endcode
|
||||
* @code
|
||||
* testShow()<<"Foo";
|
||||
* @endcode
|
||||
*/
|
||||
#define testShow() ::detail::testPassx()
|
||||
|
||||
/** Compare value of PVStructure field
|
||||
*
|
||||
@code
|
||||
PVStructurePtr x(.....);
|
||||
testFieldEqual<epics::pvData::PVInt>(x, "alarm.severity", 1);
|
||||
@endcode
|
||||
* @code
|
||||
* PVStructurePtr x(.....);
|
||||
* testFieldEqual<epics::pvData::PVInt>(x, "alarm.severity", 1);
|
||||
* @endcode
|
||||
*/
|
||||
template<typename PVD>
|
||||
::detail::testPassx
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace epics {
|
||||
*
|
||||
* @param[in] buffer deserialization buffer.
|
||||
* @param[in] control the DeserializableControl.
|
||||
* @returns array size.
|
||||
* @returns array size, or (size_t) -1.
|
||||
*/
|
||||
static std::size_t readSize(ByteBuffer* buffer,
|
||||
DeserializableControl* control);
|
||||
@@ -80,20 +80,22 @@ namespace epics {
|
||||
|
||||
/**
|
||||
* std::string deserialization helper method.
|
||||
* TODO This method cannot return "null", but Java implementation
|
||||
* could have serialized "null" value as well. We need to decide
|
||||
* how to deserialize "null".
|
||||
*
|
||||
* @param[in] buffer deserialization buffer
|
||||
* @param[in] control control
|
||||
* @param[in] max length limit
|
||||
* @returns deserialized string
|
||||
*
|
||||
* @todo This method cannot return "null", but Java implementation
|
||||
* could have serialized "null" value as well. We need to decide
|
||||
* how to deserialize "null".
|
||||
* could have serialized "null" value as well. This implementation
|
||||
* returns an empty string in that event.
|
||||
*
|
||||
* @since UNRELEASED Added the max argument.
|
||||
* Test for the DESERIALIZE_STRING_HAS_LIMIT macro to detect.
|
||||
* Retroactive default is 2 GiB
|
||||
*/
|
||||
static std::string deserializeString(ByteBuffer* buffer,
|
||||
DeserializableControl* control);
|
||||
DeserializableControl* control, std::size_t max = 0x7fffffff);
|
||||
|
||||
private:
|
||||
SerializeHelper() {};
|
||||
@@ -112,4 +114,12 @@ namespace epics {
|
||||
}
|
||||
}
|
||||
|
||||
/** Feature test macro for max argument
|
||||
*
|
||||
* of SerializeHelper::deserializeString()
|
||||
*
|
||||
* @since UNRELEASED added
|
||||
*/
|
||||
#define DESERIALIZE_STRING_HAS_LIMIT
|
||||
|
||||
#endif /* SERIALIZEHELPER_H_ */
|
||||
@@ -47,12 +47,13 @@ namespace epics {
|
||||
DeserializableControl* control) {
|
||||
control->ensureData(1);
|
||||
int8 b = buffer->getByte();
|
||||
if(b==-1)
|
||||
return -1;
|
||||
else if(b==-2) {
|
||||
if (b == -1)
|
||||
return -1; // NULL
|
||||
else if (b == -2) {
|
||||
control->ensureData(sizeof(int32));
|
||||
int32 s = buffer->getInt();
|
||||
if(s<0) THROW_BASE_EXCEPTION("negative size");
|
||||
if (s < 0)
|
||||
THROW_BASE_EXCEPTION("negative size");
|
||||
return s;
|
||||
}
|
||||
else
|
||||
@@ -100,44 +101,37 @@ namespace epics {
|
||||
}
|
||||
|
||||
string SerializeHelper::deserializeString(ByteBuffer* buffer,
|
||||
DeserializableControl* control) {
|
||||
DeserializableControl* control, std::size_t max) {
|
||||
|
||||
std::size_t size = SerializeHelper::readSize(buffer, control);
|
||||
if(size!=(size_t)-1) // TODO null strings check, to be removed in the future
|
||||
{
|
||||
if (buffer->getRemaining()>=size)
|
||||
{
|
||||
// entire string is in buffer, simply create a string out of it (copy)
|
||||
std::size_t pos = buffer->getPosition();
|
||||
string str(buffer->getBuffer()+pos, size);
|
||||
buffer->setPosition(pos+size);
|
||||
return str;
|
||||
}
|
||||
else
|
||||
{
|
||||
string str;
|
||||
str.reserve(size);
|
||||
try {
|
||||
std::size_t i = 0;
|
||||
while(true) {
|
||||
std::size_t toRead = min(size-i, buffer->getRemaining());
|
||||
std::size_t pos = buffer->getPosition();
|
||||
str.append(buffer->getBuffer()+pos, toRead);
|
||||
buffer->setPosition(pos+toRead);
|
||||
i += toRead;
|
||||
if(i<size)
|
||||
control->ensureData(1); // at least one
|
||||
else
|
||||
break;
|
||||
}
|
||||
return str;
|
||||
} catch(...) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
if (size == (std::size_t) -1)
|
||||
return std::string(); // NULL from old implementations
|
||||
|
||||
if (size > max)
|
||||
THROW_BASE_EXCEPTION("string too long");
|
||||
|
||||
if (buffer->getRemaining() >= size) { // All present
|
||||
std::size_t pos = buffer->getPosition();
|
||||
string str(buffer->getBuffer() + pos, size);
|
||||
buffer->setPosition(pos + size);
|
||||
return str;
|
||||
}
|
||||
else
|
||||
return std::string();
|
||||
|
||||
string str;
|
||||
str.reserve(size);
|
||||
std::size_t i = 0;
|
||||
while (true) {
|
||||
std::size_t toRead = min(size-i, buffer->getRemaining());
|
||||
std::size_t pos = buffer->getPosition();
|
||||
str.append(buffer->getBuffer() + pos, toRead);
|
||||
buffer->setPosition(pos + toRead);
|
||||
i += toRead;
|
||||
if (i < size)
|
||||
control->ensureData(1); // at least one
|
||||
else
|
||||
break;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ void Status::deserialize(ByteBuffer *buffer, DeserializableControl *flusher)
|
||||
}
|
||||
else
|
||||
{
|
||||
if(typeCode < 0 || typeCode > STATUSTYPE_FATAL)
|
||||
typeCode = STATUSTYPE_FATAL;
|
||||
m_statusType = (StatusType)typeCode;
|
||||
m_message = SerializeHelper::deserializeString(buffer, flusher);
|
||||
m_stackDump = SerializeHelper::deserializeString(buffer, flusher);
|
||||
|
||||
@@ -888,6 +888,7 @@ public:
|
||||
const BitSet* xhighlight;
|
||||
mode_t xmode;
|
||||
format_t xfmt;
|
||||
bool xarrAsStr;
|
||||
public:
|
||||
explicit Formatter(const PVStructure& top)
|
||||
:xtop(top)
|
||||
@@ -895,6 +896,7 @@ public:
|
||||
,xhighlight(0)
|
||||
,xmode(Auto)
|
||||
,xfmt(NT)
|
||||
,xarrAsStr(false)
|
||||
{}
|
||||
|
||||
// those fields (and their parents) to be printed. non-NT mode.
|
||||
@@ -906,6 +908,8 @@ public:
|
||||
|
||||
FORCE_INLINE Formatter& format(format_t f) { xfmt = f; return *this; }
|
||||
|
||||
FORCE_INLINE Formatter& arrayAsString(bool b) { xarrAsStr = b; return *this; }
|
||||
|
||||
friend epicsShareFunc std::ostream& operator<<(std::ostream& strm, const Formatter& format);
|
||||
friend void printRaw(std::ostream& strm, const PVStructure::Formatter& format, const PVStructure& cur);
|
||||
};
|
||||
|
||||
@@ -303,15 +303,57 @@ static void testSerialize()
|
||||
#undef TOFRO
|
||||
}
|
||||
|
||||
static void testDeserializeOversize()
|
||||
{
|
||||
testDiag("testDeserializeOversize");
|
||||
|
||||
// Size field declares INT32_MAX bytes but no payload follows: 0xFE
|
||||
// long-form marker + big-endian int32. deserialize() must reject this
|
||||
// via ensureData() before allocating a ~2GB backing store.
|
||||
const char msg[] = {(char)0xFE, 0x7F, (char)0xFF, (char)0xFF, (char)0xFF};
|
||||
BitSet dut;
|
||||
ByteBuffer buf((char*)msg, sizeof(msg), EPICS_ENDIAN_BIG);
|
||||
try {
|
||||
deserializeFromBuffer(&dut, buf);
|
||||
testFail("oversized BitSet size was not rejected");
|
||||
} catch(std::exception& e) {
|
||||
testPass("oversized BitSet size rejected: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
static void testDeserializeNull()
|
||||
{
|
||||
testDiag("testDeserializeNull");
|
||||
|
||||
// A NULL size is encoded on the wire as the single byte 0xFF. Without
|
||||
// the null check in deserialize() this would be cast to UINT32_MAX bytes
|
||||
// and overflow the allocation size; it must instead yield an empty BitSet.
|
||||
const char msg[] = {(char)0xFF};
|
||||
BitSet dut;
|
||||
dut.set(5); // ensure deserialize actually clears/replaces existing bits
|
||||
ByteBuffer buf((char*)msg, sizeof(msg), EPICS_ENDIAN_BIG);
|
||||
try {
|
||||
deserializeFromBuffer(&dut, buf);
|
||||
testPass("NULL BitSet size accepted");
|
||||
} catch(std::exception& e) {
|
||||
testFail("NULL BitSet size rejected: %s", e.what());
|
||||
}
|
||||
testOk1(dut.isEmpty());
|
||||
testOk(buf.getRemaining()==0, "buffer remaining 0 == %u",
|
||||
(unsigned)buf.getRemaining());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MAIN(testBitSet)
|
||||
{
|
||||
testPlan(90);
|
||||
testPlan(94);
|
||||
testInitialize();
|
||||
testGetSetClearFlip();
|
||||
testOperators();
|
||||
testLogical();
|
||||
testSerialize();
|
||||
testDeserializeOversize();
|
||||
testDeserializeNull();
|
||||
return testDone();
|
||||
}
|
||||
@@ -849,11 +849,60 @@ void testFromString(int byteOrder)
|
||||
testOk1(_data->getSubFieldT<PVString>("Y")->get()=="testing");
|
||||
}
|
||||
|
||||
void testDeserializeTypeFail(const char *buf, size_t buflen)
|
||||
{
|
||||
|
||||
std::vector<char> bytes(buf, buf + buflen);
|
||||
ByteBuffer B(&bytes[0], bytes.size(), EPICS_ENDIAN_BIG);
|
||||
|
||||
try{
|
||||
FieldConstPtr ptr(getFieldCreate()->deserialize(&B, control));
|
||||
}catch(std::exception& e) {
|
||||
testPass("Expected exception: %s", e.what());
|
||||
return;
|
||||
}
|
||||
testFail("Unexpected success? %d", __LINE__);
|
||||
}
|
||||
|
||||
void testMalformed()
|
||||
{
|
||||
testDiag("testMalformed()");
|
||||
|
||||
#define CASE(STR) testDeserializeTypeFail(STR, sizeof(STR)-1)
|
||||
// a structure array where the element type is not a structure
|
||||
CASE("\x88\x83\x2b\x01\x01");
|
||||
// a union array where ...
|
||||
CASE("\x89\x83\x2b\x01\x01");
|
||||
#undef CASE
|
||||
{
|
||||
std::vector<char> inp(500u);
|
||||
for(size_t i=0; i<inp.size(); i+=5) {
|
||||
inp[i+0] = '\x80'; // Struct
|
||||
inp[i+1] = '\xff'; // null ID String
|
||||
inp[i+2] = '\x01'; // 1 member field
|
||||
inp[i+3] = '\x01'; // member name length 1
|
||||
inp[i+4] = 'x' ; // member name
|
||||
}
|
||||
testDeserializeTypeFail(&inp[0], inp.size());
|
||||
}
|
||||
{
|
||||
std::vector<char> inp(500u);
|
||||
for(size_t i=0; i<inp.size(); i+=5) {
|
||||
inp[i+0] = '\x81'; // Union
|
||||
inp[i+1] = '\xff'; // null ID String
|
||||
inp[i+2] = '\x01'; // 1 member field
|
||||
inp[i+3] = '\x01'; // member name length 1
|
||||
inp[i+4] = 'x' ; // member name
|
||||
}
|
||||
testDeserializeTypeFail(&inp[0], inp.size());
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace
|
||||
|
||||
MAIN(testSerialization) {
|
||||
|
||||
testPlan(234);
|
||||
testPlan(238);
|
||||
|
||||
flusher = new SerializableControlImpl();
|
||||
control = new DeserializableControlImpl();
|
||||
@@ -880,6 +929,8 @@ MAIN(testSerialization) {
|
||||
testFromString(EPICS_ENDIAN_BIG);
|
||||
testFromString(EPICS_ENDIAN_LITTLE);
|
||||
|
||||
testMalformed();
|
||||
|
||||
delete buffer;
|
||||
delete control;
|
||||
delete flusher;
|
||||
|
||||
Reference in new issue
Block a user