memptr.java
/*
** Module : memptr.java
** Abstract : Progress 4GL compatible memptr data type
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 GES 20050906 @22564 Created initial version with support for a subset of memptr
** processing.
** 002 GES 20060422 @25654 Added a copy constructor.
** 003 GES 20060724 @28172 Use forced assign in copy constructor.
** 004 GES 20060807 @28476 Moved to externalizable interface to optimize network performance.
** 005 CA 20130123 Added c'tor which builds a new instance from a BaseDataType value,
** if possible. This should be used only when constructing an instance
** using the value returned by a DYNAMIC-FUNCTION call; 4GL does some
** special casting in this case, which is not implemented yet.
** 006 GES 20130322 Fixed the BDT c'tor.
** 007 GES 20130523 Major rewrite to back this using a native memory buffer instead of
** a byte[] on the Java heap. Added missing features. Many deviations
** from the 4GL were found and fixed. The actual data access and
** storage is now in this class instead of in the parent, and this is
** exposed via a set of abstract worker methods that we implement here.
** 008 CA 20130916 On P2J Client side, use the memory daemon directly; this allows
** access to memptr instances from P2J Client side.
** 009 GES 20131212 Made isUninitialized() public.
** 010 MAG 20140602 Fixed the BDT c'tor and modified the assign(BDT,boolean) on _POLY
** morphing. Add compareTo(Object).
** 011 CA 20140811 Fixed clearing of unknown flag in assignment/output param.
** 012 ECF 20150824 Implemented context cleanup processing to deallocate memory not
** previously freed explicitly.
** CA 20150827 In memptr c'tors, if a memptr instance is passed, just copy its
** pointer.
** 013 OM 20160705 Replaced deep copy with pointer copy and reference counter. Removed
** negative allocation on WIN platform. Fixed toStringMessage() method.
** 014 CA 20160629 memptr vars can never be undoable.
** 015 OM 20160831 Optimized context-local accesses. Javadoc updates.
** 016 GES 20171207 Removed forced version of assign().
** 017 CA 20180214 Replaced workArea with lazy-initialization, as when transferred via
** the socket, the Reader thread can't access context-local state
** GES 20180322 Added compiledWordSize().
** 018 ECF 20181214 Implemented LargeObject interface.
** 019 HC 20190302 Implemented emulation of GetWindowRect and GetClientRect Windows API
** functions.
** 020 IAS 20190617 Changed the signature of the writeByteRange method
** 021 ECF 20190628 Implemented COPY-LOB runtime.
** 022 CA 20191203 Fixed overlay and unknown COPY-LOB related bugs.
** 023 IAS 20201007 Added Type enum.
** OM 20210404 Fine-tuned errors generated by edge-case in copy-lob statements.
** CA 20210428 Fixed conversion when the lvalue for an assign-styme stmt is a OO property
** (chained or not). In this case, a setter must wrap the assign-style stmt, to
** change the property's value.
** CA 20210818 Fixed memptr parameter runtime - they share the 'pointer structure' with the
** passed argument.
** CA 20210823 A fix for CA/20210818 - assignment of a parameter to a non-unknown memptr will
** clear the 'srcWasUnknown' flag.
** CA 20220217 If assign(memptr) is called for a memptr referencing the same address, do nothing.
** AL2 20220328 Added proxy checks for BDT.
** CA 20220515 Allow library and memptr calls to be executed on server-side.
** CA 20220920 A fix for CA/20220217 - assign to an unknown memptr must set this instance to
** unknown, even if both their null address (0) matches.
** CA 20220922 The address space must always use a positive size (negative sizes are allowed on
** linux).
** CA 20221020 'checkAddressSpace' must ignore 0 address number.
** CA 20221025 Added a protection against NegativeArraySizeException in clearAddressSpace.
** EVL 20221025 Small fix for message construction with two long values summing results.
** 024 CA 20230215 'instantiateDefault' now is implemented by each sub-class.
** CA 20230215 'duplicate()' method returns the real type instead of BDT.
** 025 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 026 CA 20230918 Cleanup the memory allocations only if on server-side or if the FWD client is
** still running.
** 027 CA 20231017 Fixed 'writeByteRange' to consider the given length (number of bytes) to write.
** 028 OM 20231001 Avoid deprecated APIs.
** 029 GBB 20240826 Moving OSResourceManager to osresource package.
** 030 GBB 20240912 OSResourceManager method isServerSide to use an enum param.
** 031 GBB 20250403 asByteArray made public to be accessed from CoreAppserver.
** 032 ICP 20250328 Made writeByte, readByteRange, and writeByteRange public to enable
** external direct memory manipulation (e.g., from MemoryOutputStream).
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.io.*;
import java.util.logging.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.p2j.util.osresource.*;
import org.roaringbitmap.longlong.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.security.*;
/**
* Implements the unique behavior of a Progress 4GL compatible {@code MEMPTR} data type
* which is backed by a native platform memory buffer (instead of being allocated on the Java
* heap). The contents of the buffer are mutable. All Progress 4GL language features are
* supported up to the 10.2B level.
* <p>
* Format string related processing is handled in method {@link #toString(String)}.
* <p>
* The following is the mapping of Progress language features to the corresponding feature in
* this class:
* <p>
* <pre>
* Progress Function Java Method
* ----------------------------- -----------------------
* get-byte-order {@link #getByteOrder}
* get-pointer-value {@link #getPointerValue}
* get-size {@link #length}
*
* Progress Language Statement Java Method
* ----------------------------- -----------------------
* set-byte-order {@link #setByteOrder}
* set-pointer-value {@link #setPointerValue}
* set-size {@link #setLength}
* </pre>
* <p>
* All of the above features are specific to the {@code MEMPTR} type (they do not work for
* {@code RAW} variables in the 4GL, such attempted usage will cause a compiler error). All
* other features are shared and are implemented in the common base class.
*/
public class memptr
extends BinaryData
implements LargeObject
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(memptr.class.getName());
/** No byte order has been explicitly requested. This is the default state of a memptr. */
public static final int NO_BYTE_ORDER = 0;
/** Operations should use the native byte order on the current machine. */
public static final int HOST_BYTE_ORDER = 1;
/** Operations should use big endian byte order. */
public static final int BIG_ENDIAN = 2;
/** Operations should use little endian byte order. */
public static final int LITTLE_ENDIAN = 3;
/** Smallest value accepted in SET-SIZE() without generating an error. */
public static final int LOWER_BOUND = -2147483648;
/** Largest value accepted in SET-SIZE() without generating an error. */
public static final int UPPER_BOUND = 2147483647;
/** Stores context local instances of the remote memory manager proxy. */
private static final ContextLocal<WorkArea> ctxt = new ContextLocal<WorkArea>()
{
@Override
protected WorkArea initialValue()
{
WorkArea wa = new WorkArea();
wa.serverSide = OSResourceManager.getInstance().isServerSide(OsResourceType.MEMPTR);
wa.bufMgr = OSResourceManager.getMemptr();
wa.nativeByteOrder = wa.bufMgr.isLittleEndian() ? LITTLE_ENDIAN : BIG_ENDIAN;
wa.compiledWordSize = wa.bufMgr.compiledWordSize();
wa.winOS = EnvironmentOps.isUnderWindowsFamily();
return wa;
}
@Override
protected void cleanup(WorkArea wa)
{
try
{
if (wa.serverSide || SessionManager.get().getSession().isRunning())
{
// cleanup only if the allocation is done on server-side or if the FWD client is still running
// (if it has terminated, the heap will be de-allocated along with the JVM process).
wa.bufMgr.cleanup();
}
}
catch (SilentUnwindException exc)
{
// this will occur in cases where the cleanup is triggered from the server due to
// the client being disconnected; we don't want to unwind and disrupt other cleanup,
// so just log and eat the exception
if (LOG.isLoggable(Level.FINE))
{
LOG.log(Level.FINE, "Error cleaning up memptr context", exc);
}
}
}
};
/**
* The working area where this instance was created. Caches costly access to context local.
* Lazy-initialized in {@link #locate()} as it can't be initialized by the c'tor - when
* transported via the network, the Reader thread will not be able to access the context.
*/
private WorkArea workArea;
/** Pointer structure shared between parameters and arguments. */
private memptrPointer pointer = new memptrPointer(NO_BYTE_ORDER, 0, 0);
/** Flag indicating this is a parameter. */
private boolean parameter = false;
/** Flag indicating if the source argument for this parameter was unknown. */
private boolean srcWasUnknown = false;
/**
* Default constructor, creates an instance that represents an uninitialized byte array (a
* {@code null} array).
*/
public memptr()
{
}
/**
* This is a special c'tor which should be used only when converting the
* value returned by a function or method with polymorphic return type into the
* expected type (i.e. DYNAMIC-FUNCTION()). In such cases, the 4GL does some
* automatic type conversion (see {@link #assign(BaseDataType)}).
*
* @param value
* The value to be used for this instance.
*/
public memptr(BaseDataType value)
{
this(); // initialize winOS
if (value == null)
{
setUnknown();
return;
}
// the value may be a proxy; make sure to unwrap and work with the real value
value = value.val();
if (value.isUnknown())
{
setUnknown();
}
else
{
if (value instanceof memptr)
{
copyPointer((memptr) value);
}
else
{
assign(value);
}
}
}
/**
* Constructs an instance after copying the parameter's data into the internal representation
* of this class.
* <p>
* If the parameter is {@code unknown}, this instance will be set to {@code unknown}.
*
* @param value
* The value to be used for this instance.
*/
public memptr(memptr value)
{
this(); // init win_os
// the value may be a proxy; make sure to unwrap and work with the real value
if (BaseDataType.isProxy(value))
{
if (value.val() instanceof memptr)
{
value = (memptr) value.val();
}
else
{
initialize(value);
return;
}
}
copyPointer(value);
}
/**
* Constructs an instance after copying the parameter's data into the internal representation
* of this class.
* <p>
* If the parameter is {@code null}, the instance will be {@code unknown}.
*
* @param value
* The value to be used for this instance.
*/
public memptr(byte[] value)
{
this(); // init win_os
assign(value);
}
/**
* Creates a new instance of the same type that represents {@code unknown}.
*
* @return An instance that represents {@code unknown}.
*/
public static memptr instantiateUnknownMemptr()
{
memptr m = new memptr();
m.setUnknown();
return m;
}
/**
* Get the type
* @return type
*/
@Override
public Type getType()
{
return Type.MEMPTR;
}
/**
* Compares this instance with the specified instance and returns a -1 if this instance is less
* than the specified, 0 if the two instances are equal and 1 if this instance is greater than
* the specified instance. This is the implementation of the {@code Comparable} interface.
* <p>
* The algorithm will fail to give meaningful results in the case where one tries to sort
* against other objects that do not represent compatible values.
*
* @param obj
* The instance to compare against.
*
* @return -1, 0 or 1 depending on if this instance is less than, equal to or greater
* (respectively) than the specified instance {@code obj}.
*/
@Override
public int compareTo(Object obj)
{
if (obj instanceof BaseDataType)
{
return super.compareTo(new memptr((BaseDataType) obj));
}
return super.compareTo(obj);
}
/**
* Does the same as standard {@code clone()} method but returns an instance of
* {@code BaseDataType} and doesn't throw the {@code CloneNotSupportedException}.
*
* @return A clone of this instance.
*/
@Override
public memptr duplicate()
{
memptr m = new memptr();
// copy reference of the instance data
m.copyPointer(this);
return m;
}
/**
* Creates a new instance of the same type that is {@code unknown}.
*
* @return An instance that represents {@code unknown}.
*/
@Override
public BaseDataType instantiateUnknown()
{
memptr m = new memptr();
m.setUnknown();
return m;
}
/**
* Creates a new instance of the same type that represents the default initialized value.
*
* @return An instance that represents the default value.
*/
@Override
public BaseDataType instantiateDefault()
{
return new memptr();
}
/**
* Reports if this instance represents <code>unknown</code>.
*
* @return <code>true</code> if this instance is set to the
* <code>unknown</code>.
*/
@Override
public boolean isUnknown()
{
return unknown || pointer.unknown;
}
/**
* Sets the state of this instance's <code>unknown</code> flag to <code>true</code>.
* <p>
* <b>Warning: the data stored in this instance WILL BE deallocated after calling this
* method.</b>
*/
@Override
public void setUnknown()
{
super.setUnknown();
this.pointer.unknown = true;
}
/**
* Method implementation of the GET-POINTER-VALUE 4GL built-in function which returns a 64-bit
* integer value representing the pointer used by the instance. This is a real memory address
* for the first byte of the buffer that has been allocated. Of course, this is not only very
* dangerous, but it is also not portable. Yet, this is how the 4GL does it.
*
* @return The 64-bit integer value representing the location in memory of the instance's
* buffer. If {@code unknown}, the pointer will be {@code unknown}. If
* uninitialized, this will return 0.
*/
public int64 getPointerValue()
{
// unknown takes precedence over being uninitialized
if (isUnknown())
{
return new int64();
}
return new int64(pointer.addr);
}
/**
* Sets the binary data to point to a memory address. The current size is left unchanged
* and any current buffer is NOT deallocated. This is just how the 4GL implements it.
* <p>
* If the instance is {@code unknown}, then this call silently fails. It does NOT change the
* pointer value, the instance will still be {@code unknown}. This is how the 4GL does it.
*
* @param ptr
* The 64-bit value representing the pointer to the memory address. This may be
* any value (negative, 0 or a positive number). Since the 4GL has no unsigned
* 64-bit data type, it is necessary negative numbers be supported since memory
* addresses are typically unsigned values. Even though most systems do not
* allocate valid memory at address 0 (so that 0 can be used as a well known
* invalid memory address), the 4GL does allow 0 to be used (perhaps it is needed
* on some systems). If this value is {@code unknown}, the method will silently
* return with no change to the pointer value. This is how the 4GL does it.
*
* @return The current instance.
*/
public memptr setPointerValue(NumberType ptr)
{
if (ptr == null || ptr.isUnknown())
{
// no error here, just silently return
return this;
}
return setPointerValue(ptr.longValue());
}
/**
* Sets the binary data to point to a memory address. The current size is left unchanged
* and any current buffer is NOT deallocated. This is just how the 4GL implements it.
* <p>
* If the instance is {@code unknown}, then this call silently fails. It does NOT change the
* pointer value, the instance will still be {@code unknown}. This is how the 4GL does it.
*
* @param ptr
* The 64-bit value representing the pointer to the memory address. This may be
* any value (negative, 0 or a positive number). Since the 4GL has no unsigned
* 64-bit data type, it is necessary negative numbers be supported since memory
* addresses are typically unsigned values. Even though most systems do not
* allocate valid memory at address 0 (so that 0 can be used as a well known
* invalid memory address), the 4GL does allow 0 to be used (perhaps it is needed
* on some systems).
*
* @return The current instance.
*/
public memptr setPointerValue(long ptr)
{
if (isUnknown())
{
// no error here, no change to the state at all, just silently return
return this;
}
// protect this so only this context's memory addresses can be referred by the pointer
checkAddressSpace(ptr);
// all other values are accepted and saved off
pointer.addr = ptr;
return this;
}
/**
* Reports the byte order mode configured in this instance. By default (and contrary to the
* Progress documentation), this is set to 0. This will remain 0 until {@link #setByteOrder}
* is called. Since {@code setByteOrder} can only ever be called with 3 values, those
* are the only possible values that can be returned from here. The only exception to this
* is that when the instance is {@code unknown}, then {@code unknown} will be
* returned, no matter what has been previously configured.
* <p>
* The following are the possible return values:
* <p>
* <pre>
* 4GL Constant Java Constant Value
* ------------------ ------------------ ---------
* n/a NO_BYTE_ORDER 0
* HOST-BYTE-ORDER HOST_BYTE_ORDER 1
* BIG-ENDIAN BIG_ENDIAN 2
* LITTLE-ENDIAN LITTLE_ENDIAN 3
* </pre>
* <p>
* The fact that Progress returns 0 by default allows one can tell when this value has been
* explicitly configured, since it is never possible to set it back to 0 via
* {@code setByteOrder}. When set to 0 or to HOST_BYTE_ORDER, the result is the same.
* The native byte ordering of the current platform will be used.
* <p>
* This value is interesting in another way: it does not change when an instance is
* de-allocated, re-allocated, set to {@code unknown} or assigned. The configuration
* value stays with the instance across all these events and thus it is not really
* associated with a specific memory buffer, but rather with the variable instance itself.
* Don't be fooled by the fact that this returns {@code unknown} when the instance is
* {@code unknown}. The byte order is still properly set (or defaulted) and when the
* instance is no longer {@code unknown}, that value will be returned.
*
* @return The byte-order setting of the instance.
*/
public integer getByteOrder()
{
if (isUnknown())
{
return new integer();
}
return new integer(pointer.byteOrder);
}
/**
* Defines the byte ordering scheme to use when reading/writing certain data types into the
* memory buffer. The following data types change behavior depending on byte ordering:
* <p>
* <pre>
* DOUBLE
* FLOAT
* INT64
* LONG
* SHORT
* UNSIGNED-LONG
* UNSIGNED-SHORT
* </pre>
* <p>
* For each of the above listed data types, both the get and the set will honor the byte
* ordering. This means that the only data types that do NOT honor byte ordering are
* BYTE (a single byte being read or written), BYTES (a sequence of bytes) and STRING (a
* sequence of characters).
* <p>
* Any valid value set through this method is retained for the lifetime of the instance,
* even when it is set to {@code unknown} and/or de-allocated and then re-allocated.
* Interestingly, the value does not get copied to the target instance during assignment,
* so it is instance-specific. This is all compatible with how the 4GL works.
* <p>
* The following are the only possible byte order modes that can be specified:
* <p>
* <pre>
* 4GL Constant Java Constant Value
* ------------------ ------------------ ---------
* HOST-BYTE-ORDER HOST_BYTE_ORDER 1
* BIG-ENDIAN BIG_ENDIAN 2
* LITTLE-ENDIAN LITTLE_ENDIAN 3
* </pre>
* <p>
* This method returns without any changes if the instance is {@code unknown}.
*
* @param order
* The new byte order mode.
*
* @return The current instance is returned.
*/
public memptr setByteOrder(int order)
{
if (isUnknown())
{
return this;
}
if (order == HOST_BYTE_ORDER || order == BIG_ENDIAN || order == LITTLE_ENDIAN)
{
pointer.byteOrder = order;
}
else
{
ErrorManager.recordOrThrowError(9259, "Invalid value for SET-BYTE-ORDER.");
}
return this;
}
/**
* Defines the byte ordering scheme to use when reading/writing certain data types into the
* memory buffer. The following data types change behavior depending on byte ordering:
* <p>
* <pre>
* DOUBLE
* FLOAT
* INT64
* LONG
* SHORT
* UNSIGNED-LONG
* UNSIGNED-SHORT
* </pre>
* <p>
* For each of the above listed data types, both the get and the set will honor the byte
* ordering. This means that the only data types that do NOT honor byte ordering are
* BYTE (a single byte being read or written), BYTES (a sequence of bytes) and STRING (a
* sequence of characters).
* <p>
* Any valid value set through this method is retained for the lifetime of the instance,
* even when it is set to {@code unknown} and/or de-allocated and then re-allocated.
* Interestingly, the value does not get copied to the target instance during assignment,
* so it is instance-specific. This is all compatible with how the 4GL works.
* <p>
* The following are the only possible byte order modes that can be specified:
* <p>
* <pre>
* 4GL Constant Java Constant Value
* ------------------ ------------------ ---------
* HOST-BYTE-ORDER HOST_BYTE_ORDER 1
* BIG-ENDIAN BIG_ENDIAN 2
* LITTLE-ENDIAN LITTLE_ENDIAN 3
* </pre>
* <p>
* If the parameter is {@code unknown}, then the instance itself will be set to
* {@code unknown} and the byte order will NOT be changed. Subsequent allocation of
* a new buffer will retain the same byte order setting. This is how the 4GL does it.
* <p>
* This method returns without any changes if the instance is {@code unknown}.
*
* @param order
* The new byte order mode.
*/
public void setByteOrder(NumberType order)
{
if (order == null || order.isUnknown())
{
setUnknown();
}
else
{
setByteOrder(order.intValue());
}
}
/**
* Sets the array to a given length. If set to 0, this de-allocates any currently allocated
* array (if no array is allocated, this does nothing). If set to a positive number, this will
* allocate a new array if no array is allocated, otherwise this method will do nothing.
* <p>
* This method will try to emulate the allocation based on the current (-ly configured) OS.
* See {@link EnvironmentOps#isUnderWindowsFamily()}:
* <ul>
* <li>On {@code WIN32} platform will only accept positive values when allocating a new
* native memory buffer. If the buffer is already allocated and a negative value is
* passed then this is a no-op;</li>
* <li>On {@code UNIX} will accept and apparently allocate {@code abs(len)} bytes.</li>
* </ul>
* <p>
* On 32-bit Linux platforms, the 4GL "real lower bound" is -7 and not 0 as one would expect.
* Since this is an obscure and platform-specific nonsense behavior which is unlikely to be
* needed in a real application, it is not honored at this time (which leaves this code
* simpler).
*
* @param len
* New length of the array.
*
* @return The current instance.
*/
@Override
public BinaryData setLength(long len)
throws ErrorConditionException
{
if (parameter && srcWasUnknown)
{
genUninitializedError();
return this;
}
if (isUnknown() && len == 0)
{
// if 'unknown' is set and 'len' is zero, do nothing.
return this;
}
// generate an error if the size is out of bounds, this must occur before the test for
// being uninitialized
RangeCheck.check(len, LOWER_BOUND, UPPER_BOUND);
WorkArea wa = locate();
// are we already initialized?
if (isUninitialized())
{
if (len != 0 && ((wa.winOS && len > 0) || !wa.winOS))
{
pointer.addr = wa.bufMgr.allocate(Math.abs(len)); // len may be negative only on linux
// we save the size IF and ONLY IF the allocation succeeded
if (pointer.addr != 0)
{
// remember that size may be negative here, that is OK (yes, the 4GL actually
// reports the negative size on Linux, while on Windows the reported value is
// different, see the visibleLength() method)
pointer.size = len;
clearUnknown();
recordAddressSpace(pointer.addr, pointer.size);
}
}
// if len is negative on windows, nothing happens
}
else
{
// this is a de-allocation request
if (len == 0)
{
deallocate();
}
else
{
// if we are already initialized but the size was never set (this happens in the case
// that setPointerValue() was used), then this call is supposed to store the size for
// subsequent boundary checking purposes; once we set this value, we don't allow it
// to be changed again without deallocating first
if (pointer.size == 0)
{
// this can be any non-zero value that can fit in an int (we already have range
// checked this, so we know there is no loss of data);
pointer.size = len;
}
}
}
return this;
}
/**
* Reports if this instance represents an uninitialized buffer. Being uninitialized is not the
* same as being of zero length. Rather it means that the buffer was never allocated. Being
* {@code unknown} is also different from being uninitialized.
*
* @return {@code true} if this instance is uninitialized.
*/
@Override
public boolean isUninitialized()
{
return (pointer.addr == 0);
}
/**
* Creates a string representation of the instance data, since a memptr represents a 'pointer'
* it doesn't have a real string representation. If the instance represents {@code unknown}, a
* '?' will be returned, otherwise the empty string will be returned.
*
* @return The empty string or '?' if {@code unknown}.
*/
@Override
public String toString()
{
return toString(null);
}
/**
* Creates a string representation of the instance data using the given format string, since a
* memptr represents a 'pointer' it doesn't have a real string representation and the format
* string is meaningless. If the instance represents {@code unknown}, a '?' will be returned,
* otherwise the empty string will be returned.
*
* @param fmt
* The format string to use.
*
* @return The empty string or '?' if {@code unknown}.
*/
@Override
public String toString(String fmt)
{
if (isUnknown() || isUninitialized())
{
return "?";
}
return getString(1).toStringMessage();
}
/**
* Creates a string representation of the instance data in a form that is compatible with the
* {@code MESSAGE} language statement. If the instance represents {@code unknown}, a '?' will
* be returned.
* <p>
* This is equivalent to {@link #toString}.
*
* @return The empty string or '?' if {@code unknown}.
*/
@Override
public String toStringMessage()
{
return toString();
}
/**
* Creates a string representation of the instance data using the 'export' format. If the
* instance represents {@code unknown}, a '?' will be returned. Any other data is directly
* output into the string and no formatting or conversion of binary data is done.
*
* @return The binary data in the instance as a string.
*/
@Override
public String toStringExport()
{
if (isUnknown())
{
return "?";
}
return new String(readByteRange(0, findNextNull(0), false));
}
/**
* Replacement for the default object reading method. The latest state is read from the input
* source. The parent's state is read first.
*
* @param in
* The input source from which fields will be restored.
*
* @throws IOException
* In case of I/O errors.
* @throws ClassNotFoundException
* If payload can't be instantiated.
*/
@Override
public void readExternal(ObjectInput in)
throws IOException,
ClassNotFoundException
{
super.readExternal(in);
pointer.byteOrder = in.readInt();
pointer.addr = in.readLong();
pointer.size = in.readLong();
}
/**
* Replacement for the default object writing method. The latest state is written to the output
* destination. The parent's state is written first.
*
* @param out
* The output destination to which fields will be saved.
*
* @throws IOException
* In case of I/O errors.
*/
@Override
public void writeExternal(ObjectOutput out)
throws IOException
{
super.writeExternal(out);
out.writeInt(pointer.byteOrder);
out.writeLong(pointer.addr);
out.writeLong(pointer.size);
}
/**
* Write the given byte data into the data managed by this object, overwriting the data
* that are at the given location, if any.
* <p>
* This implementation:
* <ul>
* <li>ignores the {@code trim} option (i.e., any existing bytes beyond the end of the
* write are left in place, regardless of the value of {@code trim});</li>
* <li>raises an error if the write goes past the end of the existing value.</li>
* </ul>
*
* @param overlay
* Flag indicating if the OVERLAY option is used.
* @param data
* Data to be assigned to this object.
* @param offset
* Offset position (in characters) in the target large object.
* @param trim
* {@code true} to truncate any remaining data in the target large object; {@code
* false} to leave remaining data alone. Ignored.
*/
@Override
public void write(boolean overlay, byte[] data, int offset, boolean trim)
{
int dataLen;
if (data == null || (dataLen = data.length) == 0)
{
if (!overlay)
{
assign(data);
}
// else nothing to write; leave existing state alone
return;
}
else if (overlay && isUninitialized())
{
ErrorManager.recordOrThrowError(11393);
// MEMPTR target of COPY-LOB must be initialized. (11393)
return;
}
if (pointer.size < offset + dataLen)
{
ErrorManager.recordOrThrowError(11394);
// MEMPTR target of COPY-LOB is not big enough. (11394)
return;
}
// simple case: just replace existing value with data
if (!overlay || isUnknown() || (offset == 0 && pointer.size == dataLen))
{
assign(data);
return;
}
// complex case: overlay data over existing data, leave any remaining, original data intact
writeByteRange(data, offset, dataLen, false);
}
/**
* Indicate whether this object manages character data (as opposed to binary data).
*
* @return {@code false}.
*/
@Override
public boolean isCharacterData()
{
return false;
}
/**
* Return the entire contents of the current buffer as an array. This may be a copy of the
* data, depending on the subclass' implementation. DO NOT MODIFY the data that is returned,
* since it is undefined as to whether the changes will or will not be reflected in the actual
* storage.
*
* @param pos
* Starting offset position.
* @param len
* Length of range to return.
*
* @return The contents of the instance (possibly a copy). If the instance is uninitialized
* the returned value will be a 0 length array. If the instance is {@code unknown}
* or has an undefined size, then {@code null} will be returned.
*/
@Override
public byte[] asByteArray(long pos, long len)
{
byte[] copy = null;
if (!isUnknown())
{
if (isUninitialized())
{
copy = new byte[0];
}
else
{
if (pointer.size >= 1)
{
copy = locate().bufMgr.read(pointer.addr, pos, Math.min(pointer.size, len));
}
}
}
return copy;
}
/**
* Sets the state (data and unknown value) of this instance based on the state of the passed
* instance.
* <p>
* If the given instance is <code>null</code> or <code>unknown</code>, the instance will be
* set to <code>unknown</code>. If the length of the given data is 0 OR if the length of the
* an initialized buffer is indeterminate, then the instance will be reset as uninitialized
* (including deallocation of any current buffer). If the length of the array is greater than
* 0, then any current buffer will be deallocated, a new buffer of the proper size will be
* allocated and the data will be copied in. This is how the 4GL does it.
*
* @param data
* The instance from which to copy state.
*/
@Override
public void assign(BinaryData data)
{
if (BaseDataType.isProxy(data))
{
assign(data.val());
return;
}
if (data instanceof memptr)
{
if (data.isUnknown())
{
setUnknown();
return;
}
if (parameter)
{
srcWasUnknown = false;
// allow assignment from here
}
if (((memptr) data).pointer.addr == pointer.addr)
{
// same memory address, nothing to do.
return;
}
}
super.assign(data);
}
/**
* Report if the byte ordering mode of the platform is little endian.
*
* @param workArea
* The optional context to obtain endian information. If not provided, the current
* context is used.
*
* @return {@code true} if the platform natively uses little endian byte ordering and
* {@code false} if it uses big endian.
*/
protected static boolean isLittleEndianPlatform(WorkArea workArea)
{
if (workArea == null)
{
workArea = ctxt.get();
}
return workArea.nativeByteOrder == LITTLE_ENDIAN;
}
/**
* Returns the word size (the size of a pointer) for the native code as compiled. This may be
* different than the word size of the operating system in the case where 32-bit code is run
* on a 64-bit system.
*
* @return This value will be either 32 or 64 depending on how the native library was
* compiled. If there was an error in inspecting this value, -1 will be returned.
*/
protected static int compiledWordSize()
{
// can't use locate() in static context
WorkArea wa = ctxt.get();
return wa.compiledWordSize;
}
/**
* Setup this instance to act as a parameter; this shares the {@link #pointer} with the source memptr.
*
* @param src
* The source argument.
*/
protected void asParameter(memptr src)
{
this.unknown = src.unknown;
this.pointer = src.pointer;
this.parameter = true;
this.srcWasUnknown= src.isUnknown();
}
/**
* Sets the state of this instance's <code>unknown</code> flag to <code>false</code>.
*/
@Override
protected void clearUnknown()
{
super.clearUnknown();
this.pointer.unknown = false;
}
/**
* Release any resources associated with the instance.
*/
@Override
protected void deallocate()
{
if (pointer.addr != 0)
{
locate().bufMgr.deallocate(pointer.addr);
clearAddressSpace(pointer.addr, pointer.size);
pointer.addr = 0;
pointer.size = 0;
}
}
/**
* Determines if the array of binary data stored in an instance of this class must be limited
* in length.
*
* @return {@code true} if the length must be limited.
*/
@Override
protected boolean isLengthLimited()
{
return false;
}
/**
* Determines the maximum length of the array of binary data stored in an instance of this
* class.
*
* @return Always 0 (this class has no pre-determined limit on size).
*/
@Override
protected long lengthLimit()
{
return 0;
}
/**
* Determines if the array of binary data stored in an instance of this class must be
* automatically extended in length when operations write past the current length. Such
* extensions will pad with {@code null} bytes.
*
* @return {@code true} if the length must automatically extended.
*/
@Override
protected boolean isAutoExtend()
{
return false;
}
/**
* Helper to create a new byte array with the same contents as the given byte array and the
* specified length. If the given target length is smaller than the source array length, the
* output array is truncated to the specified size. If the target length is larger than the
* source array length, the output array is extended to the given size and any additional bytes
* are initialized to '\0'.
* <p>
* This is safe to use on 0 length arrays.
*
* @param len
* The output array length.
*/
@Override
protected void extendBytes(long len)
{
// nothing to do, this is only used in auto-extend sub-classes
}
/**
* Replace the entire contents of the current buffer with the given array.
* <p>
* The {@link BinaryData#unknown} flag is also cleared, only if {@code value} is not
* {@code null} and if the length is greater than 0.
*
* @param value
* The new contents.
*/
@Override
protected void replaceContents(byte[] value)
{
if (value == null)
{
setUnknown();
}
else
{
deallocate();
if (value.length > 0)
{
// allocates memory, clears unknown and saves size
setLength(value.length);
// copy the data
writeByteRange(value, 0, -1, false);
clearUnknown();
}
}
}
/**
* Return the entire contents of the current buffer as an array. This may be a copy of the
* data, depending on the subclass' implementation. DO NOT MODIFY the data that is returned,
* since it is undefined as to whether the changes will or will not be reflected in the actual
* storage.
*
* @return The contents of the instance (possibly a copy). If the instance is uninitialized
* the returned value will be a 0 length array. If the instance is {@code unknown}
* or has an undefined size, then {@code null} will be returned.
*/
@Override
public byte[] asByteArray()
{
return asByteArray(0, pointer.size);
}
/**
* The actual size of the buffer (in bytes).
*
* @return The length or 0 if uninitialized.
*/
@Override
protected long internalLength()
{
return pointer.size;
}
/**
* The the length of the buffer as reported to P4GL application.
*
* @return The length or 0 if uninitialized.
*/
@Override
protected long visibleLength()
{
if (pointer.size >= 0)
{
return pointer.size;
}
// when negative, in linux it is reported as is, but on windows the complement is returned
// (remember that size is limited to a 32-bit signed value in the 4GL, to return it as an
// unsigned value in a long, we clear the most significant 4 bytes which is equivalent to
// 0x00000000FFFFFFFF & size)
return locate().winOS ? 0x1_0000_0000L + pointer.size : pointer.size;
}
/**
* Returns the next index position into the array which is the {@code null} byte, starting at a
* given index position.
*
* @param pos
* The 0-based index into the array at which to start searching.
*
* @return The next index into the array which is the {@code null} byte or the length of the
* array if no {@code null} byte exists.
*/
@Override
protected long findNextNull(long pos)
{
// we must pass -1 as the size if we don't know what the real size is, this means the code
// will read until it finds a null byte or gets an access violation, this is how the 4GL
// does it too
long limit = (pointer.size < 1) ? -1 : pointer.size;
return locate().bufMgr.findNextNull(pointer.addr, pos, limit);
}
/**
* Read the byte at the given location. All boundary checking and error handling must have
* already been done in the caller such that this is known to be a valid access. Any
* unexpected failure may cause a platform-specific exception that terminates the process!
*
* @param pos
* The 0-based index position at which to read the byte.
*
* @return The byte at that location.
*/
@Override
protected byte readByte(long pos)
{
byte[] subset = locate().bufMgr.read(pointer.addr, pos, 1);
return subset[0];
}
/**
* Write the byte into the given location. All boundary checking and error handling must have
* already been done in the caller such that this is known to be a valid access. Any
* unexpected failure may cause a platform-specific exception that terminates the process!
*
* @param val
* The byte to write.
* @param pos
* The 0-based index position at which to read the byte.
*/
@Override
public void writeByte(byte val, long pos)
{
byte[] subset = new byte[1];
subset[0] = val;
locate().bufMgr.write(pointer.addr, pos, subset);
}
/**
* Read the bytes at the given location. All boundary checking and error handling must have
* already been done in the caller such that this is known to be a valid access. Any
* unexpected failure may cause a platform-specific exception that terminates the process!
*
* @param pos
* The 0-based index position at which to read the bytes.
* @param len
* The number of bytes to read.
* @param endian
* {@code true} to force the returned data to be ordered by the endian-ness of the
* instance.
*
* @return The byte range at that location.
*/
@Override
public byte[] readByteRange(long pos, long len, boolean endian)
{
byte[] data = locate().bufMgr.read(pointer.addr, pos, len);
// reverse the byte order if needed
if (endian && !isLittleEndian())
{
data = reverseBytes(data);
}
return data;
}
/**
* Write the bytes at the given location. All boundary checking and error handling must have
* already been done in the caller such that this is known to be a valid access. Any
* unexpected failure may cause a platform-specific exception that terminates the process!
*
* @param data
* The bytes to write.
* @param pos
* The 0-based index position at which to start writing the bytes.
* @param len
* How many bytes should be written. -1 means write everything to the end of the
* array. This is the implementation of the {@link BinaryData#writeByteRange}
* method. The len parameter was added to fix bug in the {@link raw#writeByteRange}.
* For the memptr is is intentionally ignored.
* @param endian
* {@code true} to honor the endian-ness of the instance. The {@code data}
* array MUST be ordered in little-endian order if the endian flag is on.
*/
@Override
public void writeByteRange(byte[] data, long pos, long len, boolean endian)
{
// reverse the byte order if needed
if (endian && !isLittleEndian())
{
data = reverseBytes(data);
}
if (len > 0 && len < data.length)
{
byte[] aux = new byte[(int) len];
System.arraycopy(data, 0, aux, 0, (int) len);
data = aux;
}
locate().bufMgr.write(pointer.addr, pos, data);
}
/**
* Check if this instance is undoable. For {@link memptr} case, these instances can never be
* undoable.
*
* @return Always {@code false}
*/
@Override
protected boolean isUndoable()
{
return false;
}
/**
* Copy the specified number of bytes from the source instance into the current instance at
* the given location. All boundary checking and error handling must have already been
* done in the caller such that this is known to be a valid access. Any unexpected failure
* may cause a platform-specific exception that terminates the process!
* <p>
* This method is implemented completely on the client side to avoid multiple (costly) round
* trips.
*
* @param data
* The source of data to copy.
* @param pos
* The 0-based index position in the destination at which to write the bytes.
* @param len
* The number of bytes to write.
*/
protected void copyByteRange(memptr data, long pos, long len)
{
locate().bufMgr.copy(data.pointer.addr, pointer.addr, pos, len);
}
/**
* Returns the low-level memory manager.
*
* @return see above.
*/
static LowLevelBuffer getMemoryManager()
{
return ctxt.get().bufMgr;
}
/**
* Report if the effective byte ordering mode is little endian.
*
* @return {@code true} if the instance should use little endian byte ordering and
* {@code false} if the instance should use big endian.
*/
private boolean isLittleEndian()
{
if (pointer.byteOrder == LITTLE_ENDIAN || pointer.byteOrder == BIG_ENDIAN)
{
// explicit setting should be honored
return (pointer.byteOrder == LITTLE_ENDIAN);
}
else
{
// NO_BYTE_ORDER and HOST_BYTE_ORDER both will force the native platform
// ordering to be used
return isLittleEndianPlatform(locate());
}
}
/**
* Copy the pointer details from the specified instance.
*
* @param m
* The {@link memptr} instance which needs to be duplicated.
*/
private void copyPointer(memptr m)
{
this.pointer.addr = m.pointer.addr;
this.pointer.byteOrder = m.pointer.byteOrder;
this.pointer.size = m.pointer.size;
this.pointer.unknown = m.pointer.unknown;
this.unknown = m.unknown;
if (pointer.addr != 0)
{
locate().bufMgr.incrementReference(pointer.addr);
}
}
/**
* Resolve the context-local {@link #workArea} variable (if not yet resolved) and return it.
*
* @return The {@link #workArea}.
*/
private WorkArea locate()
{
if (workArea == null)
{
workArea = ctxt.get();
}
return workArea;
}
/**
* When memptr is on server-side, allocated address spaces are recorded for each context, to prohibit
* cross-context usage of pointer addresses.
* <p>
* This method raises an ERROR condition if the context tries to {@link #setPointerValue set a pointer} to
* an address which was not allocated in this context.
*
* @param ptr
* The memory address.
*/
private void checkAddressSpace(long ptr)
{
WorkArea wa = locate();
if (ptr == 0 || !wa.serverSide)
{
return;
}
if (wa.addressSpace.contains(ptr))
{
return;
}
String msg = "Memory violation: trying to SET-POINTER-VALUE to an address unallocated in this context.";
LOG.log(Level.SEVERE, msg);
ErrorManager.recordOrThrowError(-100, msg, false);
}
/**
* When memptr is on server-side, allocated address spaces are recorded for each context, to prohibit
* cross-context usage of pointer addresses.
* <p>
* This records the allocated address space in this context.
*
* @param ptr
* The memory address.
* @param size
* The allocated size.
*/
private void recordAddressSpace(long ptr, long size)
{
WorkArea wa = locate();
if (!wa.serverSide)
{
return;
}
size = Math.abs(size);
wa.addressSpace.addRange(ptr, ptr + size);
}
/**
* When memptr is on server-side, allocated address spaces are recorded for each context, to prohibit
* cross-context usage of pointer addresses.
* <p>
* This clears the allocated address space in this context.
*
* @param ptr
* The memory address.
* @param size
* The allocated size.
*/
private void clearAddressSpace(long ptr, long size)
{
WorkArea wa = locate();
if (!wa.serverSide)
{
return;
}
size = Math.abs(size);
try
{
// create a bitmap with the address space being cleared
wa.cleaner.addRange(ptr, ptr + size);
// remove it from the allowed address space
wa.addressSpace.andNot(wa.cleaner);
wa.cleaner.clear();
}
catch (NegativeArraySizeException ex)
{
if (LOG.isLoggable(Level.SEVERE))
{
LOG.log(Level.SEVERE,
"Exception while clearing address space [" + ptr + ":" + (ptr + size) + "] at " + ptr +
" with size " + size,
ex);
}
}
}
/**
* Container for context-local data.
*/
private static class WorkArea
{
/** Flag indicating if the memptr is handled on server-side. */
private boolean serverSide;
/** Remote proxy for access and management of memory buffers. */
private LowLevelBuffer bufMgr = null;
/** Stores the byte order of the native platform being used. */
private int nativeByteOrder = 0;
/** Stores the word size of the compiled code in the client. */
private int compiledWordSize = -1;
/** Stores the OS information for this context. */
private boolean winOS = false;
/** The allocated address space in this context. */
private final Roaring64Bitmap addressSpace = new Roaring64Bitmap();
/**
* A helper instance to clean the {@link #addressSpace}, when a pointer is
* {@link memptr#deallocate() deallocated}.
*/
private final Roaring64Bitmap cleaner = new Roaring64Bitmap();
}
/**
* A data structure with the details about the 'pointer' referenced by this instance.
*/
private static class memptrPointer
{
/** The byte order used for this instance. */
private int byteOrder;
/** The actual platform-specific pointer to our memory region (on the client). */
private long addr;
/** Stores the size that was last set for querying and boundary checking. */
private long size;
/** Flag indicating if the instance is unknown. */
private boolean unknown = false;
/**
* Initialize this structure with the specified details.
*
* @param byteOrder
* The byte order used for this instance.
* @param addr
* The actual platform-specific pointer to our memory region (on the client).
* @param size
* Stores the size that was last set for querying and boundary checking.
*/
public memptrPointer(int byteOrder, long addr, long size)
{
this.byteOrder = byteOrder;
this.addr = addr;
this.size = size;
}
}
}