virSecuritySELinuxSetTPMFileLabel(virSecurityManager *mgr,
                                  virDomainDef *def,
                                  virDomainTPMDef *tpm)
{
    int rc;
    virSecurityLabelDef *seclabel;
    char *cancel_path;
    const char *tpmdev;

    seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
    if (seclabel == NULL)
        return 0;

    switch (tpm->type) {
    case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
        tpmdev = tpm->data.passthrough.source.data.file.path;
        rc = virSecuritySELinuxSetFilecon(mgr, tpmdev, seclabel->imagelabel, false);
        if (rc < 0)
            return -1;

        if ((cancel_path = virTPMCreateCancelPath(tpmdev)) != NULL) {
            rc = virSecuritySELinuxSetFilecon(mgr,
                                              cancel_path,
                                              seclabel->imagelabel, false);
            VIR_FREE(cancel_path);
            if (rc < 0) {
                virSecuritySELinuxRestoreTPMFileLabelInt(mgr, def, tpm);
                return -1;
            }
        } else {
            return -1;
        }
        break;
    case VIR_DOMAIN_TPM_TYPE_EMULATOR:
        tpmdev = tpm->data.emulator.source.data.nix.path;
        rc = virSecuritySELinuxSetFilecon(mgr, tpmdev, seclabel->imagelabel, false);
        if (rc < 0)
            return -1;
        break;
    case VIR_DOMAIN_TPM_TYPE_LAST:
        break;
    }

    return 0;
}


virSecuritySELinuxSetUSBLabel(virUSBDevice *dev G_GNUC_UNUSED,
                              const char *file, void *opaque)
{
    return virSecuritySELinuxSetHostdevLabelHelper(file, true, opaque);
}

security: fix SELinux label generation logic

A process can access a file if the set of MCS categories
for the file is equal-to *or* a subset-of, the set of
MCS categories for the process.

If there are two VMs:

  a) svirt_t:s0:c117
  b) svirt_t:s0:c117,c720

Then VM (b) is able to access files labelled for VM (a).

IOW, we must discard case where the categories are equal
because that is a subset of many other valid category pairs.

Fixes: https://gitlab.com/libvirt/libvirt/-/issues/153
CVE-2021-3631
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>

struct _virSecuritySELinuxData {
    char *domain_context;
    char *alt_domain_context;
    char *file_context;
    char *content_context;
    GHashTable *mcs;
    bool skipAllLabel;
    struct selabel_handle *label_handle;
}

struct _virSecuritySELinuxContextItem {
    char *path;
    char *tcon;
    bool remember; /* Whether owner remembering should be done for @path/@src */
    bool restore; /* Whether current operation is 'set' or 'restore' */
}

static int virSecuritySELinuxSetFilecon(virSecurityManager *mgr,
                                        const char *path,
                                        const char *tcon,
                                        bool remember);

static int virSecuritySELinuxRestoreFileLabel(virSecurityManager *mgr,
                                              const char *path,
                                              bool recall);

struct virSecuritySELinuxMoveImageMetadataData {
    virSecurityManager *mgr;
    const char *src;
    const char *dst;
}


virSecuritySELinuxGetSecurityMountOptions(virSecurityManager *mgr,
                                          virDomainDef *def)
{
    char *opts = NULL;
    virSecurityLabelDef *secdef;

    if ((secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME))) {
        if (!secdef->imagelabel)
            secdef->imagelabel = virSecuritySELinuxGenImageLabel(mgr, def);

        if (secdef->imagelabel) {
            opts = g_strdup_printf(
                                   ",context=\"%s\"",
                                   (const char*) secdef->imagelabel);
        }
    }

    if (!opts)
        opts = g_strdup("");

    VIR_DEBUG("imageLabel=%s opts=%s",
              secdef ? secdef->imagelabel : "(null)", opts);
    return opts;
}

struct _virSecuritySELinuxData {
    char *domain_context;
    char *alt_domain_context;
    char *file_context;
    char *content_context;
    GHashTable *mcs;
    bool skipAllLabel;
    struct selabel_handle *label_handle;
}

struct _virSecuritySELinuxCallbackData {
    virSecurityManager *mgr;
    virDomainDef *def;
}

struct _virSecuritySELinuxContextItem {
    char *path;
    char *tcon;
    bool remember; /* Whether owner remembering should be done for @path/@src */
    bool restore; /* Whether current operation is 'set' or 'restore' */
}

struct _virSecuritySELinuxContextList {
    virSecurityManager *manager;
    virSecuritySELinuxContextItem **items;
    size_t nItems;
    bool lock;
}

#define SECURITY_SELINUX_NAME "selinux"

static int
virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManager *mgr,
                                         virDomainDef *def,
                                         virDomainTPMDef *tpm);

static int virSecuritySELinuxSetFilecon(virSecurityManager *mgr,
                                        const char *path,
                                        const char *tcon,
                                        bool remember);

static int virSecuritySELinuxRestoreFileLabel(virSecurityManager *mgr,
                                              const char *path,
                                              bool recall);

struct virSecuritySELinuxMoveImageMetadataData {
    virSecurityManager *mgr;
    const char *src;
    const char *dst;
}

struct _virSecuritySELinuxChardevCallbackData {
    virSecurityManager *mgr;
    bool chardevStdioLogd;
}


virSecuritySELinuxLXCInitialize(virSecurityManager *mgr)
{
    g_autoptr(virConf) selinux_conf = NULL;
    virSecuritySELinuxData *data = virSecurityManagerGetPrivateData(mgr);

    data->skipAllLabel = true;

    data->label_handle = selabel_open(SELABEL_CTX_FILE, NULL, 0);
    if (!data->label_handle) {
        virReportSystemError(errno, "%s",
                             _("cannot open SELinux label_handle"));
        return -1;
    }

    if (!(selinux_conf = virConfReadFile(selinux_lxc_contexts_path(), 0)))
        goto error;

    if (virConfGetValueString(selinux_conf, "process", &data->domain_context) < 0)
        goto error;

    if (!data->domain_context) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("missing 'process' value in selinux lxc contexts file '%s'"),
                       selinux_lxc_contexts_path());
        goto error;
    }

    if (virConfGetValueString(selinux_conf, "file", &data->file_context) < 0)
        goto error;

    if (!data->file_context) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("missing 'file' value in selinux lxc contexts file '%s'"),
                       selinux_lxc_contexts_path());
        goto error;
    }

    if (virConfGetValueString(selinux_conf, "content", &data->content_context) < 0)
        goto error;

    if (!data->content_context) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("missing 'content' value in selinux lxc contexts file '%s'"),
                       selinux_lxc_contexts_path());
        goto error;
    }

    if (!(data->mcs = virHashNew(NULL)))
        goto error;

    return 0;

 error:
    selabel_close(data->label_handle);
    data->label_handle = NULL;
    VIR_FREE(data->domain_context);
    VIR_FREE(data->file_context);
    VIR_FREE(data->content_context);
    virHashFree(data->mcs);
    return -1;
}

typedef struct _virSecuritySELinuxData virSecuritySELinuxData;

struct _virSecuritySELinuxData {
    char *domain_context;
    char *alt_domain_context;
    char *file_context;
    char *content_context;
    GHashTable *mcs;
    bool skipAllLabel;
    struct selabel_handle *label_handle;
}

struct _virSecuritySELinuxCallbackData {
    virSecurityManager *mgr;
    virDomainDef *def;
}

struct _virSecuritySELinuxContextList {
    virSecurityManager *manager;
    virSecuritySELinuxContextItem **items;
    size_t nItems;
    bool lock;
}

#define SECURITY_SELINUX_NAME "selinux"

static int
virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManager *mgr,
                                         virDomainDef *def,
                                         virDomainTPMDef *tpm);

static int virSecuritySELinuxSetFilecon(virSecurityManager *mgr,
                                        const char *path,
                                        const char *tcon,
                                        bool remember);

static int virSecuritySELinuxRestoreFileLabel(virSecurityManager *mgr,
                                              const char *path,
                                              bool recall);

struct virSecuritySELinuxMoveImageMetadataData {
    virSecurityManager *mgr;
    const char *src;
    const char *dst;
}

struct _virSecuritySELinuxChardevCallbackData {
    virSecurityManager *mgr;
    bool chardevStdioLogd;
}


virSecuritySELinuxSetImageFDLabel(virSecurityManager *mgr G_GNUC_UNUSED,
                                  virDomainDef *def,
                                  int fd)
{
    virSecurityLabelDef *secdef;

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
    if (!secdef || !secdef->imagelabel)
        return 0;

    return virSecuritySELinuxFSetFilecon(fd, secdef->imagelabel);
}

struct _virSecuritySELinuxCallbackData {
    virSecurityManager *mgr;
    virDomainDef *def;
}

struct _virSecuritySELinuxContextList {
    virSecurityManager *manager;
    virSecuritySELinuxContextItem **items;
    size_t nItems;
    bool lock;
}

#define SECURITY_SELINUX_NAME "selinux"

static int
virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManager *mgr,
                                         virDomainDef *def,
                                         virDomainTPMDef *tpm);

static int virSecuritySELinuxSetFilecon(virSecurityManager *mgr,
                                        const char *path,
                                        const char *tcon,
                                        bool remember);

static int virSecuritySELinuxRestoreFileLabel(virSecurityManager *mgr,
                                              const char *path,
                                              bool recall);

struct virSecuritySELinuxMoveImageMetadataData {
    virSecurityManager *mgr;
    const char *src;
    const char *dst;
}

struct _virSecuritySELinuxChardevCallbackData {
    virSecurityManager *mgr;
    bool chardevStdioLogd;
}


virSecuritySELinuxDomainRestorePathLabel(virSecurityManager *mgr,
                                         virDomainDef *def,
                                         const char *path)
{
    virSecurityLabelDef *secdef;

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
    if (!secdef || !secdef->relabel)
        return 0;

    return virSecuritySELinuxRestoreFileLabel(mgr, path, true);
}


virSecuritySELinuxDriverOpen(virSecurityManager *mgr)
{
    return virSecuritySELinuxInitialize(mgr);
}

struct _virSecuritySELinuxCallbackData {
    virSecurityManager *mgr;
    virDomainDef *def;
}

struct _virSecuritySELinuxContextList {
    virSecurityManager *manager;
    virSecuritySELinuxContextItem **items;
    size_t nItems;
    bool lock;
}

static int
virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManager *mgr,
                                         virDomainDef *def,
                                         virDomainTPMDef *tpm);

static int virSecuritySELinuxSetFilecon(virSecurityManager *mgr,
                                        const char *path,
                                        const char *tcon,
                                        bool remember);

static int virSecuritySELinuxRestoreFileLabel(virSecurityManager *mgr,
                                              const char *path,
                                              bool recall);

struct virSecuritySELinuxMoveImageMetadataData {
    virSecurityManager *mgr;
    const char *src;
    const char *dst;
}

struct _virSecuritySELinuxChardevCallbackData {
    virSecurityManager *mgr;
    bool chardevStdioLogd;
}


virSecuritySELinuxRestoreImageLabelInt(virSecurityManager *mgr,
                                       virDomainDef *def,
                                       virStorageSource *src,
                                       bool migrated)
{
    if (virSecuritySELinuxRestoreImageLabelSingle(mgr, def, src, migrated) < 0)
        return -1;

    return 0;
}

struct _virSecuritySELinuxData {
    char *domain_context;
    char *alt_domain_context;
    char *file_context;
    char *content_context;
    GHashTable *mcs;
    bool skipAllLabel;
    struct selabel_handle *label_handle;
}

struct _virSecuritySELinuxCallbackData {
    virSecurityManager *mgr;
    virDomainDef *def;
}

struct _virSecuritySELinuxContextItem {
    char *path;
    char *tcon;
    bool remember; /* Whether owner remembering should be done for @path/@src */
    bool restore; /* Whether current operation is 'set' or 'restore' */
}

struct _virSecuritySELinuxContextList {
    virSecurityManager *manager;
    virSecuritySELinuxContextItem **items;
    size_t nItems;
    bool lock;
}

static int
virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManager *mgr,
                                         virDomainDef *def,
                                         virDomainTPMDef *tpm);

static int virSecuritySELinuxSetFilecon(virSecurityManager *mgr,
                                        const char *path,
                                        const char *tcon,
                                        bool remember);

static int virSecuritySELinuxRestoreFileLabel(virSecurityManager *mgr,
                                              const char *path,
                                              bool recall);

struct virSecuritySELinuxMoveImageMetadataData {
    virSecurityManager *mgr;
    const char *src;
    const char *dst;
}

struct _virSecuritySELinuxChardevCallbackData {
    virSecurityManager *mgr;
    bool chardevStdioLogd;
}


