/* obj.c
*
* This file implements a generic object "class". All other classes can
* use the service of this base class here to include auto-destruction and
* other capabilities in a generic manner.
*
* As of 2008-02-29, I (rgerhards) am adding support for dynamically loadable
* objects. In essence, each object will soon be available via its interface,
* only. Before any object's code is accessed (including global static methods),
* the caller needs to obtain an object interface. To do so, it needs to provide
* the object name and the file where the object is expected to reside in. A
* file may not be given, in which case the object is expected to reside in
* the rsyslog core. The caller than receives an interface pointer which can
* be utilized to access all the object's methods. This method enables rsyslog
* to load library modules on demand. In order to keep overhead low, callers
* should request object interface only once in the object Init function and
* free them when they exit. The only exception is when a caller needs to
* access an object only conditional, in which case a pointer to its interface
* shall be aquired as need first arises but still be released only on exit
* or when there definitely is no further need. The whole idea is to limit
* the very performance-intense act of dynamically loading an objects library.
* Of course, it is possible to violate this suggestion, but than you should
* have very good reasoning to do so.
*
* Please note that there is one trick we need to do. Each object queries
* the object interfaces and it does so via objUse(). objUse, however, is
* part of the obj object's interface (implemented via the file you are
* just reading). So in order to obtain a pointer to objUse, we need to
* call it - obviously not possible. One solution would be that objUse is
* hardcoded into all callers. That, however, would bring us into slight
* trouble with actually dynamically loaded modules, as we should NOT
* rely on the OS loader to resolve symbols back to the caller (this
* is a feature not universally available and highly importable). Of course,
* we can solve this with a pHostQueryEtryPoint() call. It still sounds
* somewhat unnatural to call a regular interface function via a special
* method. So what we do instead is define a special function called
* objGetObjInterface() which delivers our own interface. That function
* than will be defined global and be queriable via pHostQueryEtryPoint().
* I agree, technically this is much the same, but from an architecture
* point of view it looks cleaner (at least to me).
*
* Please note that there is another egg-hen problem: we use a linked list,
* which is provided by the linkedList object. However, we need to
* initialize the linked list before we can provide the UseObj()
* functionality. That, in turn, would probably be required by the
* linkedList object. So the solution is to use a backdoor just to
* init the linked list and from then on use the usual interfaces.
*
* File begun on 2008-01-04 by RGerhards
*
* Copyright 2008 Rainer Gerhards and Adiscon GmbH.
*
* This file is part of rsyslog.
*
* Rsyslog is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Rsyslog is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Rsyslog. If not, see .
*
* A copy of the GPL can be found in the file "COPYING" in this distribution.
*/
#include "config.h"
#include
#include
#include
#include
#include
/* how many objects are supported by rsyslogd? */
#define OBJ_NUM_IDS 100 /* TODO change to a linked list? info: 16 were currently in use 2008-02-29 */
#include "rsyslog.h"
#include "syslogd-types.h"
#include "srUtils.h"
#include "obj.h"
#include "stream.h"
#include "modules.h"
#include "errmsg.h"
#include "cfsysline.h"
/* static data */
DEFobjCurrIf(obj) /* we define our own interface, as this is expected by some macros! */
DEFobjCurrIf(var)
DEFobjCurrIf(module)
DEFobjCurrIf(errmsg)
static objInfo_t *arrObjInfo[OBJ_NUM_IDS]; /* array with object information pointers */
/* cookies for serialized lines */
#define COOKIE_OBJLINE '<'
#define COOKIE_PROPLINE '+'
#define COOKIE_ENDLINE '>'
#define COOKIE_BLANKLINE '.'
/* forward definitions */
static rsRetVal FindObjInfo(cstr_t *pszObjName, objInfo_t **ppInfo);
/* methods */
/* This is a dummy method to be used when a standard method has not been
* implemented by an object. Having it allows us to simply call via the
* jump table without any NULL pointer checks - which gains quite
* some performance. -- rgerhards, 2008-01-04
*/
static rsRetVal objInfoNotImplementedDummy(void __attribute__((unused)) *pThis)
{
return RS_RET_NOT_IMPLEMENTED;
}
/* and now the macro to check if something is not implemented
* must be provided an objInfo_t pointer.
*/
#define objInfoIsImplemented(pThis, method) \
(pThis->objMethods[method] != objInfoNotImplementedDummy)
/* construct an object Info object. Each class shall do this on init. The
* resulting object shall be cached during the lifetime of the class and each
* object shall receive a reference. A constructor and destructor MUST be provided for all
* objects, thus they are in the parameter list.
* pszID is the identifying object name and must point to constant pool memory. It is never freed.
*/
static rsRetVal
InfoConstruct(objInfo_t **ppThis, uchar *pszID, int iObjVers,
rsRetVal (*pConstruct)(void *), rsRetVal (*pDestruct)(void *),
rsRetVal (*pQueryIF)(interface_t*), modInfo_t *pModInfo)
{
DEFiRet;
int i;
objInfo_t *pThis;
assert(ppThis != NULL);
if((pThis = calloc(1, sizeof(objInfo_t))) == NULL)
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
pThis->pszID = pszID;
pThis->lenID = strlen((char*)pszID);
pThis->pszName = (uchar*)strdup((char*)pszID); /* it's OK if we have NULL ptr, GetName() will deal with that! */
pThis->iObjVers = iObjVers;
pThis->QueryIF = pQueryIF;
pThis->pModInfo = pModInfo;
pThis->objMethods[0] = pConstruct;
pThis->objMethods[1] = pDestruct;
for(i = 2 ; i < OBJ_NUM_METHODS ; ++i) {
pThis->objMethods[i] = objInfoNotImplementedDummy;
}
*ppThis = pThis;
finalize_it:
RETiRet;
}
/* destruct the objInfo object - must be done only when no more instances exist.
* rgerhards, 2008-03-10
*/
static rsRetVal
InfoDestruct(objInfo_t **ppThis)
{
DEFiRet;
objInfo_t *pThis;
assert(ppThis != NULL);
pThis = *ppThis;
assert(pThis != NULL);
if(pThis->pszName != NULL)
free(pThis->pszName);
free(pThis);
*ppThis = NULL;
RETiRet;
}
/* set a method handler */
static rsRetVal
InfoSetMethod(objInfo_t *pThis, objMethod_t objMethod, rsRetVal (*pHandler)(void*))
{
assert(pThis != NULL);
assert(objMethod > 0 && objMethod < OBJ_NUM_METHODS);
pThis->objMethods[objMethod] = pHandler;
return RS_RET_OK;
}
/* destruct the base object properties.
* rgerhards, 2008-01-29
*/
static rsRetVal
DestructObjSelf(obj_t *pThis)
{
DEFiRet;
ISOBJ_assert(pThis);
if(pThis->pszName != NULL) {
free(pThis->pszName);
}
RETiRet;
}
/* --------------- object serializiation / deserialization support --------------- */
/* serialize the header of an object
* pszRecType must be either "Obj" (Object) or "OPB" (Object Property Bag)
*/
static rsRetVal objSerializeHeader(strm_t *pStrm, obj_t *pObj, uchar *pszRecType)
{
DEFiRet;
ISOBJ_TYPE_assert(pStrm, strm);
ISOBJ_assert(pObj);
assert(!strcmp((char*) pszRecType, "Obj") || !strcmp((char*) pszRecType, "OPB"));
/* object cookie and serializer version (so far always 1) */
CHKiRet(strmWriteChar(pStrm, COOKIE_OBJLINE));
CHKiRet(strmWrite(pStrm, (uchar*) pszRecType, 3)); /* record types are always 3 octets */
CHKiRet(strmWriteChar(pStrm, ':'));
CHKiRet(strmWriteChar(pStrm, '1'));
/* object type, version and string length */
CHKiRet(strmWriteChar(pStrm, ':'));
CHKiRet(strmWrite(pStrm, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID));
CHKiRet(strmWriteChar(pStrm, ':'));
CHKiRet(strmWriteLong(pStrm, objGetVersion(pObj)));
/* record trailer */
CHKiRet(strmWriteChar(pStrm, ':'));
CHKiRet(strmWriteChar(pStrm, '\n'));
finalize_it:
RETiRet;
}
/* begin serialization of an object
* rgerhards, 2008-01-06
*/
static rsRetVal
BeginSerialize(strm_t *pStrm, obj_t *pObj)
{
DEFiRet;
ISOBJ_TYPE_assert(pStrm, strm);
ISOBJ_assert(pObj);
CHKiRet(strmRecordBegin(pStrm));
CHKiRet(objSerializeHeader(pStrm, pObj, (uchar*) "Obj"));
finalize_it:
RETiRet;
}
/* begin serialization of an object's property bag
* Note: a property bag is used to serialize some of an objects
* properties, but not necessarily all. A good example is the queue
* object, which at some stage needs to serialize a number of its
* properties, but not the queue data itself. From the object point
* of view, a property bag can not be used to re-instantiate an object.
* Otherwise, the serialization is exactly the same.
* rgerhards, 2008-01-11
*/
static rsRetVal
BeginSerializePropBag(strm_t *pStrm, obj_t *pObj)
{
DEFiRet;
ISOBJ_TYPE_assert(pStrm, strm);
ISOBJ_assert(pObj);
CHKiRet(strmRecordBegin(pStrm));
CHKiRet(objSerializeHeader(pStrm, pObj, (uchar*) "OPB"));
finalize_it:
RETiRet;
}
/* append a property
*/
static rsRetVal
SerializeProp(strm_t *pStrm, uchar *pszPropName, propType_t propType, void *pUsr)
{
DEFiRet;
uchar *pszBuf = NULL;
size_t lenBuf = 0;
uchar szBuf[64];
varType_t vType = VARTYPE_NONE;
ISOBJ_TYPE_assert(pStrm, strm);
assert(pszPropName != NULL);
/*dbgprintf("objSerializeProp: strm %p, propName '%s', type %d, pUsr %p\n", pStrm, pszPropName, propType, pUsr);*/
/* if we have no user pointer, there is no need to write this property.
* TODO: think if that's the righ point of view
* rgerhards, 2008-01-06
*/
if(pUsr == NULL) {
ABORT_FINALIZE(RS_RET_OK);
}
/* TODO: use the stream functions for data conversion here - should be quicker */
switch(propType) {
case PROPTYPE_PSZ:
pszBuf = (uchar*) pUsr;
lenBuf = strlen((char*) pszBuf);
vType = VARTYPE_STR;
break;
case PROPTYPE_SHORT:
CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((short*) pUsr)));
pszBuf = szBuf;
lenBuf = strlen((char*) szBuf);
vType = VARTYPE_NUMBER;
break;
case PROPTYPE_INT:
CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((int*) pUsr)));
pszBuf = szBuf;
lenBuf = strlen((char*) szBuf);
vType = VARTYPE_NUMBER;
break;
case PROPTYPE_LONG:
CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((long*) pUsr)));
pszBuf = szBuf;
lenBuf = strlen((char*) szBuf);
vType = VARTYPE_NUMBER;
break;
case PROPTYPE_INT64:
CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((int64*) pUsr)));
pszBuf = szBuf;
lenBuf = strlen((char*) szBuf);
vType = VARTYPE_NUMBER;
break;
case PROPTYPE_CSTR:
pszBuf = rsCStrGetSzStrNoNULL((cstr_t *) pUsr);
lenBuf = rsCStrLen((cstr_t*) pUsr);
vType = VARTYPE_STR;
break;
case PROPTYPE_SYSLOGTIME:
lenBuf = snprintf((char*) szBuf, sizeof(szBuf), "%d:%d:%d:%d:%d:%d:%d:%d:%d:%c:%d:%d",
((syslogTime_t*)pUsr)->timeType,
((syslogTime_t*)pUsr)->year,
((syslogTime_t*)pUsr)->month,
((syslogTime_t*)pUsr)->day,
((syslogTime_t*)pUsr)->hour,
((syslogTime_t*)pUsr)->minute,
((syslogTime_t*)pUsr)->second,
((syslogTime_t*)pUsr)->secfrac,
((syslogTime_t*)pUsr)->secfracPrecision,
((syslogTime_t*)pUsr)->OffsetMode,
((syslogTime_t*)pUsr)->OffsetHour,
((syslogTime_t*)pUsr)->OffsetMinute);
if(lenBuf > sizeof(szBuf) - 1)
ABORT_FINALIZE(RS_RET_PROVIDED_BUFFER_TOO_SMALL);
vType = VARTYPE_SYSLOGTIME;
pszBuf = szBuf;
break;
default:
dbgprintf("invalid PROPTYPE %d\n", propType);
break;
}
/* cookie */
CHKiRet(strmWriteChar(pStrm, COOKIE_PROPLINE));
/* name */
CHKiRet(strmWrite(pStrm, pszPropName, strlen((char*)pszPropName)));
CHKiRet(strmWriteChar(pStrm, ':'));
/* type */
CHKiRet(strmWriteLong(pStrm, (int) vType));
CHKiRet(strmWriteChar(pStrm, ':'));
/* length */
CHKiRet(strmWriteLong(pStrm, lenBuf));
CHKiRet(strmWriteChar(pStrm, ':'));
/* data */
CHKiRet(strmWrite(pStrm, (uchar*) pszBuf, lenBuf));
/* trailer */
CHKiRet(strmWriteChar(pStrm, ':'));
CHKiRet(strmWriteChar(pStrm, '\n'));
finalize_it:
RETiRet;
}
/* end serialization of an object. The caller receives a
* standard C string, which he must free when no longer needed.
*/
static rsRetVal
EndSerialize(strm_t *pStrm)
{
DEFiRet;
assert(pStrm != NULL);
CHKiRet(strmWriteChar(pStrm, COOKIE_ENDLINE));
CHKiRet(strmWrite(pStrm, (uchar*) "End\n", sizeof("END\n") - 1));
CHKiRet(strmWriteChar(pStrm, COOKIE_BLANKLINE));
CHKiRet(strmWriteChar(pStrm, '\n'));
CHKiRet(strmRecordEnd(pStrm));
finalize_it:
RETiRet;
}
/* define a helper to make code below a bit cleaner (and quicker to write) */
#define NEXTC CHKiRet(strmReadChar(pStrm, &c))//;dbgprintf("c: %c\n", c);
/* de-serialize an embedded, non-octect-counted string. This is useful
* for deserializing the object name inside the header. The string is
* terminated by the first occurence of the ':' character.
* rgerhards, 2008-02-29
*/
static rsRetVal
objDeserializeEmbedStr(cstr_t **ppStr, strm_t *pStrm)
{
DEFiRet;
uchar c;
cstr_t *pStr = NULL;
assert(ppStr != NULL);
CHKiRet(rsCStrConstruct(&pStr));
NEXTC;
while(c != ':') {
CHKiRet(rsCStrAppendChar(pStr, c));
NEXTC;
}
CHKiRet(rsCStrFinish(pStr));
*ppStr = pStr;
finalize_it:
if(iRet != RS_RET_OK && pStr != NULL)
rsCStrDestruct(&pStr);
RETiRet;
}
/* de-serialize a number */
static rsRetVal objDeserializeNumber(number_t *pNum, strm_t *pStrm)
{
DEFiRet;
number_t i;
int bIsNegative;
uchar c;
assert(pNum != NULL);
NEXTC;
if(c == '-') {
bIsNegative = 1;
NEXTC;
} else {
bIsNegative = 0;
}
/* we check this so that we get more meaningful error codes */
if(!isdigit(c)) ABORT_FINALIZE(RS_RET_INVALID_NUMBER);
i = 0;
while(isdigit(c)) {
i = i * 10 + c - '0';
NEXTC;
}
if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER);
if(bIsNegative)
i *= -1;
*pNum = i;
finalize_it:
RETiRet;
}
/* de-serialize a string, length must be provided but may be 0 */
static rsRetVal objDeserializeStr(cstr_t **ppCStr, int iLen, strm_t *pStrm)
{
DEFiRet;
int i;
uchar c;
cstr_t *pCStr = NULL;
assert(ppCStr != NULL);
assert(iLen >= 0);
CHKiRet(rsCStrConstruct(&pCStr));
NEXTC;
for(i = 0 ; i < iLen ; ++i) {
CHKiRet(rsCStrAppendChar(pCStr, c));
NEXTC;
}
CHKiRet(rsCStrFinish(pCStr));
/* check terminator */
if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER);
*ppCStr = pCStr;
finalize_it:
if(iRet != RS_RET_OK && pCStr != NULL)
rsCStrDestruct(&pCStr);
RETiRet;
}
/* de-serialize a syslogTime -- rgerhards,2008-01-08 */
#define GETVAL(var) \
CHKiRet(objDeserializeNumber(&l, pStrm)); \
pTime->var = l;
static rsRetVal objDeserializeSyslogTime(syslogTime_t *pTime, strm_t *pStrm)
{
DEFiRet;
number_t l;
uchar c;
assert(pTime != NULL);
GETVAL(timeType);
GETVAL(year);
GETVAL(month);
GETVAL(day);
GETVAL(hour);
GETVAL(minute);
GETVAL(second);
GETVAL(secfrac);
GETVAL(secfracPrecision);
/* OffsetMode is a single character! */
NEXTC; pTime->OffsetMode = c;
NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER);
GETVAL(OffsetHour);
GETVAL(OffsetMinute);
finalize_it:
RETiRet;
}
#undef GETVAL
/* de-serialize an object header
* rgerhards, 2008-01-07
*/
static rsRetVal objDeserializeHeader(uchar *pszRecType, cstr_t **ppstrID, int* poVers, strm_t *pStrm)
{
DEFiRet;
number_t oVers;
uchar c;
assert(ppstrID != NULL);
assert(poVers != NULL);
assert(!strcmp((char*) pszRecType, "Obj") || !strcmp((char*) pszRecType, "OPB"));
/* check header cookie */
NEXTC; if(c != COOKIE_OBJLINE) ABORT_FINALIZE(RS_RET_INVALID_HEADER);
NEXTC; if(c != pszRecType[0]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE);
NEXTC; if(c != pszRecType[1]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE);
NEXTC; if(c != pszRecType[2]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE);
NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_HEADER);
NEXTC; if(c != '1') ABORT_FINALIZE(RS_RET_INVALID_HEADER_VERS);
NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_HEADER_VERS);
/* object type and version */
CHKiRet(objDeserializeEmbedStr(ppstrID, pStrm));
CHKiRet(objDeserializeNumber(&oVers, pStrm));
/* and now we skip over the rest until the delemiting \n */
NEXTC;
while(c != '\n') {
NEXTC;
}
*poVers = oVers;
finalize_it:
RETiRet;
}
/* Deserialize a single property. Pointer must be positioned at begin of line. Whole line
* up until the \n is read.
*/
static rsRetVal objDeserializeProperty(var_t *pProp, strm_t *pStrm)
{
DEFiRet;
number_t i;
number_t iLen;
uchar c;
assert(pProp != NULL);
/* check cookie */
NEXTC;
if(c != COOKIE_PROPLINE) {
/* oops, we've read one char that does not belong to use - unget it first */
CHKiRet(strmUnreadChar(pStrm, c));
ABORT_FINALIZE(RS_RET_NO_PROPLINE);
}
/* get the property name first */
CHKiRet(rsCStrConstruct(&pProp->pcsName));
NEXTC;
while(c != ':') {
CHKiRet(rsCStrAppendChar(pProp->pcsName, c));
NEXTC;
}
CHKiRet(rsCStrFinish(pProp->pcsName));
/* property type */
CHKiRet(objDeserializeNumber(&i, pStrm));
pProp->varType = i;
/* size (needed for strings) */
CHKiRet(objDeserializeNumber(&iLen, pStrm));
/* we now need to deserialize the value */
switch(pProp->varType) {
case VARTYPE_STR:
CHKiRet(objDeserializeStr(&pProp->val.pStr, iLen, pStrm));
break;
case VARTYPE_NUMBER:
CHKiRet(objDeserializeNumber(&pProp->val.num, pStrm));
break;
case VARTYPE_SYSLOGTIME:
CHKiRet(objDeserializeSyslogTime(&pProp->val.vSyslogTime, pStrm));
break;
default:
dbgprintf("invalid VARTYPE %d\n", pProp->varType);
break;
}
/* we should now be at the end of the line. So the next char must be \n */
NEXTC;
if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_PROPFRAME);
finalize_it:
RETiRet;
}
/* de-serialize an object trailer. This does not get any data but checks if the
* format is ok.
* rgerhards, 2008-01-07
*/
static rsRetVal objDeserializeTrailer(strm_t *pStrm)
{
DEFiRet;
uchar c;
/* check header cookie */
NEXTC; if(c != COOKIE_ENDLINE) ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
NEXTC; if(c != 'E') ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
NEXTC; if(c != 'n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
NEXTC; if(c != 'd') ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
NEXTC; if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
NEXTC; if(c != COOKIE_BLANKLINE) ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
NEXTC; if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
finalize_it:
RETiRet;
}
/* This method tries to recover a serial store if it got out of sync.
* To do so, it scans the line beginning cookies and waits for the object
* cookie. If that is found, control is returned. If the store is exhausted,
* we will receive an RS_RET_EOF error as part of NEXTC, which will also
* terminate this function. So we may either return with somehting that
* looks like a valid object or end of store.
* rgerhards, 2008-01-07
*/
static rsRetVal objDeserializeTryRecover(strm_t *pStrm)
{
DEFiRet;
uchar c;
int bWasNL;
int bRun;
assert(pStrm != NULL);
bRun = 1;
bWasNL = 0;
while(bRun) {
NEXTC;
if(c == '\n')
bWasNL = 1;
else {
if(bWasNL == 1 && c == COOKIE_OBJLINE)
bRun = 0; /* we found it! */
else
bWasNL = 0;
}
}
CHKiRet(strmUnreadChar(pStrm, c));
finalize_it:
dbgprintf("deserializer has possibly been able to re-sync and recover, state %d\n", iRet);
RETiRet;
}
/* De-serialize the properties of an object. This includes processing
* of the trailer. Header must already have been processed.
* rgerhards, 2008-01-11
*/
static rsRetVal objDeserializeProperties(obj_t *pObj, objInfo_t *pObjInfo, strm_t *pStrm)
{
DEFiRet;
var_t *pVar = NULL;
ISOBJ_assert(pObj);
ISOBJ_TYPE_assert(pStrm, strm);
ASSERT(pObjInfo != NULL);
CHKiRet(var.Construct(&pVar));
CHKiRet(var.ConstructFinalize(pVar));
iRet = objDeserializeProperty(pVar, pStrm);
while(iRet == RS_RET_OK) {
CHKiRet(pObjInfo->objMethods[objMethod_SETPROPERTY](pObj, pVar));
/* re-init var object - TODO: method of var! */
rsCStrDestruct(&pVar->pcsName); /* no longer needed */
if(pVar->varType == VARTYPE_STR) {
if(pVar->val.pStr != NULL)
rsCStrDestruct(&pVar->val.pStr);
}
iRet = objDeserializeProperty(pVar, pStrm);
}
if(iRet != RS_RET_NO_PROPLINE)
FINALIZE;
CHKiRet(objDeserializeTrailer(pStrm)); /* do trailer checks */
finalize_it:
if(pVar != NULL)
var.Destruct(&pVar);
RETiRet;
}
/* De-Serialize an object.
* Params: Pointer to object Pointer (pObj) (like a obj_t**, but can not do that due to compiler warning)
* expected object ID (to check against), a fixup function that can modify the object before it is finalized
* and a user pointer that is to be passed to that function in addition to the object. The fixup function
* pointer may be NULL, in which case none is called.
* The caller must destruct the created object.
* rgerhards, 2008-01-07
*/
static rsRetVal
Deserialize(void *ppObj, uchar *pszTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr)
{
DEFiRet;
rsRetVal iRetLocal;
obj_t *pObj = NULL;
int oVers = 0; /* after all, it is totally useless but takes up some execution time... */
cstr_t *pstrID = NULL;
objInfo_t *pObjInfo;
assert(ppObj != NULL);
assert(pszTypeExpected != NULL);
ISOBJ_TYPE_assert(pStrm, strm);
/* we de-serialize the header. if all goes well, we are happy. However, if
* we experience a problem, we try to recover. We do this by skipping to
* the next object header. This is defined via the line-start cookies. In
* worst case, we exhaust the queue, but then we receive EOF return state,
* from objDeserializeTryRecover(), what will cause us to ultimately give up.
* rgerhards, 2008-07-08
*/
do {
iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm);
if(iRetLocal != RS_RET_OK) {
dbgprintf("objDeserialize error %d during header processing - trying to recover\n", iRetLocal);
CHKiRet(objDeserializeTryRecover(pStrm));
}
} while(iRetLocal != RS_RET_OK);
if(rsCStrSzStrCmp(pstrID, pszTypeExpected, strlen((char*)pszTypeExpected))) // TODO: optimize strlen() - caller shall provide
ABORT_FINALIZE(RS_RET_INVALID_OID);
CHKiRet(FindObjInfo(pstrID, &pObjInfo));
CHKiRet(pObjInfo->objMethods[objMethod_CONSTRUCT](&pObj));
/* we got the object, now we need to fill the properties */
CHKiRet(objDeserializeProperties(pObj, pObjInfo, pStrm));
/* check if we need to call a fixup function that modifies the object
* before it is finalized. -- rgerhards, 2008-01-13
*/
if(fFixup != NULL)
CHKiRet(fFixup(pObj, pUsr));
/* we have a valid object, let's finalize our work and return */
if(objInfoIsImplemented(pObjInfo, objMethod_CONSTRUCTION_FINALIZER))
CHKiRet(pObjInfo->objMethods[objMethod_CONSTRUCTION_FINALIZER](pObj));
*((obj_t**) ppObj) = pObj;
finalize_it:
if(iRet != RS_RET_OK && pObj != NULL)
free(pObj); // TODO: check if we can call destructor 2008-01-13 rger
if(pstrID != NULL)
rsCStrDestruct(&pstrID);
RETiRet;
}
/* De-Serialize an object, but treat it as property bag.
* rgerhards, 2008-01-11
*/
rsRetVal
objDeserializeObjAsPropBag(obj_t *pObj, strm_t *pStrm)
{
DEFiRet;
rsRetVal iRetLocal;
cstr_t *pstrID = NULL;
int oVers = 0; /* after all, it is totally useless but takes up some execution time... */
objInfo_t *pObjInfo;
ISOBJ_assert(pObj);
ISOBJ_TYPE_assert(pStrm, strm);
/* we de-serialize the header. if all goes well, we are happy. However, if
* we experience a problem, we try to recover. We do this by skipping to
* the next object header. This is defined via the line-start cookies. In
* worst case, we exhaust the queue, but then we receive EOF return state
* from objDeserializeTryRecover(), what will cause us to ultimately give up.
* rgerhards, 2008-07-08
*/
do {
iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm);
if(iRetLocal != RS_RET_OK) {
dbgprintf("objDeserializeObjAsPropBag error %d during header - trying to recover\n", iRetLocal);
CHKiRet(objDeserializeTryRecover(pStrm));
}
} while(iRetLocal != RS_RET_OK);
if(rsCStrSzStrCmp(pstrID, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID))
ABORT_FINALIZE(RS_RET_INVALID_OID);
CHKiRet(FindObjInfo(pstrID, &pObjInfo));
/* we got the object, now we need to fill the properties */
CHKiRet(objDeserializeProperties(pObj, pObjInfo, pStrm));
finalize_it:
if(pstrID != NULL)
rsCStrDestruct(&pstrID);
RETiRet;
}
/* De-Serialize an object property bag. As a property bag contains only partial properties,
* it is not instanciable. Thus, the caller must provide a pointer of an already-instanciated
* object of the correct type.
* Params: Pointer to object (pObj)
* Pointer to be passed to the function
* The caller must destruct the created object.
* rgerhards, 2008-01-07
*/
static rsRetVal
DeserializePropBag(obj_t *pObj, strm_t *pStrm)
{
DEFiRet;
rsRetVal iRetLocal;
cstr_t *pstrID = NULL;
int oVers;
objInfo_t *pObjInfo;
ISOBJ_assert(pObj);
ISOBJ_TYPE_assert(pStrm, strm);
/* we de-serialize the header. if all goes well, we are happy. However, if
* we experience a problem, we try to recover. We do this by skipping to
* the next object header. This is defined via the line-start cookies. In
* worst case, we exhaust the queue, but then we receive EOF return state
* from objDeserializeTryRecover(), what will cause us to ultimately give up.
* rgerhards, 2008-07-08
*/
do {
iRetLocal = objDeserializeHeader((uchar*) "OPB", &pstrID, &oVers, pStrm);
if(iRetLocal != RS_RET_OK) {
dbgprintf("objDeserializePropBag error %d during header - trying to recover\n", iRetLocal);
CHKiRet(objDeserializeTryRecover(pStrm));
}
} while(iRetLocal != RS_RET_OK);
if(rsCStrSzStrCmp(pstrID, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID))
ABORT_FINALIZE(RS_RET_INVALID_OID);
CHKiRet(FindObjInfo(pstrID, &pObjInfo));
/* we got the object, now we need to fill the properties */
CHKiRet(objDeserializeProperties(pObj, pObjInfo, pStrm));
finalize_it:
if(pstrID != NULL)
rsCStrDestruct(&pstrID);
RETiRet;
}
#undef NEXTC /* undef helper macro */
/* --------------- end object serializiation / deserialization support --------------- */
/* set the object (instance) name
* rgerhards, 2008-01-29
* TODO: change the naming to a rsCStr obj! (faster)
*/
static rsRetVal
SetName(obj_t *pThis, uchar *pszName)
{
DEFiRet;
if(pThis->pszName != NULL)
free(pThis->pszName);
pThis->pszName = (uchar*) strdup((char*) pszName);
if(pThis->pszName == NULL)
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
finalize_it:
RETiRet;
}
/* get the object (instance) name
* Note that we use a non-standard calling convention. Thus function must never
* fail, else we run into real big problems. So it must make sure that at least someting
* is returned.
* rgerhards, 2008-01-30
*/
static uchar *
GetName(obj_t *pThis)
{
uchar *ret;
uchar szName[128];
BEGINfunc
ISOBJ_assert(pThis);
if(pThis->pszName == NULL) {
snprintf((char*)szName, sizeof(szName)/sizeof(uchar), "%s %p", objGetClassName(pThis), pThis);
SetName(pThis, szName);
/* looks strange, but we NEED to re-check because if there was an
* error in objSetName(), the pointer may still be NULL
*/
if(pThis->pszName == NULL) {
ret = objGetClassName(pThis);
} else {
ret = pThis->pszName;
}
} else {
ret = pThis->pszName;
}
ENDfunc
return ret;
}
/* Find the objInfo object for the current object
* rgerhards, 2008-02-29
*/
static rsRetVal
FindObjInfo(cstr_t *pstrOID, objInfo_t **ppInfo)
{
DEFiRet;
int bFound;
int i;
assert(pstrOID != NULL);
assert(ppInfo != NULL);
bFound = 0;
i = 0;
while(!bFound && i < OBJ_NUM_IDS) {
#if 0
RUNLOG_VAR("%d", i);
if(arrObjInfo[i] != NULL) {
RUNLOG_VAR("%p", arrObjInfo[i]->pszID);
RUNLOG_VAR("%s", arrObjInfo[i]->pszID);
}
#endif
if(arrObjInfo[i] != NULL && !rsCStrSzStrCmp(pstrOID, arrObjInfo[i]->pszID, arrObjInfo[i]->lenID)) {
bFound = 1;
break;
}
++i;
}
if(!bFound)
ABORT_FINALIZE(RS_RET_NOT_FOUND);
*ppInfo = arrObjInfo[i];
finalize_it:
if(iRet == RS_RET_OK) {
/* DEV DEBUG ONLY dbgprintf("caller requested object '%s', found at index %d\n", (*ppInfo)->pszID, i);*/
/*EMPTY BY INTENSION*/;
} else {
dbgprintf("caller requested object '%s', not found (iRet %d)\n", rsCStrGetSzStr(pstrOID), iRet);
}
RETiRet;
}
/* register a classes' info pointer, so that we can reference it later, if needed to
* (e.g. for de-serialization support).
* rgerhards, 2008-01-07
* In this function, we look for a free space in the object table. While we do so, we
* also detect if the same object has already been registered, which is not valid.
* rgerhards, 2008-02-29
*/
static rsRetVal
RegisterObj(uchar *pszObjName, objInfo_t *pInfo)
{
DEFiRet;
int bFound;
int i;
assert(pszObjName != NULL);
assert(pInfo != NULL);
bFound = 0;
i = 0;
while(!bFound && i < OBJ_NUM_IDS && arrObjInfo[i] != NULL) {
if( arrObjInfo[i] != NULL
&& !strcmp((char*)arrObjInfo[i]->pszID, (char*)pszObjName)) {
bFound = 1;
break;
}
++i;
}
if(bFound) ABORT_FINALIZE(RS_RET_OBJ_ALREADY_REGISTERED);
if(i >= OBJ_NUM_IDS) ABORT_FINALIZE(RS_RET_OBJ_REGISTRY_OUT_OF_SPACE);
arrObjInfo[i] = pInfo;
/* DEV debug only: dbgprintf("object '%s' successfully registered with index %d, qIF %p\n", pszObjName, i, pInfo->QueryIF); */
finalize_it:
if(iRet != RS_RET_OK) {
errmsg.LogError(NO_ERRCODE, "registering object '%s' failed with error code %d", pszObjName, iRet);
}
RETiRet;
}
/* deregister a classes' info pointer, usually called because the class is unloaded.
* After deregistration, the class can no longer be accessed, except if it is reloaded.
* rgerhards, 2008-03-10
*/
static rsRetVal
UnregisterObj(uchar *pszObjName)
{
DEFiRet;
int bFound;
int i;
assert(pszObjName != NULL);
bFound = 0;
i = 0;
while(!bFound && i < OBJ_NUM_IDS) {
if( arrObjInfo[i] != NULL
&& !strcmp((char*)arrObjInfo[i]->pszID, (char*)pszObjName)) {
bFound = 1;
break;
}
++i;
}
if(!bFound)
ABORT_FINALIZE(RS_RET_OBJ_NOT_REGISTERED);
InfoDestruct(&arrObjInfo[i]);
/* DEV debug only: dbgprintf("object '%s' successfully unregistered with index %d\n", pszObjName, i); */
finalize_it:
if(iRet != RS_RET_OK) {
dbgprintf("unregistering object '%s' failed with error code %d\n", pszObjName, iRet);
}
RETiRet;
}
/* This function shall be called by anyone who would like to use an object. It will
* try to locate the object, load it into memory if not already present and return
* a pointer to the objects interface.
* rgerhards, 2008-02-29
*/
static rsRetVal
UseObj(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf)
{
DEFiRet;
cstr_t *pStr = NULL;
objInfo_t *pObjInfo;
/* DEV debug only: dbgprintf("source file %s requests object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded); */
if(pIf->ifIsLoaded == 1) {
ABORT_FINALIZE(RS_RET_OK); /* we are already set */
}
if(pIf->ifIsLoaded == 2) {
ABORT_FINALIZE(RS_RET_LOAD_ERROR); /* we had a load error and can not continue */
}
/* we must be careful that we do not enter in infinite loop if an error occurs during
* loading a module. ModLoad emits an error message in such cases and that potentially
* can trigger the same code here. So we initially set the module state to "load error"
* and set it to "fully initialized" when the load succeeded. It's a bit hackish, but
* looks like a good solution. -- rgerhards, 2008-03-07
*/
pIf->ifIsLoaded = 2;
CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName));
iRet = FindObjInfo(pStr, &pObjInfo);
if(iRet == RS_RET_NOT_FOUND) {
/* in this case, we need to see if we can dynamically load the object */
if(pObjFile == NULL) {
FINALIZE; /* no chance, we have lost... */
} else {
CHKiRet(module.Load(pObjFile));
/* NOW, we must find it or we have a problem... */
CHKiRet(FindObjInfo(pStr, &pObjInfo));
}
} else if(iRet != RS_RET_OK) {
FINALIZE; /* give up */
}
/* if we reach this point, we have a valid pObjInfo */
if(pObjFile != NULL) { /* NULL means core module */
module.Use(srcFile, pObjInfo->pModInfo); /* increase refcount */
}
CHKiRet(pObjInfo->QueryIF(pIf));
pIf->ifIsLoaded = 1; /* we are happy */
finalize_it:
if(pStr != NULL)
rsCStrDestruct(&pStr);
RETiRet;
}
/* This function shall be called when a caller is done with an object. Its primary
* purpose is to keep the reference count correct, which is highly important for
* modules residing in loadable modules.
* rgerhards, 2008-03-10
*/
static rsRetVal
ReleaseObj(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf)
{
DEFiRet;
cstr_t *pStr = NULL;
objInfo_t *pObjInfo;
dbgprintf("source file %s requests object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded);
if(pObjFile == NULL)
FINALIZE; /* if it is not a lodable module, we do not need to do anything... */
if(pIf->ifIsLoaded == 0) {
ABORT_FINALIZE(RS_RET_OK); /* we are already set */ /* TODO: flag an error? */
}
if(pIf->ifIsLoaded == 2) {
pIf->ifIsLoaded = 0; /* clean up */
ABORT_FINALIZE(RS_RET_OK); /* we had a load error and can not continue */
}
CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName));
CHKiRet(FindObjInfo(pStr, &pObjInfo));
/* if we reach this point, we have a valid pObjInfo */
//if(pObjInfo->pModInfo != NULL) { /* NULL means core module */
module.Release(srcFile, &pObjInfo->pModInfo); /* decrease refcount */
pIf->ifIsLoaded = 0; /* indicated "no longer valid" */
finalize_it:
if(pStr != NULL)
rsCStrDestruct(&pStr);
RETiRet;
}
/* queryInterface function
* rgerhards, 2008-02-29
*/
BEGINobjQueryInterface(obj)
CODESTARTobjQueryInterface(obj)
if(pIf->ifVersion != objCURR_IF_VERSION) { /* check for current version, increment on each change */
ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
}
/* ok, we have the right interface, so let's fill it
* Please note that we may also do some backwards-compatibility
* work here (if we can support an older interface version - that,
* of course, also affects the "if" above).
*/
pIf->UseObj = UseObj;
pIf->ReleaseObj = ReleaseObj;
pIf->InfoConstruct = InfoConstruct;
pIf->DestructObjSelf = DestructObjSelf;
pIf->BeginSerializePropBag = BeginSerializePropBag;
pIf->InfoSetMethod = InfoSetMethod;
pIf->BeginSerialize = BeginSerialize;
pIf->SerializeProp = SerializeProp;
pIf->EndSerialize = EndSerialize;
pIf->RegisterObj = RegisterObj;
pIf->UnregisterObj = UnregisterObj;
pIf->Deserialize = Deserialize;
pIf->DeserializePropBag = DeserializePropBag;
pIf->SetName = SetName;
pIf->GetName = GetName;
finalize_it:
ENDobjQueryInterface(obj)
/* This function returns a pointer to our own interface. It is used as the
* hook that every object (including dynamically loaded ones) can use to
* obtain a pointer to our interface which than can be used to obtain
* pointers to any other interface in the system. This function must be
* externally visible because of its special nature.
* rgerhards, 2008-02-29 [nice - will have that date the next time in 4 years ;)]
*/
rsRetVal
objGetObjInterface(obj_if_t *pIf)
{
DEFiRet;
assert(pIf != NULL);
objQueryInterface(pIf);
RETiRet;
}
/* exit our class
* rgerhards, 2008-03-11
*/
rsRetVal
objClassExit(void)
{
DEFiRet;
/* release objects we no longer need */
objRelease(var, CORE_COMPONENT);
objRelease(module, CORE_COMPONENT);
objRelease(errmsg, CORE_COMPONENT);
/* TODO: implement the class exits! */
#if 0
errmsgClassInit(pModInfo);
cfsyslineInit(pModInfo);
varClassInit(pModInfo);
#endif
moduleClassExit();
RETiRet;
}
/* initialize our own class
* Please note that this also initializes those classes that we rely on.
* Though this is a bit dirty, we need to do it - otherwise we can't get
* around that bootstrap problem. We need to face the fact the the obj
* class is a little different from the rest of the system, as it provides
* the core class loader functionality.
* rgerhards, 2008-02-29
*/
rsRetVal
objClassInit(modInfo_t *pModInfo)
{
DEFiRet;
int i;
/* first, initialize the object system itself. This must be done
* before any other object is created.
*/
for(i = 0 ; i < OBJ_NUM_IDS ; ++i) {
arrObjInfo[i] = NULL;
}
/* request objects we use */
CHKiRet(objGetObjInterface(&obj)); /* get ourselves ;) */
/* init classes we use (limit to as few as possible!) */
CHKiRet(errmsgClassInit(pModInfo));
CHKiRet(cfsyslineInit());
CHKiRet(varClassInit(pModInfo));
CHKiRet(moduleClassInit(pModInfo));
CHKiRet(objUse(var, CORE_COMPONENT));
CHKiRet(objUse(module, CORE_COMPONENT));
CHKiR
y[z] = 256
assert_equal(256, y[z])
x = Hash.new(0)
x[1] = 1
assert_equal(1, x[1])
assert_equal(0, x[2])
x = Hash.new([])
assert_equal([], x[22])
assert_same(x[22], x[22])
x = Hash.new{[]}
assert_equal([], x[22])
assert_not_same(x[22], x[22])
x = Hash.new{|h,k| z = k; h[k] = k*2}
z = 0
assert_equal(44, x[22])
assert_equal(22, z)
z = 0
assert_equal(44, x[22])
assert_equal(0, z)
x.default = 5
assert_equal(5, x[23])
x = Hash.new
def x.default(k)
$z = k
self[k] = k*2
end
$z = 0
assert_equal(44, x[22])
assert_equal(22, $z)
$z = 0
assert_equal(44, x[22])
assert_equal(0, $z)
end
# From rubicon
def setup
@cls = Hash
@h = @cls[
1 => 'one', 2 => 'two', 3 => 'three',
self => 'self', true => 'true', nil => 'nil',
'nil' => nil
]
@verbose = $VERBOSE
$VERBOSE = nil
end
def teardown
$VERBOSE = @verbose
end
def test_s_AREF
h = @cls["a" => 100, "b" => 200]
assert_equal(100, h['a'])
assert_equal(200, h['b'])
assert_nil(h['c'])
h = @cls.[]("a" => 100, "b" => 200)
assert_equal(100, h['a'])
assert_equal(200, h['b'])
assert_nil(h['c'])
end
def test_s_new
h = @cls.new
assert_instance_of(@cls, h)
assert_nil(h.default)
assert_nil(h['spurious'])
h = @cls.new('default')
assert_instance_of(@cls, h)
assert_equal('default', h.default)
assert_equal('default', h['spurious'])
end
def test_AREF # '[]'
t = Time.now
h = @cls[
1 => 'one', 2 => 'two', 3 => 'three',
self => 'self', t => 'time', nil => 'nil',
'nil' => nil
]
assert_equal('one', h[1])
assert_equal('two', h[2])
assert_equal('three', h[3])
assert_equal('self', h[self])
assert_equal('time', h[t])
assert_equal('nil', h[nil])
assert_equal(nil, h['nil'])
assert_equal(nil, h['koala'])
h1 = h.dup
h1.default = :default
assert_equal('one', h1[1])
assert_equal('two', h1[2])
assert_equal('three', h1[3])
assert_equal('self', h1[self])
assert_equal('time', h1[t])
assert_equal('nil', h1[nil])
assert_equal(nil, h1['nil'])
assert_equal(:default, h1['koala'])
end
def test_ASET # '[]='
t = Time.now
h = @cls.new
h[1] = 'one'
h[2] = 'two'
h[3] = 'three'
h[self] = 'self'
h[t] = 'time'
h[nil] = 'nil'
h['nil'] = nil
assert_equal('one', h[1])
assert_equal('two', h[2])
assert_equal('three', h[3])
assert_equal('self', h[self])
assert_equal('time', h[t])
assert_equal('nil', h[nil])
assert_equal(nil, h['nil'])
assert_equal(nil, h['koala'])
h[1] = 1
h[nil] = 99
h['nil'] = nil
z = [1,2]
h[z] = 256
assert_equal(1, h[1])
assert_equal('two', h[2])
assert_equal('three', h[3])
assert_equal('self', h[self])
assert_equal('time', h[t])
assert_equal(99, h[nil])
assert_equal(nil, h['nil'])
assert_equal(nil, h['koala'])
assert_equal(256, h[z])
end
def test_EQUAL # '=='
h1 = @cls[ "a" => 1, "c" => 2 ]
h2 = @cls[ "a" => 1, "c" => 2, 7 => 35 ]
h3 = @cls[ "a" => 1, "c" => 2, 7 => 35 ]
h4 = @cls[ ]
assert(h1 == h1)
assert(h2 == h2)
assert(h3 == h3)
assert(h4 == h4)
assert(!(h1 == h2))
assert(h2 == h3)
assert(!(h3 == h4))
end
def test_clear
assert(@h.size > 0)
@h.clear
assert_equal(0, @h.size)
assert_nil(@h[1])
end
def test_clone
for taint in [ false, true ]
for untrust in [ false, true ]
for frozen in [ false, true ]
a = @h.clone
a.taint if taint
a.untrust if untrust
a.freeze if frozen
b = a.clone
assert_equal(a, b)
assert(a.__id__ != b.__id__)
assert_equal(a.frozen?, b.frozen?)
assert_equal(a.untrusted?, b.untrusted?)
assert_equal(a.tainted?, b.tainted?)
end
end
end
end
def test_default
assert_nil(@h.default)
h = @cls.new(:xyzzy)
assert_equal(:xyzzy, h.default)
end
def test_default=
assert_nil(@h.default)
@h.default = :xyzzy
assert_equal(:xyzzy, @h.default)
end
def test_delete
h1 = @cls[ 1 => 'one', 2 => 'two', true => 'true' ]
h2 = @cls[ 1 => 'one', 2 => 'two' ]
h3 = @cls[ 2 => 'two' ]
assert_equal('true', h1.delete(true))
assert_equal(h2, h1)
assert_equal('one', h1.delete(1))
assert_equal(h3, h1)
assert_equal('two', h1.delete(2))
assert_equal(@cls[], h1)
assert_nil(h1.delete(99))
assert_equal(@cls[], h1)
assert_equal('default 99', h1.delete(99) {|i| "default #{i}" })
end
def test_delete_if
base = @cls[ 1 => 'one', 2 => false, true => 'true', 'cat' => 99 ]
h1 = @cls[ 1 => 'one', 2 => false, true => 'true' ]
h2 = @cls[ 2 => false, 'cat' => 99 ]
h3 = @cls[ 2 => false ]
h = base.dup
assert_equal(h, h.delete_if { false })
assert_equal(@cls[], h.delete_if { true })
h = base.dup
assert_equal(h1, h.delete_if {|k,v| k.instance_of?(String) })
assert_equal(h1, h)
h = base.dup
assert_equal(h2, h.delete_if {|k,v| v.instance_of?(String) })
assert_equal(h2, h)
h = base.dup
assert_equal(h3, h.delete_if {|k,v| v })
assert_equal(h3, h)
h = base.dup
n = 0
h.delete_if {|*a|
n += 1
assert_equal(2, a.size)
assert_equal(base[a[0]], a[1])
h.shift
true
}
assert_equal(base.size, n)
end
def test_dup
for taint in [ false, true ]
for untrust in [ false, true ]
for frozen in [ false, true ]
a = @h.dup
a.taint if taint
a.freeze if frozen
b = a.dup
assert_equal(a, b)
assert(a.__id__ != b.__id__)
assert_equal(false, b.frozen?)
assert_equal(a.tainted?, b.tainted?)
assert_equal(a.untrusted?, b.untrusted?)
end
end
end
end
def test_each
count = 0
@cls[].each { |k, v| count + 1 }
assert_equal(0, count)
h = @h
h.each do |k, v|
assert_equal(v, h.delete(k))
end
assert_equal(@cls[], h)
end
def test_each_key
count = 0
@cls[].each_key { |k| count + 1 }
assert_equal(0, count)
h = @h
h.each_key do |k|
h.delete(k)
end
assert_equal(@cls[], h)
end
def test_each_pair
count = 0
@cls[].each_pair { |k, v| count + 1 }
assert_equal(0, count)
h = @h
h.each_pair do |k, v|
assert_equal(v, h.delete(k))
end
assert_equal(@cls[], h)
end
def test_each_value
res = []
@cls[].each_value { |v| res << v }
assert_equal(0, [].length)
@h.each_value { |v| res << v }
assert_equal(0, [].length)
expected = []
@h.each { |k, v| expected << v }
assert_equal([], expected - res)
assert_equal([], res - expected)
end
def test_empty?
assert(@cls[].empty?)
assert(!@h.empty?)
end
def test_fetch
assert_raise(KeyError) { @cls[].fetch(1) }
assert_raise(KeyError) { @h.fetch('gumby') }
assert_equal('gumbygumby', @h.fetch('gumby') {|k| k * 2 })
assert_equal('pokey', @h.fetch('gumby', 'pokey'))
assert_equal('one', @h.fetch(1))
assert_equal(nil, @h.fetch('nil'))
assert_equal('nil', @h.fetch(nil))
end
def test_key?
assert(!@cls[].key?(1))
assert(!@cls[].key?(nil))
assert(@h.key?(nil))
assert(@h.key?(1))
assert(!@h.key?('gumby'))
end
def test_value?
assert(!@cls[].value?(1))
assert(!@cls[].value?(nil))
assert(@h.value?('one'))
assert(@h.value?(nil))
assert(!@h.value?('gumby'))
end
def test_include?
assert(!@cls[].include?(1))
assert(!@cls[].include?(nil))
assert(@h.include?(nil))
assert(@h.include?(1))
assert(!@h.include?('gumby'))
end
def test_key
assert_equal(1, @h.key('one'))
assert_equal(nil, @h.key('nil'))
assert_equal('nil', @h.key(nil))
assert_equal(nil, @h.key('gumby'))
assert_equal(nil, @cls[].key('gumby'))
end
def test_values_at
res = @h.values_at('dog', 'cat', 'horse')
assert(res.length == 3)
assert_equal([nil, nil, nil], res)
res = @h.values_at
assert(res.length == 0)
res = @h.values_at(3, 2, 1, nil)
assert_equal 4, res.length
assert_equal %w( three two one nil ), res
res = @h.values_at(3, 99, 1, nil)
assert_equal 4, res.length
assert_equal ['three', nil, 'one', 'nil'], res
end
def test_invert
h = @h.invert
assert_equal(1, h['one'])
assert_equal(true, h['true'])
assert_equal(nil, h['nil'])
h.each do |k, v|
assert(@h.key?(v)) # not true in general, but works here
end
h = @cls[ 'a' => 1, 'b' => 2, 'c' => 1].invert
assert_equal(2, h.length)
assert(h[1] == 'a' || h[1] == 'c')
assert_equal('b', h[2])
end
def test_key?
assert(!@cls[].key?(1))
assert(!@cls[].key?(nil))
assert(@h.key?(nil))
assert(@h.key?(1))
assert(!@h.key?('gumby'))
end
def test_keys
assert_equal([], @cls[].keys)
keys = @h.keys
expected = []
@h.each { |k, v| expected << k }
assert_equal([], keys - expected)
assert_equal([], expected - keys)
end
def test_length
assert_equal(0, @cls[].length)
assert_equal(7, @h.length)
end
def test_member?
assert(!@cls[].member?(1))
assert(!@cls[].member?(nil))
assert(@h.member?(nil))
assert(@h.member?(1))
assert(!@h.member?('gumby'))
end
def test_rehash
a = [ "a", "b" ]
c = [ "c", "d" ]
h = @cls[ a => 100, c => 300 ]
assert_equal(100, h[a])
a[0] = "z"
assert_nil(h[a])
h.rehash
assert_equal(100, h[a])
end
def test_reject
base = @cls[ 1 => 'one', 2 => false, true => 'true', 'cat' => 99 ]
h1 = @cls[ 1 => 'one', 2 => false, true => 'true' ]
h2 = @cls[ 2 => false, 'cat' => 99 ]
h3 = @cls[ 2 => false ]
h = base.dup
assert_equal(h, h.reject { false })
assert_equal(@cls[], h.reject { true })
h = base.dup
assert_equal(h1, h.reject {|k,v| k.instance_of?(String) })
assert_equal(h2, h.reject {|k,v| v.instance_of?(String) })
assert_equal(h3, h.reject {|k,v| v })
assert_equal(base, h)
end
def test_reject!
base = @cls[ 1 => 'one', 2 => false, true => 'true', 'cat' => 99 ]
h1 = @cls[ 1 => 'one', 2 => false, true => 'true' ]
h2 = @cls[ 2 => false, 'cat' => 99 ]
h3 = @cls[ 2 => false ]
h = base.dup
assert_equal(nil, h.reject! { false })
assert_equal(@cls[], h.reject! { true })
h = base.dup
assert_equal(h1, h.reject! {|k,v| k.instance_of?(String) })
assert_equal(h1, h)
h = base.dup
assert_equal(h2, h.reject! {|k,v| v.instance_of?(String) })
assert_equal(h2, h)
h = base.dup
assert_equal(h3, h.reject! {|k,v| v })
assert_equal(h3, h)
end
def test_replace
h = @cls[ 1 => 2, 3 => 4 ]
h1 = h.replace(@cls[ 9 => 8, 7 => 6 ])
assert_equal(h, h1)
assert_equal(8, h[9])
assert_equal(6, h[7])
assert_nil(h[1])
assert_nil(h[2])
end
def test_shift
h = @h.dup
@h.length.times {
k, v = h.shift
assert(@h.key?(k))
assert_equal(@h[k], v)
}
assert_equal(0, h.length)
end
def test_size
assert_equal(0, @cls[].length)
assert_equal(7, @h.length)
end
def test_sort
h = @cls[].sort
assert_equal([], h)
h = @cls[ 1 => 1, 2 => 1 ].sort
assert_equal([[1,1], [2,1]], h)
h = @cls[ 'cat' => 'feline', 'ass' => 'asinine', 'bee' => 'beeline' ]
h1 = h.sort
assert_equal([ %w(ass asinine), %w(bee beeline), %w(cat feline)], h1)
end
def test_store
t = Time.now
h = @cls.new
h.store(1, 'one')
h.store(2, 'two')
h.store(3, 'three')
h.store(self, 'self')
h.store(t, 'time')
h.store(nil, 'nil')
h.store('nil', nil)
assert_equal('one', h[1])
assert_equal('two', h[2])
assert_equal('three', h[3])
assert_equal('self', h[self])
assert_equal('time', h[t])
assert_equal('nil', h[nil])
assert_equal(nil, h['nil'])
assert_equal(nil, h['koala'])
h.store(1, 1)
h.store(nil, 99)
h.store('nil', nil)
assert_equal(1, h[1])
assert_equal('two', h[2])
assert_equal('three', h[3])
assert_equal('self', h[self])
assert_equal('time', h[t])
assert_equal(99, h[nil])
assert_equal(nil, h['nil'])
assert_equal(nil, h['koala'])
end
def test_to_a
assert_equal([], @cls[].to_a)
assert_equal([[1,2]], @cls[ 1=>2 ].to_a)
a = @cls[ 1=>2, 3=>4, 5=>6 ].to_a
assert_equal([1,2], a.delete([1,2]))
assert_equal([3,4], a.delete([3,4]))
assert_equal([5,6], a.delete([5,6]))
assert_equal(0, a.length)
h = @cls[ 1=>2, 3=>4, 5=>6 ]
h.taint
h.untrust
a = h.to_a
assert_equal(true, a.tainted?)
assert_equal(true, a.untrusted?)
end
def test_to_hash
h = @h.to_hash
assert_equal(@h, h)
end
def test_to_s
h = @cls[ 1 => 2, "cat" => "dog", 1.5 => :fred ]
assert_equal(h.inspect, h.to_s)
$, = ":"
assert_equal(h.inspect, h.to_s)
h = @cls[]
assert_equal(h.inspect, h.to_s)
$, = nil
end
def test_update
h1 = @cls[ 1 => 2, 2 => 3, 3 => 4 ]
h2 = @cls[ 2 => 'two', 4 => 'four' ]
ha = @cls[ 1 => 2, 2 => 'two', 3 => 4, 4 => 'four' ]
hb = @cls[ 1 => 2, 2 => 3, 3 => 4, 4 => 'four' ]
assert_equal(ha, h1.update(h2))
assert_equal(ha, h1)
h1 = @cls[ 1 => 2, 2 => 3, 3 => 4 ]
h2 = @cls[ 2 => 'two', 4 => 'four' ]
assert_equal(hb, h2.update(h1))
assert_equal(hb, h2)
end
def test_value?
assert(!@cls[].value?(1))
assert(!@cls[].value?(nil))
assert(@h.value?(nil))
assert(@h.value?('one'))
assert(!@h.value?('gumby'))
end
def test_values
assert_equal([], @cls[].values)
vals = @h.values
expected = []
@h.each { |k, v| expected << v }
assert_equal([], vals - expected)
assert_equal([], expected - vals)
end
def test_security_check
h = {}
assert_raise(SecurityError) do
Thread.new do
$SAFE = 4
h[1] = 1
end.join
end
end
def test_intialize_wrong_arguments
assert_raise(ArgumentError) do
Hash.new(0) { }
end
end
def test_create
assert_equal({1=>2, 3=>4}, Hash[[[1,2],[3,4]]])
assert_raise(ArgumentError) { Hash[0, 1, 2] }
assert_equal({1=>2, 3=>4}, Hash[1,2,3,4])
o = Object.new
def o.to_hash() {1=>2} end
assert_equal({1=>2}, Hash[o], "[ruby-dev:34555]")
end
def test_rehash2
h = {1 => 2, 3 => 4}
assert_equal(h.dup, h.rehash)
assert_raise(RuntimeError) { h.each { h.rehash } }
assert_equal({}, {}.rehash)
end
def test_fetch2
assert_equal(:bar, @h.fetch(0, :foo) { :bar })
end
def test_default_proc
h = Hash.new {|h, k| h + k + "baz" }
assert_equal("foobarbaz", h.default_proc.call("foo", "bar"))
h = {}
assert_nil(h.default_proc)
end
def test_shift2
h = Hash.new {|h, k| :foo }
h[1] = 2
assert_equal([1, 2], h.shift)
assert_equal(:foo, h.shift)
assert_equal(:foo, h.shift)
h = Hash.new(:foo)
h[1] = 2
assert_equal([1, 2], h.shift)
assert_equal(:foo, h.shift)
assert_equal(:foo, h.shift)
h = {1=>2}
h.each { assert_equal([1, 2], h.shift) }
end
def test_reject_bang2
assert_equal({1=>2}, {1=>2,3=>4}.reject! {|k, v| k + v == 7 })
assert_nil({1=>2,3=>4}.reject! {|k, v| k == 5 })
assert_nil({}.reject! { })
end
def test_select
assert_equal({3=>4,5=>6}, {1=>2,3=>4,5=>6}.select {|k, v| k + v >= 7 })
end
def test_clear2
assert_equal({}, {1=>2,3=>4,5=>6}.clear)
h = {1=>2,3=>4,5=>6}
h.each { h.clear }
assert_equal({}, h)
end
def test_replace2
h1 = Hash.new { :foo }
h2 = {}
h2.replace h1
assert_equal(:foo, h2[0])
end
def test_size2
assert_equal(0, {}.size)
end
def test_equal2
assert({} != 0)
o = Object.new
def o.to_hash; {}; end
def o.==(x); true; end
assert({} == o)
def o.==(x); false; end
assert({} != o)
h1 = {1=>2}; h2 = {3=>4}
assert(h1 != h2)
h1 = {1=>2}; h2 = {1=>4}
assert(h1 != h2)
h1 = {}; h1[h1] = h1; h1.rehash
h2 = {}; h2[h2] = h2; h2.rehash
assert(h1 != h2)
end
def test_eql
assert(!({}.eql?(0)))
o = Object.new
def o.to_hash; {}; end
def o.eql?(x); true; end
assert({}.eql?(o))
def o.eql?(x); false; end
assert(!({}.eql?(o)))
end
def test_hash2
assert_kind_of(Integer, {}.hash)
end
def test_update2
h1 = {1=>2, 3=>4}
h2 = {1=>3, 5=>7}
h1.update(h2) {|k, v1, v2| k + v1 + v2 }
assert_equal({1=>6, 3=>4, 5=>7}, h1)
end
def test_merge
h1 = {1=>2, 3=>4}
h2 = {1=>3, 5=>7}
assert_equal({1=>3, 3=>4, 5=>7}, h1.merge(h2))
assert_equal({1=>6, 3=>4, 5=>7}, h1.merge(h2) {|k, v1, v2| k + v1 + v2 })
end
def test_assoc
assert_equal([3,4], {1=>2, 3=>4, 5=>6}.assoc(3))
assert_nil({1=>2, 3=>4, 5=>6}.assoc(4))
end
def test_rassoc
assert_equal([3,4], {1=>2, 3=>4, 5=>6}.rassoc(4))
assert_nil({1=>2, 3=>4, 5=>6}.rassoc(3))
end
def test_flatten
assert_equal([[1], [2]], {[1] => [2]}.flatten)
end
def test_callcc
h = {1=>2}
c = nil
f = false
h.each { callcc {|c2| c = c2 } }
unless f
f = true
c.call
end
assert_raise(RuntimeError) { h.each { h.rehash } }
h = {1=>2}
c = nil
assert_raise(RuntimeError) do
h.each { callcc {|c2| c = c2 } }
h.clear
c.call
end
end
def test_compare_by_identity
a = "foo"
assert(!{}.compare_by_identity?)
h = { a => "bar" }
assert(!h.compare_by_identity?)
h.compare_by_identity
assert(h.compare_by_identity?)
#assert_equal("bar", h[a])
assert_nil(h["foo"])
end
def test_hash_hash
assert_equal({0=>2,11=>1}.hash, {11=>1,0=>2}.hash)
end
end