virSecuritySELinuxMCSFind(virSecurityManager *mgr,
                          const char *sens,
                          int catMin,
                          int catMax)
{
    virSecuritySELinuxData *data = virSecurityManagerGetPrivateData(mgr);
    int catRange;
    char *mcs = NULL;

    /* +1 since virRandomInt range is exclusive of the upper bound */
    catRange = (catMax - catMin) + 1;

    if (catRange < 8) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Category range c%d-c%d too small"),
                       catMin, catMax);
        return NULL;
    }

    VIR_DEBUG("Using sensitivity level '%s' cat min %d max %d range %d",
              sens, catMin, catMax, catRange);

    for (;;) {
        int c1 = virRandomInt(catRange);
        int c2 = virRandomInt(catRange);

        VIR_DEBUG("Try cat %s:c%d,c%d", sens, c1 + catMin, c2 + catMin);

        if (c1 == c2) {
            /*
             * A process can access a file if the set of MCS categories
             * for the file is equal-to *or* a subset-of, the set of
             * MCS categories for the process.
             *
             * IOW, we must discard case where the categories are equal
             * because that is a subset of other category pairs.
             */
            continue;
        } else {
            if (c1 > c2) {
                int t = c1;
                c1 = c2;
                c2 = t;
            }
            mcs = g_strdup_printf("%s:c%d,c%d", sens, catMin + c1, catMin + c2);
        }

        if (virHashLookup(data->mcs, mcs) == NULL)
            break;

        VIR_FREE(mcs);
    }

    return mcs;
}

typedef struct _virSecuritySELinuxData virSecuritySELinuxData;

struct _virSecuritySELinuxData {
    char *domain_context;
    char *alt_domain_context;
    char *file_context;
    char *content_context;
    GHashTable *mcs;
    bool skipAllLabel;
    struct selabel_handle *label_handle;
}

struct _virSecuritySELinuxCallbackData {
    virSecurityManager *mgr;
    virDomainDef *def;
}

struct _virSecuritySELinuxContextItem {
    char *path;
    char *tcon;
    bool remember; /* Whether owner remembering should be done for @path/@src */
    bool restore; /* Whether current operation is 'set' or 'restore' */
}

struct _virSecuritySELinuxContextList {
    virSecurityManager *manager;
    virSecuritySELinuxContextItem **items;
    size_t nItems;
    bool lock;
}

static int
virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManager *mgr,
                                         virDomainDef *def,
                                         virDomainTPMDef *tpm);

static int virSecuritySELinuxSetFilecon(virSecurityManager *mgr,
                                        const char *path,
                                        const char *tcon,
                                        bool remember);

static int virSecuritySELinuxRestoreFileLabel(virSecurityManager *mgr,
                                              const char *path,
                                              bool recall);

struct virSecuritySELinuxMoveImageMetadataData {
    virSecurityManager *mgr;
    const char *src;
    const char *dst;
}

struct _virSecuritySELinuxChardevCallbackData {
    virSecurityManager *mgr;
    bool chardevStdioLogd;
}


virSecuritySELinuxRememberLabel(const char *path,
                                const char *con)
{
    return virSecuritySetRememberedLabel(SECURITY_SELINUX_NAME,
                                         path, con);
}

struct _virSecuritySELinuxData {
    char *domain_context;
    char *alt_domain_context;
    char *file_context;
    char *content_context;
    GHashTable *mcs;
    bool skipAllLabel;
    struct selabel_handle *label_handle;
}

struct _virSecuritySELinuxContextItem {
    char *path;
    char *tcon;
    bool remember; /* Whether owner remembering should be done for @path/@src */
    bool restore; /* Whether current operation is 'set' or 'restore' */
}

#define SECURITY_SELINUX_NAME "selinux"

static int virSecuritySELinuxSetFilecon(virSecurityManager *mgr,
                                        const char *path,
                                        const char *tcon,
                                        bool remember);

static int virSecuritySELinuxRestoreFileLabel(virSecurityManager *mgr,
                                              const char *path,
                                              bool recall);

struct virSecuritySELinuxMoveImageMetadataData {
    virSecurityManager *mgr;
    const char *src;
    const char *dst;
}


virSecuritySELinuxSetHostdevLabelHelper(const char *file,
                                        bool remember,
                                        void *opaque)
{
    virSecurityLabelDef *secdef;
    virSecuritySELinuxCallbackData *data = opaque;
    virSecurityManager *mgr = data->mgr;
    virDomainDef *def = data->def;

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
    if (secdef == NULL)
        return 0;
    return virSecuritySELinuxSetFilecon(mgr, file, secdef->imagelabel, remember);
}

struct _virSecuritySELinuxData {
    char *domain_context;
    char *alt_domain_context;
    char *file_context;
    char *content_context;
    GHashTable *mcs;
    bool skipAllLabel;
    struct selabel_handle *label_handle;
}

typedef struct _virSecuritySELinuxCallbackData virSecuritySELinuxCallbackData;

struct _virSecuritySELinuxCallbackData {
    virSecurityManager *mgr;
    virDomainDef *def;
}

struct _virSecuritySELinuxContextItem {
    char *path;
    char *tcon;
    bool remember; /* Whether owner remembering should be done for @path/@src */
    bool restore; /* Whether current operation is 'set' or 'restore' */
}

struct _virSecuritySELinuxContextList {
    virSecurityManager *manager;
    virSecuritySELinuxContextItem **items;
    size_t nItems;
    bool lock;
}

#define SECURITY_SELINUX_NAME "selinux"

static int
virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManager *mgr,
                                         virDomainDef *def,
                                         virDomainTPMDef *tpm);

static int virSecuritySELinuxSetFilecon(virSecurityManager *mgr,
                                        const char *path,
                                        const char *tcon,
                                        bool remember);

static int virSecuritySELinuxRestoreFileLabel(virSecurityManager *mgr,
                                              const char *path,
                                              bool recall);

struct virSecuritySELinuxMoveImageMetadataData {
    virSecurityManager *mgr;
    const char *src;
    const char *dst;
}

struct _virSecuritySELinuxChardevCallbackData {
    virSecurityManager *mgr;
    bool chardevStdioLogd;
}


virSecuritySELinuxContextListAppend(virSecuritySELinuxContextList *list,
                                    const char *path,
                                    const char *tcon,
                                    bool remember,
                                    bool restore)
{
    int ret = -1;
    virSecuritySELinuxContextItem *item = NULL;

    item = g_new0(virSecuritySELinuxContextItem, 1);

    item->path = g_strdup(path);
    item->tcon = g_strdup(tcon);

    item->remember = remember;
    item->restore = restore;

    if (VIR_APPEND_ELEMENT(list->items, list->nItems, item) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virSecuritySELinuxContextItemFree(item);
    return ret;
}

struct _virSecuritySELinuxData {
    char *domain_context;
    char *alt_domain_context;
    char *file_context;
    char *content_context;
    GHashTable *mcs;
    bool skipAllLabel;
    struct selabel_handle *label_handle;
}

typedef struct _virSecuritySELinuxContextItem virSecuritySELinuxContextItem;

struct _virSecuritySELinuxContextItem {
    char *path;
    char *tcon;
    bool remember; /* Whether owner remembering should be done for @path/@src */
    bool restore; /* Whether current operation is 'set' or 'restore' */
}

typedef struct _virSecuritySELinuxContextList virSecuritySELinuxContextList;

struct _virSecuritySELinuxContextList {
    virSecurityManager *manager;
    virSecuritySELinuxContextItem **items;
    size_t nItems;
    bool lock;
}

static int
virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManager *mgr,
                                         virDomainDef *def,
                                         virDomainTPMDef *tpm);

static int virSecuritySELinuxSetFilecon(virSecurityManager *mgr,
                                        const char *path,
                                        const char *tcon,
                                        bool remember);

static int virSecuritySELinuxRestoreFileLabel(virSecurityManager *mgr,
                                              const char *path,
                                              bool recall);

struct virSecuritySELinuxMoveImageMetadataData {
    virSecurityManager *mgr;
    const char *src;
    const char *dst;
}

struct _virSecuritySELinuxChardevCallbackData {
    virSecurityManager *mgr;
    bool chardevStdioLogd;
}


virSecuritySELinuxSetSecuritySmartcardCallback(virDomainDef *def,
                                               virDomainSmartcardDef *dev,
                                               void *opaque)
{
    const char *database;
    virSecurityManager *mgr = opaque;
    virSecuritySELinuxData *data = virSecurityManagerGetPrivateData(mgr);

    switch (dev->type) {
    case VIR_DOMAIN_SMARTCARD_TYPE_HOST:
        break;

    case VIR_DOMAIN_SMARTCARD_TYPE_HOST_CERTIFICATES:
        database = dev->data.cert.database;
        if (!database)
            database = VIR_DOMAIN_SMARTCARD_DEFAULT_DATABASE;
        return virSecuritySELinuxSetFilecon(mgr, database, data->content_context, true);

    case VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH:
        return virSecuritySELinuxSetChardevLabel(mgr, def,
                                                 dev->data.passthru, false);

    default:
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("unknown smartcard type %d"),
                       dev->type);
        return -1;
    }

    return 0;
}


virSecuritySELinuxRestoreHostdevLabel(virSecurityManager *mgr,
                                      virDomainDef *def,
                                      virDomainHostdevDef *dev,
                                      const char *vroot)

{
    virSecurityLabelDef *secdef;

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
    if (!secdef || !secdef->relabel)
        return 0;

    switch (dev->mode) {
    case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS:
        return virSecuritySELinuxRestoreHostdevSubsysLabel(mgr, dev, vroot);

    case VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES:
        return virSecuritySELinuxRestoreHostdevCapsLabel(mgr, dev, vroot);

    default:
        return 0;
    }
}


virSecuritySELinuxSetProcessLabel(virSecurityManager *mgr G_GNUC_UNUSED,
                                  virDomainDef *def)
{
    /* TODO: verify DOI */
    virSecurityLabelDef *secdef;

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
    if (!secdef || !secdef->label)
        return 0;

    VIR_DEBUG("label=%s", secdef->label);
    if (STRNEQ(SECURITY_SELINUX_NAME, secdef->model)) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("security label driver mismatch: "
                         "'%s' model configured for domain, but "
                         "hypervisor driver is '%s'."),
                       secdef->model, SECURITY_SELINUX_NAME);
        if (security_getenforce() == 1)
            return -1;
    }

    if (setexeccon_raw(secdef->label) == -1) {
        virReportSystemError(errno,
                             _("unable to set security context '%s'"),
                             secdef->label);
        if (security_getenforce() == 1)
            return -1;
    }

    return 0;
}

struct _virSecuritySELinuxCallbackData {
    virSecurityManager *mgr;
    virDomainDef *def;
}

struct _virSecuritySELinuxContextItem {
    char *path;
    char *tcon;
    bool remember; /* Whether owner remembering should be done for @path/@src */
    bool restore; /* Whether current operation is 'set' or 'restore' */
}

struct _virSecuritySELinuxContextList {
    virSecurityManager *manager;
    virSecuritySELinuxContextItem **items;
    size_t nItems;
    bool lock;
}

#define SECURITY_SELINUX_NAME "selinux"

static int
virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManager *mgr,
                                         virDomainDef *def,
                                         virDomainTPMDef *tpm);

static int virSecuritySELinuxSetFilecon(virSecurityManager *mgr,
                                        const char *path,
                                        const char *tcon,
                                        bool remember);

static int virSecuritySELinuxRestoreFileLabel(virSecurityManager *mgr,
                                              const char *path,
                                              bool recall);

struct virSecuritySELinuxMoveImageMetadataData {
    virSecurityManager *mgr;
    const char *src;
    const char *dst;
}

struct _virSecuritySELinuxChardevCallbackData {
    virSecurityManager *mgr;
    bool chardevStdioLogd;
}


virSecuritySELinuxRestoreImageLabelSingle(virSecurityManager *mgr,
                                          virDomainDef *def,
                                          virStorageSource *src,
                                          bool migrated)
{
    virSecurityLabelDef *seclabel;
    virSecurityDeviceLabelDef *disk_seclabel;
    g_autofree char *vfioGroupDev = NULL;
    const char *path = src->path;

    seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
    if (seclabel == NULL)
        return 0;

    disk_seclabel = virStorageSourceGetSecurityLabelDef(src,
                                                        SECURITY_SELINUX_NAME);
    if (!seclabel->relabel || (disk_seclabel && !disk_seclabel->relabel))
        return 0;

    /* If labelskip is true and there are no backing files, then we
     * know it is safe to skip the restore.  FIXME - backing files should
     * be tracked in domain XML, at which point labelskip should be a
     * per-file attribute instead of a disk attribute. */
    if (disk_seclabel && disk_seclabel->labelskip &&
        !virStorageSourceHasBacking(src))
        return 0;

    /* Don't restore labels on readonly/shared disks, because other VMs may
     * still be accessing these. Alternatively we could iterate over all
     * running domains and try to figure out if it is in use, but this would
     * not work for clustered filesystems, since we can't see running VMs using
     * the file on other nodes. Safest bet is thus to skip the restore step. */
    if (src->readonly || src->shared)
        return 0;

    /* If we have a shared FS and are doing migration, we must not change
     * ownership, because that kills access on the destination host which is
     * sub-optimal for the guest VM's I/O attempts :-) */
    if (migrated) {
        int rc = 1;

        if (virStorageSourceIsLocalStorage(src)) {
            if (!src->path)
                return 0;

            if ((rc = virFileIsSharedFS(src->path)) < 0)
                return -1;
        }

        if (rc == 1) {
            VIR_DEBUG("Skipping image label restore on %s because FS is shared",
                      src->path);
            return 0;
        }
    }

    /* This is not very clean. But so far we don't have NVMe
     * storage pool backend so that its chownCallback would be
     * called. And this place looks least offensive. */
    if (src->type == VIR_STORAGE_TYPE_NVME) {
        const virStorageSourceNVMeDef *nvme = src->nvme;

        if (!(vfioGroupDev = virPCIDeviceAddressGetIOMMUGroupDev(&nvme->pciAddr)))
            return -1;

        /* Ideally, we would check if there is not another PCI
         * device within domain def that is in the same IOMMU
         * group. But we're not doing that for hostdevs yet. */
        path = vfioGroupDev;
    }

    return virSecuritySELinuxRestoreFileLabel(mgr, path, true);
}


virSecuritySELinuxSetSCSILabel(virSCSIDevice *dev,
                               const char *file, void *opaque)
{
    virSecurityLabelDef *secdef;
    virSecuritySELinuxCallbackData *ptr = opaque;
    virSecurityManager *mgr = ptr->mgr;
    virSecuritySELinuxData *data = virSecurityManagerGetPrivateData(mgr);

    secdef = virDomainDefGetSecurityLabelDef(ptr->def, SECURITY_SELINUX_NAME);
    if (secdef == NULL)
        return 0;

    if (virSCSIDeviceGetShareable(dev))
        return virSecuritySELinuxSetFilecon(mgr, file,
                                            data->file_context, true);
    else if (virSCSIDeviceGetReadonly(dev))
        return virSecuritySELinuxSetFilecon(mgr, file,
                                            data->content_context, true);
    else
        return virSecuritySELinuxSetFilecon(mgr, file,
                                            secdef->imagelabel, true);
}

typedef struct _virSecuritySELinuxData virSecuritySELinuxData;

struct _virSecuritySELinuxData {
    char *domain_context;
    char *alt_domain_context;
    char *file_context;
    char *content_context;
    GHashTable *mcs;
    bool skipAllLabel;
    struct selabel_handle *label_handle;
}

typedef struct _virSecuritySELinuxCallbackData virSecuritySELinuxCallbackData;

struct _virSecuritySELinuxCallbackData {
    virSecurityManager *mgr;
    virDomainDef *def;
}

struct _virSecuritySELinuxContextItem {
    char *path;
    char *tcon;
    bool remember; /* Whether owner remembering should be done for @path/@src */
    bool restore; /* Whether current operation is 'set' or 'restore' */
}

struct _virSecuritySELinuxContextList {
    virSecurityManager *manager;
    virSecuritySELinuxContextItem **items;
    size_t nItems;
    bool lock;
}

#define SECURITY_SELINUX_NAME "selinux"

static int
virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManager *mgr,
                                         virDomainDef *def,
                                         virDomainTPMDef *tpm);

static int virSecuritySELinuxSetFilecon(virSecurityManager *mgr,
                                        const char *path,
                                        const char *tcon,
                                        bool remember);

static int virSecuritySELinuxRestoreFileLabel(virSecurityManager *mgr,
                                              const char *path,
                                              bool recall);

struct virSecuritySELinuxMoveImageMetadataData {
    virSecurityManager *mgr;
    const char *src;
    const char *dst;
}

struct _virSecuritySELinuxChardevCallbackData {
    virSecurityManager *mgr;
    bool chardevStdioLogd;
}


virSecuritySELinuxSetChildProcessLabel(virSecurityManager *mgr G_GNUC_UNUSED,
                                       virDomainDef *def,
                                       virCommand *cmd)
{
    /* TODO: verify DOI */
    virSecurityLabelDef *secdef;

    secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
    if (!secdef || !secdef->label)
        return 0;

    VIR_DEBUG("label=%s", secdef->label);
    if (STRNEQ(SECURITY_SELINUX_NAME, secdef->model)) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("security label driver mismatch: "
                         "'%s' model configured for domain, but "
                         "hypervisor driver is '%s'."),
                       secdef->model, SECURITY_SELINUX_NAME);
        if (security_getenforce() == 1)
            return -1;
    }

    /* save in cmd to be set after fork/before child process is exec'ed */
    virCommandSetSELinuxLabel(cmd, secdef->label);
    return 0;
}


virSecuritySELinuxRestoreMemoryLabel(virSecurityManager *mgr,
                                     virDomainDef *def,
                                     virDomainMemoryDef *mem)
{
    int ret = -1;
    virSecurityLabelDef *seclabel;

    switch (mem->model) {
    case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
    case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
        seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
        if (!seclabel || !seclabel->relabel)
            return 0;

        ret = virSecuritySELinuxRestoreFileLabel(mgr, mem->nvdimmPath, true);
        break;

    case VIR_DOMAIN_MEMORY_MODEL_DIMM:
    case VIR_DOMAIN_MEMORY_MODEL_NONE:
    case VIR_DOMAIN_MEMORY_MODEL_LAST:
        ret = 0;
        break;
    }

    return ret;
}


virSecuritySELinuxSetSysinfoLabel(virSecurityManager *mgr,
                                  virSysinfoDef *def,
                                  virSecuritySELinuxData *data)
{
    size_t i;

    for (i = 0; i < def->nfw_cfgs; i++) {
        virSysinfoFWCfgDef *f = &def->fw_cfgs[i];

        if (f->file &&
            virSecuritySELinuxSetFilecon(mgr, f->file,
                                         data->content_context, true) < 0)
            return -1;
    }

    return 0;
}


virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManager *mgr,
                                         virDomainDef *def,
                                         virDomainTPMDef *tpm)
{
    int rc = 0;
    virSecurityLabelDef *seclabel;
    char *cancel_path;
    const char *tpmdev;

    seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
    if (seclabel == NULL)
        return 0;

    switch (tpm->type) {
    case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
        tpmdev = tpm->data.passthrough.source.data.file.path;
        rc = virSecuritySELinuxRestoreFileLabel(mgr, tpmdev, false);

        if ((cancel_path = virTPMCreateCancelPath(tpmdev)) != NULL) {
            if (virSecuritySELinuxRestoreFileLabel(mgr, cancel_path, false) < 0)
                rc = -1;
            VIR_FREE(cancel_path);
        }
        break;
    case VIR_DOMAIN_TPM_TYPE_EMULATOR:
        /* swtpm will have removed the Unix socket upon termination */
    case VIR_DOMAIN_TPM_TYPE_LAST:
        break;
    }

    return rc;
}


virSecuritySELinuxContextAddRange(char *src,
                                  char *dst)
{
    char *str = NULL;
    char *ret = NULL;
    context_t srccon = NULL;
    context_t dstcon = NULL;

    if (!src || !dst)
        return ret;

    if (!(srccon = context_new(src)) || !(dstcon = context_new(dst))) {
        virReportSystemError(errno, "%s",
                             _("unable to allocate security context"));
        goto cleanup;
    }

    if (context_range_set(dstcon, context_range_get(srccon)) == -1) {
        virReportSystemError(errno,
                             _("unable to set security context range '%s'"), dst);
        goto cleanup;
    }

    if (!(str = context_str(dstcon))) {
        virReportSystemError(errno, "%s",
                             _("Unable to format SELinux context"));
        goto cleanup;
    }

    ret = g_strdup(str);

 cleanup:
    if (srccon) context_free(srccon);
    if (dstcon) context_free(dstcon);
    return ret;
}


UnicodeString::extract(int32_t start, int32_t len,
                       char *target, uint32_t dstSize) const {
  // if the arguments are illegal, then do nothing
  if(/*dstSize < 0 || */(dstSize > 0 && target == 0)) {
    return 0;
  }
  return toUTF8(start, len, target, dstSize <= 0x7fffffff ? (int32_t)dstSize : 0x7fffffff);
}

ICU-20958 Prevent SEGV_MAPERR in append

See #971

static u_atomic_int32_t beyondCount(0);

static u_atomic_int32_t finalLengthCounts[0x400];


UnicodeStringTest::doTestAppendable(UnicodeString &dest, Appendable &app) {
    static const UChar cde[3]={ 0x63, 0x64, 0x65 };
    static const UChar fg[3]={ 0x66, 0x67, 0 };
    if(!app.reserveAppendCapacity(12)) {
        errln("Appendable.reserve(12) failed");
    }
    app.appendCodeUnit(0x61);
    app.appendCodePoint(0x62);
    app.appendCodePoint(0x50000);
    app.appendString(cde, 3);
    app.appendString(fg, -1);
    UChar scratch[3];
    int32_t capacity=-1;
    UChar *buffer=app.getAppendBuffer(3, 3, scratch, 3, &capacity);
    if(capacity<3) {
        errln("Appendable.getAppendBuffer(min=3) returned capacity=%d<3", (int)capacity);
        return;
    }
    static const UChar hij[3]={ 0x68, 0x69, 0x6a };
    u_memcpy(buffer, hij, 3);
    app.appendString(buffer, 3);
    if(dest!=UNICODE_STRING_SIMPLE("ab\\U00050000cdefghij").unescape()) {
        errln("Appendable.append(...) failed");
    }
    buffer=app.getAppendBuffer(0, 3, scratch, 3, &capacity);
    if(buffer!=NULL || capacity!=0) {
        errln("Appendable.getAppendBuffer(min=0) failed");
    }
    capacity=1;
    buffer=app.getAppendBuffer(3, 3, scratch, 2, &capacity);
    if(buffer!=NULL || capacity!=0) {
        errln("Appendable.getAppendBuffer(scratch<min) failed");
    }
}

static const char *const
testEnumStrings[]={
    "a",
    "b",
    "c",
    "this is a long string which helps us test some buffer limits",
    "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
};

class TestEnumeration : public StringEnumeration {
public:
    TestEnumeration() : i(0) {}

    virtual int32_t count(UErrorCode& /*status*/) const {
        return UPRV_LENGTHOF(testEnumStrings);
    }

    virtual const UnicodeString *snext(UErrorCode &status) {
        if(U_SUCCESS(status) && i<UPRV_LENGTHOF(testEnumStrings)) {
            unistr=UnicodeString(testEnumStrings[i++], "");
            return &unistr;
        }

        return NULL;
    }

    virtual void reset(UErrorCode& /*status*/) {
        i=0;
    }

    static inline UClassID getStaticClassID() {
        return (UClassID)&fgClassID;
    }
    virtual UClassID getDynamicClassID() const {
        return getStaticClassID();
    }

private:
    static const char fgClassID;

    int32_t i;
}

const char TestEnumeration::fgClassID=0;

class TestCheckedArrayByteSink : public CheckedArrayByteSink {
public:
    TestCheckedArrayByteSink(char* outbuf, int32_t capacity)
            : CheckedArrayByteSink(outbuf, capacity), calledFlush(FALSE) {}
    virtual void Flush() { calledFlush = TRUE; }
    UBool calledFlush;
}

class SimpleAppendable : public Appendable {
public:
    explicit SimpleAppendable(UnicodeString &dest) : str(dest) {}
    virtual UBool appendCodeUnit(UChar c) { str.append(c); return TRUE; }
    SimpleAppendable &reset() { str.remove(); return *this; }
private:
    UnicodeString &str;
}


Replaceable::clone() const {
  return NULL;
}


print(const UChar *s,
      int32_t len,
      const char *name)
{
  UChar c;
  cout << name << ":|";
  for(int i = 0; i < len; ++i) {
    c = s[i];
    if(c>= 0x007E || c < 0x0020)
      cout << "[0x" << hex << s[i] << "]";
    else
      cout << (char) s[i];
  }
  cout << '|' << endl;
}


UnicodeString::UnicodeString(const char *src, int32_t length, EInvariant) {
  fUnion.fFields.fLengthAndFlags = kShortString;
  if(src==NULL) {
    // treat as an empty string
  } else {
    if(length<0) {
      length=(int32_t)uprv_strlen(src);
    }
    if(cloneArrayIfNeeded(length, length, FALSE)) {
      u_charsToUChars(src, getArrayStart(), length);
      setLength(length);
    } else {
      setToBogus();
    }
  }
}


UnicodeStringTest::TestMoveSwap() {
    static const UChar abc[3] = { 0x61, 0x62, 0x63 };  // "abc"
    UnicodeString s1(FALSE, abc, UPRV_LENGTHOF(abc));  // read-only alias
    UnicodeString s2(100, 0x7a, 100);  // 100 * 'z' should be on the heap
    UnicodeString s3("defg", 4, US_INV);  // in stack buffer
    const UChar *p = s2.getBuffer();
    s1.swap(s2);
    if(s1.getBuffer() != p || s1.length() != 100 || s2.getBuffer() != abc || s2.length() != 3) {
        errln("UnicodeString.swap() did not swap");
    }
    swap(s2, s3);
    if(s2 != UNICODE_STRING_SIMPLE("defg") || s3.getBuffer() != abc || s3.length() != 3) {
        errln("swap(UnicodeString) did not swap back");
    }
    UnicodeString s4;
    s4 = std::move(s1);
    if(s4.getBuffer() != p || s4.length() != 100 || !s1.isBogus()) {
        errln("UnicodeString = std::move(heap) did not move");
    }
    UnicodeString s5;
    s5 = std::move(s2);
    if(s5 != UNICODE_STRING_SIMPLE("defg")) {
        errln("UnicodeString = std::move(stack) did not move");
    }
    UnicodeString s6;
    s6 = std::move(s3);
    if(s6.getBuffer() != abc || s6.length() != 3) {
        errln("UnicodeString = std::move(alias) did not move");
    }
    infoln("TestMoveSwap() with rvalue references");
    s1 = static_cast<UnicodeString &&>(s6);
    if(s1.getBuffer() != abc || s1.length() != 3) {
        errln("UnicodeString move assignment operator did not move");
    }
    UnicodeString s7(static_cast<UnicodeString &&>(s4));
    if(s7.getBuffer() != p || s7.length() != 100 || !s4.isBogus()) {
        errln("UnicodeString move constructor did not move");
    }

    // Move self assignment leaves the object valid but in an undefined state.
    // Do it to make sure there is no crash,
    // but do not check for any particular resulting value.
    moveFrom(s1, s1);
    moveFrom(s2, s2);
    moveFrom(s3, s3);
    moveFrom(s4, s4);
    moveFrom(s5, s5);
    moveFrom(s6, s6);
    moveFrom(s7, s7);
    // Simple copy assignment must work.
    UnicodeString simple = UNICODE_STRING_SIMPLE("simple");
    s1 = s6 = s4 = s7 = simple;
    if(s1 != simple || s4 != simple || s6 != simple || s7 != simple) {
        errln("UnicodeString copy after self-move did not work");
    }
}


UnicodeStringTest::TestCompare()
{
    UnicodeString   test1("this is a test");
    UnicodeString   test2("this is a test");
    UnicodeString   test3("this is a test of the emergency broadcast system");
    UnicodeString   test4("never say, \"this is a test\"!!");

    UnicodeString   test5((UChar)0x5000);
    UnicodeString   test6((UChar)0x5100);

    UChar         uniChars[] = { 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73,
                 0x20, 0x61, 0x20, 0x74, 0x65, 0x73, 0x74, 0 };
    char            chars[] = "this is a test";

    // test operator== and operator!=
    if (test1 != test2 || test1 == test3 || test1 == test4)
        errln("operator== or operator!= failed");

    // test operator> and operator<
    if (test1 > test2 || test1 < test2 || !(test1 < test3) || !(test1 > test4) ||
        !(test5 < test6)
    ) {
        errln("operator> or operator< failed");
    }

    // test operator>= and operator<=
    if (!(test1 >= test2) || !(test1 <= test2) || !(test1 <= test3) || !(test1 >= test4))
        errln("operator>= or operator<= failed");

    // test compare(UnicodeString)
    if (test1.compare(test2) != 0 || test1.compare(test3) >= 0 || test1.compare(test4) <= 0)
        errln("compare(UnicodeString) failed");

    //test compare(offset, length, UnicodeString)
    if(test1.compare(0, 14, test2) != 0 ||
        test3.compare(0, 14, test2) != 0 ||
        test4.compare(12, 14, test2) != 0 ||
        test3.compare(0, 18, test1) <=0  )
        errln("compare(offset, length, UnicodeString) failes");

    // test compare(UChar*)
    if (test2.compare(uniChars) != 0 || test3.compare(uniChars) <= 0 || test4.compare(uniChars) >= 0)
        errln("compare(UChar*) failed");

    // test compare(char*)
    if (test2.compare(chars) != 0 || test3.compare(chars) <= 0 || test4.compare(chars) >= 0)
        errln("compare(char*) failed");

    // test compare(UChar*, length)
    if (test1.compare(uniChars, 4) <= 0 || test1.compare(uniChars, 4) <= 0)
        errln("compare(UChar*, length) failed");

    // test compare(thisOffset, thisLength, that, thatOffset, thatLength)
    if (test1.compare(0, 14, test2, 0, 14) != 0
    || test1.compare(0, 14, test3, 0, 14) != 0
    || test1.compare(0, 14, test4, 12, 14) != 0)
        errln("1. compare(thisOffset, thisLength, that, thatOffset, thatLength) failed");

    if (test1.compare(10, 4, test2, 0, 4) >= 0
    || test1.compare(10, 4, test3, 22, 9) <= 0
    || test1.compare(10, 4, test4, 22, 4) != 0)
        errln("2. compare(thisOffset, thisLength, that, thatOffset, thatLength) failed");

    // test compareBetween
    if (test1.compareBetween(0, 14, test2, 0, 14) != 0 || test1.compareBetween(0, 14, test3, 0, 14) != 0
                    || test1.compareBetween(0, 14, test4, 12, 26) != 0)
        errln("compareBetween failed");

    if (test1.compareBetween(10, 14, test2, 0, 4) >= 0 || test1.compareBetween(10, 14, test3, 22, 31) <= 0
                    || test1.compareBetween(10, 14, test4, 22, 26) != 0)
        errln("compareBetween failed");

    // test compare() etc. with strings that share a buffer but are not equal
    test2=test1; // share the buffer, length() too large for the stackBuffer
    test2.truncate(1); // change only the length, not the buffer
    if( test1==test2 || test1<=test2 ||
        test1.compare(test2)<=0 ||
        test1.compareCodePointOrder(test2)<=0 ||
        test1.compareCodePointOrder(0, INT32_MAX, test2)<=0 ||
        test1.compareCodePointOrder(0, INT32_MAX, test2, 0, INT32_MAX)<=0 ||
        test1.compareCodePointOrderBetween(0, INT32_MAX, test2, 0, INT32_MAX)<=0 ||
        test1.caseCompare(test2, U_FOLD_CASE_DEFAULT)<=0
    ) {
        errln("UnicodeStrings that share a buffer but have different lengths compare as equal");
    }

    /* test compareCodePointOrder() */
    {
        /* these strings are in ascending order */
        static const UChar strings[][4]={
            { 0x61, 0 },                    /* U+0061 */
            { 0x20ac, 0xd801, 0 },          /* U+20ac U+d801 */
            { 0x20ac, 0xd800, 0xdc00, 0 },  /* U+20ac U+10000 */
            { 0xd800, 0 },                  /* U+d800 */
            { 0xd800, 0xff61, 0 },          /* U+d800 U+ff61 */
            { 0xdfff, 0 },                  /* U+dfff */
            { 0xff61, 0xdfff, 0 },          /* U+ff61 U+dfff */
            { 0xff61, 0xd800, 0xdc02, 0 },  /* U+ff61 U+10002 */
            { 0xd800, 0xdc02, 0 },          /* U+10002 */
            { 0xd84d, 0xdc56, 0 }           /* U+23456 */
        };
        UnicodeString u[20]; // must be at least as long as strings[]
        int32_t i;

        for(i=0; i<UPRV_LENGTHOF(strings); ++i) {
            u[i]=UnicodeString(TRUE, strings[i], -1);
        }

        for(i=0; i<UPRV_LENGTHOF(strings)-1; ++i) {
            if(u[i].compareCodePointOrder(u[i+1])>=0 || u[i].compareCodePointOrder(0, INT32_MAX, u[i+1].getBuffer())>=0) {
                errln("error: UnicodeString::compareCodePointOrder() fails for string %d and the following one\n", i);
            }
        }
    }

    /* test caseCompare() */
    {
        static const UChar
        _mixed[]=               { 0x61, 0x42, 0x131, 0x3a3, 0xdf,       0x130,       0x49,  0xfb03,           0xd93f, 0xdfff, 0 },
        _otherDefault[]=        { 0x41, 0x62, 0x131, 0x3c3, 0x73, 0x53, 0x69, 0x307, 0x69,  0x46, 0x66, 0x49, 0xd93f, 0xdfff, 0 },
        _otherExcludeSpecialI[]={ 0x41, 0x62, 0x131, 0x3c3, 0x53, 0x73, 0x69,        0x131, 0x66, 0x46, 0x69, 0xd93f, 0xdfff, 0 },
        _different[]=           { 0x41, 0x62, 0x131, 0x3c3, 0x73, 0x53, 0x130,       0x49,  0x46, 0x66, 0x49, 0xd93f, 0xdffd, 0 };

        UnicodeString
            mixed(TRUE, _mixed, -1),
            otherDefault(TRUE, _otherDefault, -1),
            otherExcludeSpecialI(TRUE, _otherExcludeSpecialI, -1),
            different(TRUE, _different, -1);

        int8_t result;

        /* test caseCompare() */
        result=mixed.caseCompare(otherDefault, U_FOLD_CASE_DEFAULT);
        if(result!=0 || 0!=mixed.caseCompareBetween(0, INT32_MAX, otherDefault, 0, INT32_MAX, U_FOLD_CASE_DEFAULT)) {
            errln("error: mixed.caseCompare(other, default)=%ld instead of 0\n", result);
        }
        result=mixed.caseCompare(otherExcludeSpecialI, U_FOLD_CASE_EXCLUDE_SPECIAL_I);
        if(result!=0) {
            errln("error: mixed.caseCompare(otherExcludeSpecialI, U_FOLD_CASE_EXCLUDE_SPECIAL_I)=%ld instead of 0\n", result);
        }
        result=mixed.caseCompare(otherDefault, U_FOLD_CASE_EXCLUDE_SPECIAL_I);
        if(result==0 || 0==mixed.caseCompareBetween(0, INT32_MAX, otherDefault, 0, INT32_MAX, U_FOLD_CASE_EXCLUDE_SPECIAL_I)) {
            errln("error: mixed.caseCompare(other, U_FOLD_CASE_EXCLUDE_SPECIAL_I)=0 instead of !=0\n");
        }

        /* test caseCompare() */
        result=mixed.caseCompare(different, U_FOLD_CASE_DEFAULT);
        if(result<=0) {
            errln("error: mixed.caseCompare(different, default)=%ld instead of positive\n", result);
        }

        /* test caseCompare() - include the folded sharp s (U+00df) with different lengths */
        result=mixed.caseCompare(1, 4, different, 1, 5, U_FOLD_CASE_DEFAULT);
        if(result!=0 || 0!=mixed.caseCompareBetween(1, 5, different, 1, 6, U_FOLD_CASE_DEFAULT)) {
            errln("error: mixed.caseCompare(mixed, 1, 4, different, 1, 5, default)=%ld instead of 0\n", result);
        }

        /* test caseCompare() - stop in the middle of the sharp s (U+00df) */
        result=mixed.caseCompare(1, 4, different, 1, 4, U_FOLD_CASE_DEFAULT);
        if(result<=0) {
            errln("error: mixed.caseCompare(1, 4, different, 1, 4, default)=%ld instead of positive\n", result);
        }
    }

    // test that srcLength=-1 is handled in functions that
    // take input const UChar */int32_t srcLength (j785)
    {
        static const UChar u[]={ 0x61, 0x308, 0x62, 0 };
        UnicodeString s=UNICODE_STRING("a\\u0308b", 8).unescape();

        if(s.compare(u, -1)!=0 || s.compare(0, 999, u, 0, -1)!=0) {
            errln("error UnicodeString::compare(..., const UChar *, srcLength=-1) does not work");
        }

        if(s.compareCodePointOrder(u, -1)!=0 || s.compareCodePointOrder(0, 999, u, 0, -1)!=0) {
            errln("error UnicodeString::compareCodePointOrder(..., const UChar *, srcLength=-1, ...) does not work");
        }

        if(s.caseCompare(u, -1, U_FOLD_CASE_DEFAULT)!=0 || s.caseCompare(0, 999, u, 0, -1, U_FOLD_CASE_DEFAULT)!=0) {
            errln("error UnicodeString::caseCompare(..., const UChar *, srcLength=-1, ...) does not work");
        }

        if(s.indexOf(u, 1, -1, 0, 999)!=1 || s.indexOf(u+1, -1, 0, 999)!=1 || s.indexOf(u+1, -1, 0)!=1) {
            errln("error UnicodeString::indexOf(const UChar *, srcLength=-1, ...) does not work");
        }

        if(s.lastIndexOf(u, 1, -1, 0, 999)!=1 || s.lastIndexOf(u+1, -1, 0, 999)!=1 || s.lastIndexOf(u+1, -1, 0)!=1) {
            errln("error UnicodeString::lastIndexOf(const UChar *, srcLength=-1, ...) does not work");
        }

        UnicodeString s2, s3;
        s2.replace(0, 0, u+1, -1);
        s3.replace(0, 0, u, 1, -1);
        if(s.compare(1, 999, s2)!=0 || s2!=s3) {
            errln("error UnicodeString::replace(..., const UChar *, srcLength=-1, ...) does not work");
        }
    }
}


UnicodeStringTest::_testUnicodeStringHasMoreChar32Than(const UnicodeString &s, int32_t start, int32_t length, int32_t number) {
    if(s.hasMoreChar32Than(start, length, number)!=_refUnicodeStringHasMoreChar32Than(s, start, length, number)) {
        errln("hasMoreChar32Than(%d, %d, %d)=%hd is wrong\n",
                start, length, number, s.hasMoreChar32Than(start, length, number));
    }
}


UnicodeString::UnicodeString(UBool isTerminated,
                             ConstChar16Ptr textPtr,
                             int32_t textLength) {
  fUnion.fFields.fLengthAndFlags = kReadonlyAlias;
  const UChar *text = textPtr;
  if(text == NULL) {
    // treat as an empty string, do not alias
    setToEmpty();
  } else if(textLength < -1 ||
            (textLength == -1 && !isTerminated) ||
            (textLength >= 0 && isTerminated && text[textLength] != 0)
  ) {
    setToBogus();
  } else {
    if(textLength == -1) {
      // text is terminated, or else it would have failed the above test
      textLength = u_strlen(text);
    }
    setArray(const_cast<UChar *>(text), textLength,
             isTerminated ? textLength + 1 : textLength);
  }
}


    virtual UClassID getDynamicClassID() const {
        return getStaticClassID();
    }


        UnicodeString(const char * /*src*/, int32_t length, enum EInvariant /*inv*/
) : i(length) {}


static UnicodeString wrapUChars(const UChar *uchars) {
    return UnicodeString(TRUE, uchars, -1);
}


UnicodeString_charAt(int32_t offset, void *context) {
    return ((icu::UnicodeString*) context)->charAt(offset);
}


void UnicodeString::copyFieldsFrom(UnicodeString &src, UBool setSrcToBogus) U_NOEXCEPT {
  int16_t lengthAndFlags = fUnion.fFields.fLengthAndFlags = src.fUnion.fFields.fLengthAndFlags;
  if(lengthAndFlags & kUsingStackBuffer) {
    // Short string using the stack buffer, copy the contents.
    // Check for self assignment to prevent "overlap in memcpy" warnings,
    // although it should be harmless to copy a buffer to itself exactly.
    if(this != &src) {
      uprv_memcpy(fUnion.fStackFields.fBuffer, src.fUnion.fStackFields.fBuffer,
                  getShortLength() * U_SIZEOF_UCHAR);
    }
  } else {
    // In all other cases, copy all fields.
    fUnion.fFields.fArray = src.fUnion.fFields.fArray;
    fUnion.fFields.fCapacity = src.fUnion.fFields.fCapacity;
    if(!hasShortLength()) {
      fUnion.fFields.fLength = src.fUnion.fFields.fLength;
    }
    if(setSrcToBogus) {
      // Set src to bogus without releasing any memory.
      src.fUnion.fFields.fLengthAndFlags = kIsBogus;
      src.fUnion.fFields.fArray = NULL;
      src.fUnion.fFields.fCapacity = 0;
    }
  }
}


UnicodeString::getChar32Start(int32_t offset) const {
  if((uint32_t)offset < (uint32_t)length()) {
    const UChar *array = getArrayStart();
    U16_SET_CP_START(array, 0, offset);
    return offset;
  } else {
    return 0;
  }
}


UnicodeString::getLength() const {
    return length();
}


UnicodeString::setTo(UChar *buffer,
                     int32_t buffLength,
                     int32_t buffCapacity) {
  if(fUnion.fFields.fLengthAndFlags & kOpenGetBuffer) {
    // do not modify a string that has an "open" getBuffer(minCapacity)
    return *this;
  }

  if(buffer == NULL) {
    // treat as an empty string, do not alias
    releaseArray();
    setToEmpty();
    return *this;
  }

  if(buffLength < -1 || buffCapacity < 0 || buffLength > buffCapacity) {
    setToBogus();
    return *this;
  } else if(buffLength == -1) {
    // buffLength = u_strlen(buff); but do not look beyond buffCapacity
    const UChar *p = buffer, *limit = buffer + buffCapacity;
    while(p != limit && *p != 0) {
      ++p;
    }
    buffLength = (int32_t)(p - buffer);
  }

  releaseArray();

  fUnion.fFields.fLengthAndFlags = kWritableAlias;
  setArray(buffer, buffLength, buffCapacity);
  return *this;
}


UnicodeString::UnicodeString(const char *codepageData) {
  fUnion.fFields.fLengthAndFlags = kShortString;
  if(codepageData != 0) {
    setToUTF8(codepageData);
  }
}


UnicodeString::~UnicodeString()
{
#ifdef UNISTR_COUNT_FINAL_STRING_LENGTHS
  // Count lengths of strings at the end of their lifetime.
  // Useful for discussion of a desirable stack buffer size.
  // Count the contents length, not the optional NUL terminator nor further capacity.
  // Ignore open-buffer strings and strings which alias external storage.
  if((fUnion.fFields.fLengthAndFlags&(kOpenGetBuffer|kReadonlyAlias|kWritableAlias)) == 0) {
    if(hasShortLength()) {
      umtx_atomic_inc(finalLengthCounts + getShortLength());
    } else {
      umtx_atomic_inc(&beyondCount);
    }
  }
#endif

  releaseArray();
}

static u_atomic_int32_t finalLengthCounts[0x400];

static u_atomic_int32_t beyondCount(0);


UnicodeStringAppendable::getAppendBuffer(int32_t minCapacity,
                                         int32_t desiredCapacityHint,
                                         UChar *scratch, int32_t scratchCapacity,
                                         int32_t *resultCapacity) {
  if(minCapacity < 1 || scratchCapacity < minCapacity) {
    *resultCapacity = 0;
    return NULL;
  }
  int32_t oldLength = str.length();
  if(minCapacity <= (kMaxCapacity - oldLength) &&
      desiredCapacityHint <= (kMaxCapacity - oldLength) &&
      str.cloneArrayIfNeeded(oldLength + minCapacity, oldLength + desiredCapacityHint)) {
    *resultCapacity = str.getCapacity() - oldLength;
    return str.getArrayStart() + oldLength;
  }
  *resultCapacity = scratchCapacity;
  return scratch;
}


class TestEnumeration : public StringEnumeration {
public:
    TestEnumeration() : i(0) {}

    virtual int32_t count(UErrorCode& /*status*/) const {
        return UPRV_LENGTHOF(testEnumStrings);
    }

    virtual const UnicodeString *snext(UErrorCode &status) {
        if(U_SUCCESS(status) && i<UPRV_LENGTHOF(testEnumStrings)) {
            unistr=UnicodeString(testEnumStrings[i++], "");
            return &unistr;
        }

        return NULL;
    }

    virtual void reset(UErrorCode& /*status*/) {
        i=0;
    }

    static inline UClassID getStaticClassID() {
        return (UClassID)&fgClassID;
    }
    virtual UClassID getDynamicClassID() const {
        return getStaticClassID();
    }

private:
    static const char fgClassID;

    int32_t i;
}

class TestCheckedArrayByteSink : public CheckedArrayByteSink {
public:
    TestCheckedArrayByteSink(char* outbuf, int32_t capacity)
            : CheckedArrayByteSink(outbuf, capacity), calledFlush(FALSE) {}
    virtual void Flush() { calledFlush = TRUE; }
    UBool calledFlush;
}

class SimpleAppendable : public Appendable {
public:
    explicit SimpleAppendable(UnicodeString &dest) : str(dest) {}
    virtual UBool appendCodeUnit(UChar c) { str.append(c); return TRUE; }
    SimpleAppendable &reset() { str.remove(); return *this; }
private:
    UnicodeString &str;
}


UnicodeString::toUTF8(ByteSink &sink) const {
  int32_t length16 = length();
  if(length16 != 0) {
    char stackBuffer[1024];
    int32_t capacity = (int32_t)sizeof(stackBuffer);
    UBool utf8IsOwned = FALSE;
    char *utf8 = sink.GetAppendBuffer(length16 < capacity ? length16 : capacity,
                                      3*length16,
                                      stackBuffer, capacity,
                                      &capacity);
    int32_t length8 = 0;
    UErrorCode errorCode = U_ZERO_ERROR;
    u_strToUTF8WithSub(utf8, capacity, &length8,
                       getBuffer(), length16,
                       0xFFFD,  // Standard substitution character.
                       NULL,    // Don't care about number of substitutions.
                       &errorCode);
    if(errorCode == U_BUFFER_OVERFLOW_ERROR) {
      utf8 = (char *)uprv_malloc(length8);
      if(utf8 != NULL) {
        utf8IsOwned = TRUE;
        errorCode = U_ZERO_ERROR;
        u_strToUTF8WithSub(utf8, length8, &length8,
                           getBuffer(), length16,
                           0xFFFD,  // Standard substitution character.
                           NULL,    // Don't care about number of substitutions.
                           &errorCode);
      } else {
        errorCode = U_MEMORY_ALLOCATION_ERROR;
      }
    }
    if(U_SUCCESS(errorCode)) {
      sink.Append(utf8, length8);
      sink.Flush();
    }
    if(utf8IsOwned) {
      uprv_free(utf8);
    }
  }
}


UnicodeString::refCount() const {
  return umtx_loadAcquire(*((u_atomic_int32_t *)fUnion.fFields.fArray - 1));
}


    virtual const UnicodeString *snext(UErrorCode &status) {
        if(U_SUCCESS(status) && i<UPRV_LENGTHOF(testEnumStrings)) {
            unistr=UnicodeString(testEnumStrings[i++], "");
            return &unistr;
        }

        return NULL;
    }


UnicodeStringTest::TestSizeofUnicodeString() {
    // See the comments in unistr.h near the declaration of UnicodeString's fields.
    // See the API comments for UNISTR_OBJECT_SIZE.
    size_t sizeofUniStr=sizeof(UnicodeString);
    size_t expected=UNISTR_OBJECT_SIZE;
    if(expected!=sizeofUniStr) {
        // Possible cause: UNISTR_OBJECT_SIZE may not be a multiple of sizeof(pointer),
        // of the compiler might add more internal padding than expected.
        errln("sizeof(UnicodeString)=%d, expected UNISTR_OBJECT_SIZE=%d",
              (int)sizeofUniStr, (int)expected);
    }
    if(sizeofUniStr<32) {
        errln("sizeof(UnicodeString)=%d < 32, probably too small", (int)sizeofUniStr);
    }
    // We assume that the entire UnicodeString object,
    // minus the vtable pointer and 2 bytes for flags and short length,
    // is available for internal storage of UChars.
    int32_t expectedStackBufferLength=((int32_t)UNISTR_OBJECT_SIZE-sizeof(void *)-2)/U_SIZEOF_UCHAR;
    UnicodeString s;
    const UChar *emptyBuffer=s.getBuffer();
    for(int32_t i=0; i<expectedStackBufferLength; ++i) {
        s.append((UChar)0x2e);
    }
    const UChar *fullBuffer=s.getBuffer();
    if(fullBuffer!=emptyBuffer) {
        errln("unexpected reallocation when filling with assumed stack buffer size of %d",
              expectedStackBufferLength);
    }
    const UChar *terminatedBuffer=s.getTerminatedBuffer();
    if(terminatedBuffer==emptyBuffer) {
        errln("unexpected keeping stack buffer when overfilling assumed stack buffer size of %d",
              expectedStackBufferLength);
    }
}


UnicodeString::doExtract(int32_t start,
             int32_t length,
             UChar *dst,
             int32_t dstStart) const
{
  // pin indices to legal values
  pinIndices(start, length);

  // do not copy anything if we alias dst itself
  const UChar *array = getArrayStart();
  if(array + start != dst + dstStart) {
    us_arrayCopy(array, start, dst, dstStart, length);
  }
}


UnicodeString::UnicodeString(const UnicodeString& that,
                             int32_t srcStart,
                             int32_t srcLength) {
  fUnion.fFields.fLengthAndFlags = kShortString;
  setTo(that, srcStart, srcLength);
}


UnicodeStringTest::TestNameSpace() {
    // Provoke name collision unless the UnicodeString macros properly
    // qualify the icu::UnicodeString class.
    using namespace bogus;

    // Use all UnicodeString macros from unistr.h.
    icu::UnicodeString s1=icu::UnicodeString("abc", 3, US_INV);
    icu::UnicodeString s2=UNICODE_STRING("def", 3);
    icu::UnicodeString s3=UNICODE_STRING_SIMPLE("ghi");

    // Make sure the compiler does not optimize away instantiation of s1, s2, s3.
    icu::UnicodeString s4=s1+s2+s3;
    if(s4.length()!=9) {
        errln("Something wrong with UnicodeString::operator+().");
    }
}


UnicodeStringTest::TestUTF8() {
    static const uint8_t utf8[] = {
        // Code points:
        // 0x41, 0xd900,
        // 0x61, 0xdc00,
        // 0x110000, 0x5a,
        // 0x50000, 0x7a,
        // 0x10000, 0x20000,
        // 0xe0000, 0x10ffff
        0x41, 0xed, 0xa4, 0x80,
        0x61, 0xed, 0xb0, 0x80,
        0xf4, 0x90, 0x80, 0x80, 0x5a,
        0xf1, 0x90, 0x80, 0x80, 0x7a,
        0xf0, 0x90, 0x80, 0x80, 0xf0, 0xa0, 0x80, 0x80,
        0xf3, 0xa0, 0x80, 0x80, 0xf4, 0x8f, 0xbf, 0xbf
    };
    static const UChar expected_utf16[] = {
        0x41, 0xfffd, 0xfffd, 0xfffd,
        0x61, 0xfffd, 0xfffd, 0xfffd,
        0xfffd,  0xfffd, 0xfffd, 0xfffd,0x5a,
        0xd900, 0xdc00, 0x7a,
        0xd800, 0xdc00, 0xd840, 0xdc00,
        0xdb40, 0xdc00, 0xdbff, 0xdfff
    };
    UnicodeString from8 = UnicodeString::fromUTF8(StringPiece((const char *)utf8, (int32_t)sizeof(utf8)));
    UnicodeString expected(FALSE, expected_utf16, UPRV_LENGTHOF(expected_utf16));

    if(from8 != expected) {
        errln("UnicodeString::fromUTF8(StringPiece) did not create the expected string.");
    }
    std::string utf8_string((const char *)utf8, sizeof(utf8));
    UnicodeString from8b = UnicodeString::fromUTF8(utf8_string);
    if(from8b != expected) {
        errln("UnicodeString::fromUTF8(std::string) did not create the expected string.");
    }

    static const UChar utf16[] = {
        0x41, 0xd900, 0x61, 0xdc00, 0x5a, 0xd900, 0xdc00, 0x7a, 0xd800, 0xdc00, 0xdbff, 0xdfff
    };
    static const uint8_t expected_utf8[] = {
        0x41, 0xef, 0xbf, 0xbd, 0x61, 0xef, 0xbf, 0xbd, 0x5a, 0xf1, 0x90, 0x80, 0x80, 0x7a,
        0xf0, 0x90, 0x80, 0x80, 0xf4, 0x8f, 0xbf, 0xbf
    };
    UnicodeString us(FALSE, utf16, UPRV_LENGTHOF(utf16));

    char buffer[64];
    TestCheckedArrayByteSink sink(buffer, (int32_t)sizeof(buffer));
    us.toUTF8(sink);
    if( sink.NumberOfBytesWritten() != (int32_t)sizeof(expected_utf8) ||
        0 != uprv_memcmp(buffer, expected_utf8, sizeof(expected_utf8))
    ) {
        errln("UnicodeString::toUTF8() did not create the expected string.");
    }
    if(!sink.calledFlush) {
        errln("UnicodeString::toUTF8(sink) did not sink.Flush().");
    }
    // Initial contents for testing that toUTF8String() appends.
    std::string result8 = "-->";
    std::string expected8 = "-->" + std::string((const char *)expected_utf8, sizeof(expected_utf8));
    // Use the return value just for testing.
    std::string &result8r = us.toUTF8String(result8);
    if(result8r != expected8 || &result8r != &result8) {
        errln("UnicodeString::toUTF8String() did not create the expected string.");
    }
}


print(const UnicodeString& s,
      const char *name)
{
  UChar c;
  cout << name << ":|";
  for(int i = 0; i < s.length(); ++i) {
    c = s[i];
    if(c>= 0x007E || c < 0x0020)
      cout << "[0x" << hex << s[i] << "]";
    else
      cout << (char) s[i];
  }
  cout << '|' << endl;
}


UnicodeString::UnicodeString(const UnicodeString& that,
                             int32_t srcStart) {
  fUnion.fFields.fLengthAndFlags = kShortString;
  setTo(that, srcStart);
}


virtual UBool appendCodeUnit(UChar c) { str.append(c); return TRUE; }


UnicodeString::doEquals(const UnicodeString &text, int32_t len) const {
  // Requires: this & text not bogus and have same lengths.
  // Byte-wise comparison works for equality regardless of endianness.
  return uprv_memcmp(getArrayStart(), text.getArrayStart(), len * U_SIZEOF_UCHAR) == 0;
}


UnicodeString::getBuffer(int32_t minCapacity) {
  if(minCapacity>=-1 && cloneArrayIfNeeded(minCapacity)) {
    fUnion.fFields.fLengthAndFlags|=kOpenGetBuffer;
    setZeroLength();
    return getArrayStart();
  } else {
    return nullptr;
  }
}


UnicodeStringTest::TestStringEnumeration() {
    UnicodeString s;
    TestEnumeration ten;
    int32_t i, length;
    UErrorCode status;

    const UChar *pu;
    const char *pc;

    // test the next() default implementation and ensureCharsCapacity()
    for(i=0; i<UPRV_LENGTHOF(testEnumStrings); ++i) {
        status=U_ZERO_ERROR;
        pc=ten.next(&length, status);
        s=UnicodeString(testEnumStrings[i], "");
        if(U_FAILURE(status) || pc==NULL || length!=s.length() || UnicodeString(pc, length, "")!=s) {
            errln("StringEnumeration.next(%d) failed", i);
        }
    }
    status=U_ZERO_ERROR;
    if(ten.next(&length, status)!=NULL) {
        errln("StringEnumeration.next(done)!=NULL");
    }

    // test the unext() default implementation
    ten.reset(status);
    for(i=0; i<UPRV_LENGTHOF(testEnumStrings); ++i) {
        status=U_ZERO_ERROR;
        pu=ten.unext(&length, status);
        s=UnicodeString(testEnumStrings[i], "");
        if(U_FAILURE(status) || pu==NULL || length!=s.length() || UnicodeString(TRUE, pu, length)!=s) {
            errln("StringEnumeration.unext(%d) failed", i);
        }
    }
    status=U_ZERO_ERROR;
    if(ten.unext(&length, status)!=NULL) {
        errln("StringEnumeration.unext(done)!=NULL");
    }

    // test that the default clone() implementation works, and returns NULL
    if(ten.clone()!=NULL) {
        errln("StringEnumeration.clone()!=NULL");
    }

    // test that uenum_openFromStringEnumeration() works
    // Need a heap allocated string enumeration because it is adopted by the UEnumeration.
    StringEnumeration *newTen = new TestEnumeration;
    status=U_ZERO_ERROR;
    UEnumeration *uten = uenum_openFromStringEnumeration(newTen, &status);
    if (uten==NULL || U_FAILURE(status)) {
        errln("fail at file %s, line %d, UErrorCode is %s\n", __FILE__, __LINE__, u_errorName(status));
        return;
    }

    // test  uenum_next()
    for(i=0; i<UPRV_LENGTHOF(testEnumStrings); ++i) {
        status=U_ZERO_ERROR;
        pc=uenum_next(uten, &length, &status);
        if(U_FAILURE(status) || pc==NULL || strcmp(pc, testEnumStrings[i]) != 0) {
            errln("File %s, line %d, StringEnumeration.next(%d) failed", __FILE__, __LINE__, i);
        }
    }
    status=U_ZERO_ERROR;
    if(uenum_next(uten, &length, &status)!=NULL) {
        errln("File %s, line %d, uenum_next(done)!=NULL");
    }

    // test the uenum_unext()
    uenum_reset(uten, &status);
    for(i=0; i<UPRV_LENGTHOF(testEnumStrings); ++i) {
        status=U_ZERO_ERROR;
        pu=uenum_unext(uten, &length, &status);
        s=UnicodeString(testEnumStrings[i], "");
        if(U_FAILURE(status) || pu==NULL || length!=s.length() || UnicodeString(TRUE, pu, length)!=s) {
            errln("File %s, Line %d, uenum_unext(%d) failed", __FILE__, __LINE__, i);
        }
    }
    status=U_ZERO_ERROR;
    if(uenum_unext(uten, &length, &status)!=NULL) {
        errln("File %s, Line %d, uenum_unext(done)!=NULL" __FILE__, __LINE__);
    }

    uenum_close(uten);
}


UnicodeString::lastIndexOf(const UChar *srcChars,
               int32_t srcStart,
               int32_t srcLength,
               int32_t start,
               int32_t length) const
{
  if(isBogus() || srcChars == 0 || srcStart < 0 || srcLength == 0) {
    return -1;
  }

  // UnicodeString does not find empty substrings
  if(srcLength < 0 && srcChars[srcStart] == 0) {
    return -1;
  }

  // get the indices within bounds
  pinIndices(start, length);

  // find the last occurrence of the substring
  const UChar *array = getArrayStart();
  const UChar *match = u_strFindLast(array + start, length, srcChars + srcStart, srcLength);
  if(match == NULL) {
    return -1;
  } else {
    return (int32_t)(match - array);
  }
}


UnicodeString &UnicodeString::setToUTF8(StringPiece utf8) {
  unBogus();
  int32_t length = utf8.length();
  int32_t capacity;
  // The UTF-16 string will be at most as long as the UTF-8 string.
  if(length <= US_STACKBUF_SIZE) {
    capacity = US_STACKBUF_SIZE;
  } else {
    capacity = length + 1;  // +1 for the terminating NUL.
  }
  UChar *utf16 = getBuffer(capacity);
  int32_t length16;
  UErrorCode errorCode = U_ZERO_ERROR;
  u_strFromUTF8WithSub(utf16, getCapacity(), &length16,
      utf8.data(), length,
      0xfffd,  // Substitution character.
      NULL,    // Don't care about number of substitutions.
      &errorCode);
  releaseBuffer(length16);
  if(U_FAILURE(errorCode)) {
    setToBogus();
  }
  return *this;
}


class TestEnumeration : public StringEnumeration {
public:
    TestEnumeration() : i(0) {}

    virtual int32_t count(UErrorCode& /*status*/) const {
        return UPRV_LENGTHOF(testEnumStrings);
    }

    virtual const UnicodeString *snext(UErrorCode &status) {
        if(U_SUCCESS(status) && i<UPRV_LENGTHOF(testEnumStrings)) {
            unistr=UnicodeString(testEnumStrings[i++], "");
            return &unistr;
        }

        return NULL;
    }

    virtual void reset(UErrorCode& /*status*/) {
        i=0;
    }

    static inline UClassID getStaticClassID() {
        return (UClassID)&fgClassID;
    }
    virtual UClassID getDynamicClassID() const {
        return getStaticClassID();
    }

private:
    static const char fgClassID;

    int32_t i;
}

const char TestEnumeration::fgClassID=0;


UnicodeStringTest::TestRemoveReplace()
{
    UnicodeString   test1("The rain in Spain stays mainly on the plain");
    UnicodeString   test2("eat SPAMburgers!");
    UChar         test3[] = { 0x53, 0x50, 0x41, 0x4d, 0x4d, 0 };
    char            test4[] = "SPAM";
    UnicodeString&  test5 = test1;

    test1.replace(4, 4, test2, 4, 4);
    test1.replace(12, 5, test3, 4);
    test3[4] = 0;
    test1.replace(17, 4, test3);
    test1.replace(23, 4, test4);
    test1.replaceBetween(37, 42, test2, 4, 8);

    if (test1 != "The SPAM in SPAM SPAMs SPAMly on the SPAM")
        errln("One of the replace methods failed:\n"
              "  expected \"The SPAM in SPAM SPAMs SPAMly on the SPAM\",\n"
              "  got \"" + test1 + "\"");

    test1.remove(21, 1);
    test1.removeBetween(26, 28);

    if (test1 != "The SPAM in SPAM SPAM SPAM on the SPAM")
        errln("One of the remove methods failed:\n"
              "  expected \"The SPAM in SPAM SPAM SPAM on the SPAM\",\n"
              "  got \"" + test1 + "\"");

    for (int32_t i = 0; i < test1.length(); i++) {
        if (test5[i] != 0x53 && test5[i] != 0x50 && test5[i] != 0x41 && test5[i] != 0x4d && test5[i] != 0x20) {
            test1.setCharAt(i, 0x78);
        }
    }

    if (test1 != "xxx SPAM xx SPAM SPAM SPAM xx xxx SPAM")
        errln("One of the remove methods failed:\n"
              "  expected \"xxx SPAM xx SPAM SPAM SPAM xx xxx SPAM\",\n"
              "  got \"" + test1 + "\"");

    test1.remove();
    if (test1.length() != 0)
        errln("Remove() failed: expected empty string, got \"" + test1 + "\"");
}


void moveFrom(UnicodeString &dest, UnicodeString &src) {
    dest = std::move(src);
}


UnicodeStringAppendable::appendCodeUnit(UChar c) {
  return str.doAppend(&c, 0, 1).isWritable();
}


UBool Replaceable::hasMetaData() const {
    return TRUE;
}


UnicodeString::indexOf(const UChar *srcChars,
               int32_t srcStart,
               int32_t srcLength,
               int32_t start,
               int32_t length) const
{
  if(isBogus() || srcChars == 0 || srcStart < 0 || srcLength == 0) {
    return -1;
  }

  // UnicodeString does not find empty substrings
  if(srcLength < 0 && srcChars[srcStart] == 0) {
    return -1;
  }

  // get the indices within bounds
  pinIndices(start, length);

  // find the first occurrence of the substring
  const UChar *array = getArrayStart();
  const UChar *match = u_strFindFirst(array + start, length, srcChars + srcStart, srcLength);
  if(match == NULL) {
    return -1;
  } else {
    return (int32_t)(match - array);
  }
}


void UnicodeString::swap(UnicodeString &other) U_NOEXCEPT {
  UnicodeString temp;  // Empty short string: Known not to need releaseArray().
  // Copy fields without resetting source values in between.
  temp.copyFieldsFrom(*this, FALSE);
  this->copyFieldsFrom(other, FALSE);
  other.copyFieldsFrom(temp, FALSE);
  // Set temp to an empty string so that other's memory is not released twice.
  temp.fUnion.fFields.fLengthAndFlags = kShortString;
}


UnicodeString::getTerminatedBuffer() {
  if(!isWritable()) {
    return nullptr;
  }
  UChar *array = getArrayStart();
  int32_t len = length();
  if(len < getCapacity()) {
    if(fUnion.fFields.fLengthAndFlags & kBufferIsReadonly) {
      // If len<capacity on a read-only alias, then array[len] is
      // either the original NUL (if constructed with (TRUE, s, length))
      // or one of the original string contents characters (if later truncated),
      // therefore we can assume that array[len] is initialized memory.
      if(array[len] == 0) {
        return array;
      }
    } else if(((fUnion.fFields.fLengthAndFlags & kRefCounted) == 0 || refCount() == 1)) {
      // kRefCounted: Do not write the NUL if the buffer is shared.
      // That is mostly safe, except when the length of one copy was modified
      // without copy-on-write, e.g., via truncate(newLength) or remove(void).
      // Then the NUL would be written into the middle of another copy's string.

      // Otherwise, the buffer is fully writable and it is anyway safe to write the NUL.
      // Do not test if there is a NUL already because it might be uninitialized memory.
      // (That would be safe, but tools like valgrind & Purify would complain.)
      array[len] = 0;
      return array;
    }
  }
  if(len<INT32_MAX && cloneArrayIfNeeded(len+1)) {
    array = getArrayStart();
    array[len] = 0;
    return array;
  } else {
    return nullptr;
  }
}


uhash_hashUnicodeString(const UElement key) {
    const UnicodeString *str = (const UnicodeString*) key.pointer;
    return (str == NULL) ? 0 : str->hashCode();
}


UnicodeString::char32At(int32_t offset) const
{
  int32_t len = length();
  if((uint32_t)offset < (uint32_t)len) {
    const UChar *array = getArrayStart();
    UChar32 c;
    U16_GET(array, 0, offset, len, c);
    return c;
  } else {
    return kInvalidUChar;
  }
}


UnicodeString::extract(int32_t start,
                       int32_t length,
                       char *target,
                       int32_t targetCapacity,
                       enum EInvariant) const
{
  // if the arguments are illegal, then do nothing
  if(targetCapacity < 0 || (targetCapacity > 0 && target == NULL)) {
    return 0;
  }

  // pin the indices to legal values
  pinIndices(start, length);

  if(length <= targetCapacity) {
    u_UCharsToChars(getArrayStart() + start, target, length);
  }
  UErrorCode status = U_ZERO_ERROR;
  return u_terminateChars(target, targetCapacity, length, &status);
}


UnicodeString::tempSubString(int32_t start, int32_t len) const {
  pinIndices(start, len);
  const UChar *array = getBuffer();  // not getArrayStart() to check kIsBogus & kOpenGetBuffer
  if(array==NULL) {
    array=fUnion.fStackFields.fBuffer;  // anything not NULL because that would make an empty string
    len=-2;  // bogus result string
  }
  return UnicodeString(FALSE, array + start, len);
}


UnicodeString::doCompareCodePointOrder(int32_t start,
                                       int32_t length,
                                       const UChar *srcChars,
                                       int32_t srcStart,
                                       int32_t srcLength) const
{
  // compare illegal string values
  // treat const UChar *srcChars==NULL as an empty string
  if(isBogus()) {
    return -1;
  }

  // pin indices to legal values
  pinIndices(start, length);

  if(srcChars == NULL) {
    srcStart = srcLength = 0;
  }

  int32_t diff = uprv_strCompare(getArrayStart() + start, length, (srcChars!=NULL)?(srcChars + srcStart):NULL, srcLength, FALSE, TRUE);
  /* translate the 32-bit result into an 8-bit one */
  if(diff!=0) {
    return (int8_t)(diff >> 15 | 1);
  } else {
    return 0;
  }
}


UnicodeString::doReplace( int32_t start,
              int32_t length,
              const UnicodeString& src,
              int32_t srcStart,
              int32_t srcLength)
{
  // pin the indices to legal values
  src.pinIndices(srcStart, srcLength);

  // get the characters from src
  // and replace the range in ourselves with them
  return doReplace(start, length, src.getArrayStart(), srcStart, srcLength);
}


static const char *const
testEnumStrings[]={
    "a",
    "b",
    "c",
    "this is a long string which helps us test some buffer limits",
    "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
};

class TestEnumeration : public StringEnumeration {
public:
    TestEnumeration() : i(0) {}

    virtual int32_t count(UErrorCode& /*status*/) const {
        return UPRV_LENGTHOF(testEnumStrings);
    }

    virtual const UnicodeString *snext(UErrorCode &status) {
        if(U_SUCCESS(status) && i<UPRV_LENGTHOF(testEnumStrings)) {
            unistr=UnicodeString(testEnumStrings[i++], "");
            return &unistr;
        }

        return NULL;
    }

    virtual void reset(UErrorCode& /*status*/) {
        i=0;
    }

    static inline UClassID getStaticClassID() {
        return (UClassID)&fgClassID;
    }
    virtual UClassID getDynamicClassID() const {
        return getStaticClassID();
    }

private:
    static const char fgClassID;

    int32_t i;
}

const char TestEnumeration::fgClassID=0;

class TestCheckedArrayByteSink : public CheckedArrayByteSink {
public:
    TestCheckedArrayByteSink(char* outbuf, int32_t capacity)
            : CheckedArrayByteSink(outbuf, capacity), calledFlush(FALSE) {}
    virtual void Flush() { calledFlush = TRUE; }
    UBool calledFlush;
}


UnicodeString::clone() const {
  return new UnicodeString(*this);
}


UnicodeString::setToBogus()
{
  releaseArray();

  fUnion.fFields.fLengthAndFlags = kIsBogus;
  fUnion.fFields.fArray = 0;
  fUnion.fFields.fCapacity = 0;
}


UnicodeString::UnicodeString(const UChar *text,
                             int32_t textLength) {
  fUnion.fFields.fLengthAndFlags = kShortString;
  doAppend(text, 0, textLength);
}


UnicodeString::padTrailing(int32_t targetLength,
                           UChar padChar)
{
  int32_t oldLength = length();
  if(oldLength >= targetLength || !cloneArrayIfNeeded(targetLength)) {
    return FALSE;
  } else {
    // fill in padding character
    UChar *array = getArrayStart();
    int32_t length = targetLength;
    while(--length >= oldLength) {
      array[length] = padChar;
    }
    setLength(targetLength);
    return TRUE;
  }
}


UnicodeString::doIndexOf(UChar32 c,
                         int32_t start,
                         int32_t length) const {
  // pin indices
  pinIndices(start, length);

  // find the first occurrence of c
  const UChar *array = getArrayStart();
  const UChar *match = u_memchr32(array + start, c, length);
  if(match == NULL) {
    return -1;
  } else {
    return (int32_t)(match - array);
  }
}


UnicodeString::countChar32(int32_t start, int32_t length) const {
  pinIndices(start, length);
  // if(isBogus()) then fArray==0 and start==0 - u_countChar32() checks for NULL
  return u_countChar32(getArrayStart()+start, length);
}


UnicodeString::UnicodeString(int32_t capacity, UChar32 c, int32_t count) {
  fUnion.fFields.fLengthAndFlags = 0;
  if(count <= 0 || (uint32_t)c > 0x10ffff) {
    // just allocate and do not do anything else
    allocate(capacity);
  } else if(c <= 0xffff) {
    int32_t length = count;
    if(capacity < length) {
      capacity = length;
    }
    if(allocate(capacity)) {
      UChar *array = getArrayStart();
      UChar unit = (UChar)c;
      for(int32_t i = 0; i < length; ++i) {
        array[i] = unit;
      }
      setLength(length);
    }
  } else {  // supplementary code point, write surrogate pairs
    if(count > (INT32_MAX / 2)) {
      // We would get more than 2G UChars.
      allocate(capacity);
      return;
    }
    int32_t length = count * 2;
    if(capacity < length) {
      capacity = length;
    }
    if(allocate(capacity)) {
      UChar *array = getArrayStart();
      UChar lead = U16_LEAD(c);
      UChar trail = U16_TRAIL(c);
      for(int32_t i = 0; i < length; i += 2) {
        array[i] = lead;
        array[i + 1] = trail;
      }
      setLength(length);
    }
  }
}


UnicodeStringTest::TestBogus() {
    UnicodeString   test1("This is a test");
    UnicodeString   test2("This is a test");
    UnicodeString   test3("Me too!");

    // test isBogus() and setToBogus()
    if (test1.isBogus() || test2.isBogus() || test3.isBogus()) {
        errln("A string returned TRUE for isBogus()!");
    }

    // NULL pointers are treated like empty strings
    // use other illegal arguments to make a bogus string
    test3.setTo(FALSE, test1.getBuffer(), -2);
    if(!test3.isBogus()) {
        errln("A bogus string returned FALSE for isBogus()!");
    }
    if (test1.hashCode() != test2.hashCode() || test1.hashCode() == test3.hashCode()) {
        errln("hashCode() failed");
    }
    if(test3.getBuffer()!=0 || test3.getBuffer(20)!=0 || test3.getTerminatedBuffer()!=0) {
        errln("bogus.getBuffer()!=0");
    }
    if (test1.indexOf(test3) != -1) {
        errln("bogus.indexOf() != -1");
    }
    if (test1.lastIndexOf(test3) != -1) {
        errln("bogus.lastIndexOf() != -1");
    }
    if (test1.caseCompare(test3, U_FOLD_CASE_DEFAULT) != 1 || test3.caseCompare(test1, U_FOLD_CASE_DEFAULT) != -1) {
        errln("caseCompare() doesn't work with bogus strings");
    }
    if (test1.compareCodePointOrder(test3) != 1 || test3.compareCodePointOrder(test1) != -1) {
        errln("compareCodePointOrder() doesn't work with bogus strings");
    }

    // verify that non-assignment modifications fail and do not revive a bogus string
    test3.setToBogus();
    test3.append((UChar)0x61);
    if(!test3.isBogus() || test3.getBuffer()!=0) {
        errln("bogus.append('a') worked but must not");
    }

    test3.setToBogus();
    test3.findAndReplace(UnicodeString((UChar)0x61), test2);
    if(!test3.isBogus() || test3.getBuffer()!=0) {
        errln("bogus.findAndReplace() worked but must not");
    }

    test3.setToBogus();
    test3.trim();
    if(!test3.isBogus() || test3.getBuffer()!=0) {
        errln("bogus.trim() revived bogus but must not");
    }

    test3.setToBogus();
    test3.remove(1);
    if(!test3.isBogus() || test3.getBuffer()!=0) {
        errln("bogus.remove(1) revived bogus but must not");
    }

    test3.setToBogus();
    if(!test3.setCharAt(0, 0x62).isBogus() || !test3.isEmpty()) {
        errln("bogus.setCharAt(0, 'b') worked but must not");
    }

    test3.setToBogus();
    if(test3.truncate(1) || !test3.isBogus() || !test3.isEmpty()) {
        errln("bogus.truncate(1) revived bogus but must not");
    }

    // verify that assignments revive a bogus string
    test3.setToBogus();
    if(!test3.isBogus() || (test3=test1).isBogus() || test3!=test1) {
        errln("bogus.operator=() failed");
    }

    test3.setToBogus();
    if(!test3.isBogus() || test3.fastCopyFrom(test1).isBogus() || test3!=test1) {
        errln("bogus.fastCopyFrom() failed");
    }

    test3.setToBogus();
    if(!test3.isBogus() || test3.setTo(test1).isBogus() || test3!=test1) {
        errln("bogus.setTo(UniStr) failed");
    }

    test3.setToBogus();
    if(!test3.isBogus() || test3.setTo(test1, 0).isBogus() || test3!=test1) {
        errln("bogus.setTo(UniStr, 0) failed");
    }

    test3.setToBogus();
    if(!test3.isBogus() || test3.setTo(test1, 0, 0x7fffffff).isBogus() || test3!=test1) {
        errln("bogus.setTo(UniStr, 0, len) failed");
    }

    test3.setToBogus();
    if(!test3.isBogus() || test3.setTo(test1.getBuffer(), test1.length()).isBogus() || test3!=test1) {
        errln("bogus.setTo(const UChar *, len) failed");
    }

    test3.setToBogus();
    if(!test3.isBogus() || test3.setTo((UChar)0x2028).isBogus() || test3!=UnicodeString((UChar)0x2028)) {
        errln("bogus.setTo(UChar) failed");
    }

    test3.setToBogus();
    if(!test3.isBogus() || test3.setTo((UChar32)0x1d157).isBogus() || test3!=UnicodeString((UChar32)0x1d157)) {
        errln("bogus.setTo(UChar32) failed");
    }

    test3.setToBogus();
    if(!test3.isBogus() || test3.setTo(FALSE, test1.getBuffer(), test1.length()).isBogus() || test3!=test1) {
        errln("bogus.setTo(readonly alias) failed");
    }

    // writable alias to another string's buffer: very bad idea, just convenient for this test
    test3.setToBogus();
    if(!test3.isBogus() ||
            test3.setTo(const_cast<UChar *>(test1.getBuffer()),
                        test1.length(), test1.getCapacity()).isBogus() ||
            test3!=test1) {
        errln("bogus.setTo(writable alias) failed");
    }

    // verify simple, documented ways to turn a bogus string into an empty one
    test3.setToBogus();
    if(!test3.isBogus() || (test3=UnicodeString()).isBogus() || !test3.isEmpty()) {
        errln("bogus.operator=(UnicodeString()) failed");
    }

    test3.setToBogus();
    if(!test3.isBogus() || test3.setTo(UnicodeString()).isBogus() || !test3.isEmpty()) {
        errln("bogus.setTo(UnicodeString()) failed");
    }

    test3.setToBogus();
    if(test3.remove().isBogus() || test3.getBuffer()==0 || !test3.isEmpty()) {
        errln("bogus.remove() failed");
    }

    test3.setToBogus();
    if(test3.remove(0, INT32_MAX).isBogus() || test3.getBuffer()==0 || !test3.isEmpty()) {
        errln("bogus.remove(0, INT32_MAX) failed");
    }

    test3.setToBogus();
    if(test3.truncate(0) || test3.isBogus() || !test3.isEmpty()) {
        errln("bogus.truncate(0) failed");
    }

    test3.setToBogus();
    if(!test3.isBogus() || test3.setTo((UChar32)-1).isBogus() || !test3.isEmpty()) {
        errln("bogus.setTo((UChar32)-1) failed");
    }

    static const UChar nul=0;

    test3.setToBogus();
    if(!test3.isBogus() || test3.setTo(&nul, 0).isBogus() || !test3.isEmpty()) {
        errln("bogus.setTo(&nul, 0) failed");
    }

    test3.setToBogus();
    if(!test3.isBogus() || test3.getBuffer()!=0) {
        errln("setToBogus() failed to make a string bogus");
    }

    test3.setToBogus();
    if(test1.isBogus() || !(test1=test3).isBogus()) {
        errln("normal=bogus failed to make the left string bogus");
    }

    // test that NULL primitive input string values are treated like
    // empty strings, not errors (bogus)
    test2.setTo((UChar32)0x10005);
    if(test2.insert(1, nullptr, 1).length()!=2) {
        errln("UniStr.insert(...nullptr...) should not modify the string but does");
    }

    UErrorCode errorCode=U_ZERO_ERROR;
    UnicodeString
        test4((const UChar *)NULL),
        test5(TRUE, (const UChar *)NULL, 1),
        test6((UChar *)NULL, 5, 5),
        test7((const char *)NULL, 3, NULL, errorCode);
    if(test4.isBogus() || test5.isBogus() || test6.isBogus() || test7.isBogus()) {
        errln("a constructor set to bogus for a NULL input string, should be empty");
    }

    test4.setTo(NULL, 3);
    test5.setTo(TRUE, (const UChar *)NULL, 1);
    test6.setTo((UChar *)NULL, 5, 5);
    if(test4.isBogus() || test5.isBogus() || test6.isBogus()) {
        errln("a setTo() set to bogus for a NULL input string, should be empty");
    }

    // test that bogus==bogus<any
    if(test1!=test3 || test1.compare(test3)!=0) {
        errln("bogus==bogus failed");
    }

    test2.remove();
    if(test1>=test2 || !(test2>test1) || test1.compare(test2)>=0 || !(test2.compare(test1)>0)) {
        errln("bogus<empty failed");
    }
}


UnicodeString::UnicodeString(UChar ch) {
  fUnion.fFields.fLengthAndFlags = kLength1 | kShortString;
  fUnion.fStackFields.fBuffer[0] = ch;
}


UnicodeString::doLastIndexOf(UChar c,
                 int32_t start,
                 int32_t length) const
{
  if(isBogus()) {
    return -1;
  }

  // pin indices
  pinIndices(start, length);

  // find the last occurrence of c
  const UChar *array = getArrayStart();
  const UChar *match = u_memrchr(array + start, c, length);
  if(match == NULL) {
    return -1;
  } else {
    return (int32_t)(match - array);
  }
}


UnicodeString::UnicodeString(UChar32 ch) {
  fUnion.fFields.fLengthAndFlags = kShortString;
  int32_t i = 0;
  UBool isError = FALSE;
  U16_APPEND(fUnion.fStackFields.fBuffer, i, US_STACKBUF_SIZE, ch, isError);
  // We test isError so that the compiler does not complain that we don't.
  // If isError then i==0 which is what we want anyway.
  if(!isError) {
    setShortLength(i);
  }
}


void UnicodeStringTest::TestUnicodeStringInsertAppendToSelf() {
    IcuTestErrorCode status(*this, "TestUnicodeStringAppendToSelf");

    // Test append operation
    UnicodeString str(u"foo ");
    str.append(str);
    str.append(str);
    str.append(str);
    assertEquals("", u"foo foo foo foo foo foo foo foo ", str);

    // Test append operation with readonly alias to start
    str = UnicodeString(TRUE, u"foo ", 4);
    str.append(str);
    str.append(str);
    str.append(str);
    assertEquals("", u"foo foo foo foo foo foo foo foo ", str);

    // Test append operation with aliased substring
    str = u"abcde";
    UnicodeString sub = str.tempSubString(1, 2);
    str.append(sub);
    assertEquals("", u"abcdebc", str);

    // Test append operation with double-aliased substring
    str = UnicodeString(TRUE, u"abcde", 5);
    sub = str.tempSubString(1, 2);
    str.append(sub);
    assertEquals("", u"abcdebc", str);

    // Test insert operation
    str = u"a-*b";
    str.insert(2, str);
    str.insert(4, str);
    str.insert(8, str);
    assertEquals("", u"a-a-a-a-a-a-a-a-*b*b*b*b*b*b*b*b", str);

    // Test insert operation with readonly alias to start
    str = UnicodeString(TRUE, u"a-*b", 4);
    str.insert(2, str);
    str.insert(4, str);
    str.insert(8, str);
    assertEquals("", u"a-a-a-a-a-a-a-a-*b*b*b*b*b*b*b*b", str);

    // Test insert operation with aliased substring
    str = u"abcde";
    sub = str.tempSubString(1, 3);
    str.insert(2, sub);
    assertEquals("", u"abbcdcde", str);

    // Test insert operation with double-aliased substring
    str = UnicodeString(TRUE, u"abcde", 5);
    sub = str.tempSubString(1, 3);
    str.insert(2, sub);
    assertEquals("", u"abbcdcde", str);
}

static const char *const
testEnumStrings[]={
    "a",
    "b",
    "c",
    "this is a long string which helps us test some buffer limits",
    "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
};

class TestEnumeration : public StringEnumeration {
public:
    TestEnumeration() : i(0) {}

    virtual int32_t count(UErrorCode& /*status*/) const {
        return UPRV_LENGTHOF(testEnumStrings);
    }

    virtual const UnicodeString *snext(UErrorCode &status) {
        if(U_SUCCESS(status) && i<UPRV_LENGTHOF(testEnumStrings)) {
            unistr=UnicodeString(testEnumStrings[i++], "");
            return &unistr;
        }

        return NULL;
    }

    virtual void reset(UErrorCode& /*status*/) {
        i=0;
    }

    static inline UClassID getStaticClassID() {
        return (UClassID)&fgClassID;
    }
    virtual UClassID getDynamicClassID() const {
        return getStaticClassID();
    }

private:
    static const char fgClassID;

    int32_t i;
}

class TestCheckedArrayByteSink : public CheckedArrayByteSink {
public:
    TestCheckedArrayByteSink(char* outbuf, int32_t capacity)
            : CheckedArrayByteSink(outbuf, capacity), calledFlush(FALSE) {}
    virtual void Flush() { calledFlush = TRUE; }
    UBool calledFlush;
}

class SimpleAppendable : public Appendable {
public:
    explicit SimpleAppendable(UnicodeString &dest) : str(dest) {}
    virtual UBool appendCodeUnit(UChar c) { str.append(c); return TRUE; }
    SimpleAppendable &reset() { str.remove(); return *this; }
private:
    UnicodeString &str;
}


UnicodeStringTest::TestStartsWithAndEndsWithNulTerminated() {
    UnicodeString test("abcde");
    const UChar ab[] = { 0x61, 0x62, 0 };
    const UChar de[] = { 0x64, 0x65, 0 };
    assertTrue("abcde.startsWith(ab, -1)", test.startsWith(ab, -1));
    assertTrue("abcde.startsWith(ab, 0, -1)", test.startsWith(ab, 0, -1));
    assertTrue("abcde.endsWith(de, -1)", test.endsWith(de, -1));
    assertTrue("abcde.endsWith(de, 0, -1)", test.endsWith(de, 0, -1));
}

static const char *const
testEnumStrings[]={
    "a",
    "b",
    "c",
    "this is a long string which helps us test some buffer limits",
    "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
};

class TestEnumeration : public StringEnumeration {
public:
    TestEnumeration() : i(0) {}

    virtual int32_t count(UErrorCode& /*status*/) const {
        return UPRV_LENGTHOF(testEnumStrings);
    }

    virtual const UnicodeString *snext(UErrorCode &status) {
        if(U_SUCCESS(status) && i<UPRV_LENGTHOF(testEnumStrings)) {
            unistr=UnicodeString(testEnumStrings[i++], "");
            return &unistr;
        }

        return NULL;
    }

    virtual void reset(UErrorCode& /*status*/) {
        i=0;
    }

    static inline UClassID getStaticClassID() {
        return (UClassID)&fgClassID;
    }
    virtual UClassID getDynamicClassID() const {
        return getStaticClassID();
    }

private:
    static const char fgClassID;

    int32_t i;
}

const char TestEnumeration::fgClassID=0;

class SimpleAppendable : public Appendable {
public:
    explicit SimpleAppendable(UnicodeString &dest) : str(dest) {}
    virtual UBool appendCodeUnit(UChar c) { str.append(c); return TRUE; }
    SimpleAppendable &reset() { str.remove(); return *this; }
private:
    UnicodeString &str;
}


UnicodeString::releaseArray() {
  if((fUnion.fFields.fLengthAndFlags & kRefCounted) && removeRef() == 0) {
    uprv_free((int32_t *)fUnion.fFields.fArray - 1);
  }
}


UnicodeStringAppendable::appendCodePoint(UChar32 c) {
  UChar buffer[U16_MAX_LENGTH];
  int32_t cLength = 0;
  UBool isError = FALSE;
  U16_APPEND(buffer, cLength, U16_MAX_LENGTH, c, isError);
  return !isError && str.doAppend(buffer, 0, cLength).isWritable();
}


UnicodeString::fastCopyFrom(const UnicodeString &src) {
  return copyFrom(src, TRUE);
}


UnicodeString::operator=(const UnicodeString &src) {
  return copyFrom(src);
}


U_CAPI void unistr_printLengths() {
  int32_t i;
  for(i = 0; i <= 59; ++i) {
    printf("%2d,  %9d\n", i, (int32_t)finalLengthCounts[i]);
  }
  int32_t beyond = beyondCount;
  for(; i < UPRV_LENGTHOF(finalLengthCounts); ++i) {
    beyond += finalLengthCounts[i];
  }
  printf(">59, %9d\n", beyond);
}