I am building a fairly large Adobe AIR application that targets iOS
The following exception is appearing in my file loading class, but only on iOS (PC works fine):
[Fault] exception, information=TypeError: Error #1009: Cannot access a property or method of a null object reference.
This occurs during the "onComplete" call in the following function. It happened suddenly and there were no recent code changes that would have affected that area of the application.
Files are loaded at the beginning of each "scene". Each scene uses the loader to load several files (asset lists, layouts, etc.). This exception only happens on the second file of the third scene to be loaded. The invocation of file loading is handled by a manager and is identical for all scenes. The data for the erroneous load is not corrupt and is successfully loaded before the exception on the onComplete call.
private function onFileLoaded( evt:Event ):void
{
trace( "onFileLoaded" );
var fs:FileStream = evt.currentTarget as FileStream;
var id:uint = m_fileStreamToId[ fs ];
var onComplete:Function = m_fileStreamToCallback[ fs ];
var retVal:Object = null;
var success:Boolean = false;
try
{
retVal = JSON.parse( fs.readUTFBytes( fs.bytesAvailable ) );
success = true;
}
catch ( error:Error )
{
trace( error );
retVal = null;
}
fs.close();
delete m_fileStreamToId[ fs ];
delete m_fileStreamToCallback[ fs ];
onComplete( id, retVal, success );
}
onFileLoaded is only called by:
private function internalAsyncLoadFileFromDisk( id:uint, filePath:File, onComplete:Function ):void
{
var fs:FileStream = new FileStream();
fs.addEventListener( Event.COMPLETE, onFileLoaded );
fs.addEventListener( IOErrorEvent.IO_ERROR, onIoError );
m_fileStreamToId[ fs ] = id;
m_fileStreamToCallback[ fs ] = onComplete;
fs.openAsync( filePath, FileMode.READ );
}
and the onComplete function argument is always a local private function.
When the debugger announces the null object reference and points to onComplete, it should be noted that onComplete is not null and the class encapsulating the functions has not been disposed of. Also, I do not see the "onFileLoaded" printed in the trace.
The m_fileStreamToCallback and m_fileStreamToId were created to remove the use of nested functions during file loading. I had experienced null object exceptions when attempting to access member variables as well as cross-scope local variables from within nested anonymous functions on iOS (even though it always works fine on PC).
Lastly, when I try to step into the file loading class with the debugger before the erroneous call, the debugger will always throw an internal exception and disconnect from the application. It only throws this exception before the erroneous call. The debugger is able to enter it successfully for all previous loads. It is also able to break inside the erroneous function when the null object error triggers. It simply cannot enter the class by stepping into it.
Environment details:
Adobe AIR: 14
Apache Flex SDK: 4.12.1
Editor: FlashDevelop
Build system: Ant
Build system OS: Windows 7 x64
Target Device: iPad 4
Target OS: iOS 7
Update 1
The following is the public interface and the onComplete function. So cleanupAndFinalCallback is the function that is supposedly null. I will also add that I am able to successfully enter this scene from another path through the application. If I enter via multiplayer, it crashes when loading the layout. When I enter from single player it does not. Both paths are loading the same file from the disk.
//! Async load the json file from the disk.
//! #param onComplete function( functorArgs:*, retVal:Object, success:Boolean )
public function asyncLoadFileFromDisk( filePath:File, onComplete:CallbackFunctor ):void
{
var newId:uint = m_idGenerator.obtainId();
m_idToCallback[ newId ] = onComplete;
internalAsyncLoadFileFromDisk( newId, filePath, cleanupAndFinalCallback );
}
private function cleanupAndFinalCallback( id:uint, retVal:Object, success:Boolean ):void
{
var onComplete:CallbackFunctor = m_idToCallback[ id ];
delete m_idToCallback[ id ];
m_idGenerator.releaseId( id );
onComplete.executeWithArgs( retVal, success );
}
Update 2
Stepping trough the app near the error causes debugger to crash. However, if I set breakpoints along the execution path I can jump (F5) through execution near the error. So, as I stated above, onComplete is not null as is reported by the error. I was able to execute it and pass further along in execution. At some point, the debugger throws the null reference error and snaps back to that point in the code. I feel there may be something funny going on with the stack.
I suspected that there may have been some issue with the stack. So, immediately after the file was loaded and the scene transition occurred, I used a delayed call of 1s to make sure that the load call was a real asynchronous call.
It turned out that the problem was with the debugger. I still received the "null object reference" error. However, this time, it reported it in a swc that was used in my app. It appears that this report is correct.
When I remove the delayed call, the program reverts to reporting the incorrect error.
Related
With the Chrome 71 changes that start audiocontext's in a suspended state, we need to resume the audio context after a user interaction. We are handling this by displaying a red banner at the top of our website saying we need you to grant us permission to enable calling with a button. The button meets the user engagement so we can resume the audio context.
The issue I am having is that the device which I have already called device.setup() on does not have the audioContext available when the user clicks the button.
I should be able to access the audioContext through device.audio.audioContext and call the resume() method on it, but what I get instead is undefined.
Currently as a workaround I am calling device.audio._audioContext.resume() and this is never undefined and works. But it seems to me like there is some bug where the public accessor for audioContext is not being defined in the twilio-client library. Or am I doing something wrong here?
In the es5 compiled code I see the following in the device.js file I see
Object.defineProperty(Device, "audioContext", {
/**
* The AudioContext to be used by {#link Device} instances.
*/
get: function () {
return Device._audioContext;
},
enumerable: true,
configurable: true
});
I get undefined when calling this. However, the device.audio property is set in this bit of code, where it passes Device.audioContext as an option:
this.audio = new (this.options.AudioHelper || AudioHelper)
(this._updateSinkIds, this._updateInputStream, getUserMedia, {
audioContext: Device.audioContext,
enabledSounds: this._enabledSounds,
logEnabled: !!this.options.debug,
logWarnings: !!this.options.warnings,
});
And then in the audiohelper.js file the constructor calls:
Object.defineProperties(this, {
_audioContext: {
value: audioContext
},
// more stuff
},
so it seems like when the device is initially created the Device.audioContext is in existence and is passed through to audio._audioContext, but later Device.audioContext loses its reference to the audio context.
Twilio developer evangelist here.
This may be a failure of documentation or communication, but I think I know what is going on now.
Device.audioContext is a public method on the Device class. As you discovered in the code it is defined here:
Object.defineProperty(Device, "audioContext", {
/**
* The AudioContext to be used by {#link Device} instances.
*/
get: function () {
return Device._audioContext;
},
enumerable: true,
configurable: true
});
Note that this defines the audioContext property on the Device object itself.
But there is no definition for audioContext on instances of Device. This is why your code in the gist you posted fails when you try to call state.device.audioContext.
As you point out, the audio object on the device object is created using Device.audioContext. So, you can actually drop using the pseudo-private device.audio._audioContext and just use the public Device.audioContext when you want to resume().
I would rewrite to:
resumeTwilioDevice = async () => {
Twilio.Device.audioContext.resume();
};
I am taking my first steps in Dart today, and the first thing I am not sure about how to proceed is how to test whether a file that is passed as an argument to a CLI tool I'm writing is writable.
So the idea is that I have a tool that accepts an input directory and an output filename. What it does is parsing some files in the input directory, compiles the data into a meaningful JSON config and saves it in the output file.
However, before doing anything, I want to run a sanity check to see that the given output file argument can actually be used as a writable file.
The way I decided to solve this is by opening the file for Append in a try-catch block:
try {
new File(output).writeAsStringSync('', mode: FileMode.APPEND, flush: true);
} on FileSystemException catch(e) {
// do something
}
However, I don't like this solution. Mainly that it creates a file if it doesn't already exist. Also, I don't see why I should write anything into a file when I just want to know whether it is writable or not.
What is the right way to do it in Dart?
You can use file.statSync().mode or file.statSync().modeString(). See FileStat.
This is actually quite hard to do reliably in any language. As Eiko points out, knowing the file permissions is only half the story, since the current user, group and process determines how those permissions apply.
Some edge cases that can occur are:
The file is writable when checked, but becomes unwritable by the time the writing needs to happen (e.g. another process changed the permissions).
The file is not writable when checked, but becomes writable by the time the writing needs to happen.
The file is write-only: it exists and is not readable, but can be written to.
The file doesn't exist and the user/process is not permitted to create a new file in that directory.
The file system has been mounted in read-only mode.
The parent directory/directories don't exist.
So anything you write may produce false positives or false negatives.
Your method of appending nothing is a good simple test. It can be made more complicated to address some of the issues, but there will always be cases where the answer is not what you might want.
For example, if you don't like creating the file before the actual writing, test if it exists first:
bool isWritable;
final f = File(filename);
if (f.existsSync()) {
try {
// try appending nothing
f.writeAsStringSync('', mode: FileMode.APPEND, flush: true);
isWritable = true;
} on FileSystemException {
isWritable = false;
}
} else {
isWritable = ???; // do you prefer false positive or false negative
// check if the parent directory exists?
}
// isWritable now, but might not be by the time writing happens
Or delete it after testing:
bool isWritable;
final f = File(filename);
final didExist = f.existsSync();
try {
// try appending nothing
f.writeAsStringSync('', mode: FileMode.APPEND, flush: true);
isWritable = true;
if (didExist) {
f.deleteSync();
}
} on FileSystemException {
isWritable = false;
}
// isWritable now, but might not be by the time writing happens
Dart introduces an extra complication, with asynchronous code.
If using the openWrite method. It opens a stream, so any problems writing to the file are not raised when the file is opened. They occur later when using the stream or closing it, which can be far away from the file opening code where you want it detected. Or worse, it occurs in a different zone and cannot be caught.
One useful trick there is to open it twice. The first is used to detect if the file is writable when it is closed. The second is to obtain the stream that will be used for writing.
try {
final f = File(filename);
f.parent.createSync(recursive: true); // create parent(s) if they don't exist
final tmp = f.openWrite(mode: FileMode.append);
await tmp.flush();
await tmp.close(); // errors from opening will be thrown at this point
// Open it again
sinkForWritingToTheFile = f.openWrite(mode: FileMode.append);
} on FileSystemException catch (e) {
// exception from `close` will be caught here
// exception from the second `openWrite` cannot be caught here
...
}
I'm making a lua script for DeSmuMe, a Nintendo Ds Emulator. I wanted to use this command to save a value on save state: savestate.registersave(function() return frame end)
But the emulator gives me this error: :50: attempt to call field 'registersave' (a nil value). Why? How can i solve this error?
Have you seen this function in any working script?
I checked the source code of DeSmuMe on Sourceforge.
In a file called lua-engine.cpp (last changed 2015-09-15, so after the latest release) I found this:
static const struct luaL_reg statelib [] =
{
{"create", state_create},
{"save", state_save},
{"load", state_load},
#ifndef PUBLIC_RELEASE
{"verify", state_verify}, // for desync catching
#endif
// TODO
//{"loadscriptdata", state_loadscriptdata},
//{"savescriptdata", state_savescriptdata},
//{"registersave", state_registersave},
//{"registerload", state_registerload},
{NULL, NULL}
};
So obviously savestate.registersave hasn't made it into the Lua interface yet. So you can't use it.
As far as I looked, there's no answered question about QTP's Ext. sdk on stackoverflow (and almost anywhere else on the net; there isn't even a appropriated tag for it...), so I'm aware it's unlikely I get my problem solved by asking , but whatever, it worth trying.
Anyway, before I lose the attention of anyone who never heard or used the Ext. sdk, maybe I will have more luck asking you to help me figure out how to locate the error log file QTP produces at run-time. I know such a file exists in the new UFT 11.5 version, but I couldn't locate it in QTP 10 or 11 (For the record, I don't talk about QTP's Log Tracking feature, but about the "meta" error log of errors/exceptions produced by QTP itself).
Now for the question:
I'm developing an extension for QTP to support native record and run tests on my application.
I'm currently able to import an object repository, and write test steps using The COM object testing agent I developed.
Problem started when I was trying to implement the IRecordable interface; I'm getting the IRecorder object from qtp, and even able to use it as ISuppressor object to exclude redundant steps from being recorded, but all my attempts to record a step (that is, to add new recorded objects to the repository, and add steps to the test) simply failed.
This is the code that I'm using:
public class MyTestingAgent :
AutInterface.ITestable,
AutInterface.IRecordable
{
QTPInterface.IRecorder recorder;
...
public void AutInterface.IRecordable.BeginRecording(object recorder)
{
IRecordSuppressor recordSuppressor = recorder as IRecordSuppressor;
recordSuppressor.Suppress(MyTestingAgentId,
"<Suppress><Item type=\"HWND\" value=\"[#HWND]\" /></Suppress>".Replace("[#HWND]", getMyAppHWND().ToString()));
this.recorder = recorder as QTPInterface.IRecorder;
...
}
public void recordNewObjStep(string parentName, string objName, string method, Object[] arguments)
{
object[] objectHyrarchy = new object[] { findObjectId(objName), findObjectId(parentName) };
string externalParent = null;
string appDescriptionXml = getDescriptionXml(parentName, objName);
try
{
recorder.Record(MyTestingAgentId, objectHyrarchy , appDescriptionXml, externalParent, method, arguments);
Trace.TraceInformation("Record successfully done.");
}
catch (Exception e)
{
Trace.TraceError("TEAAgent.recordSTElement: " + e.ToString());
}
}
...
}
I'm pretty sure all the arguments I send with the call to Record() are accurate. getDescriptionXml() and findObjectId() are used in different cases in the code, and works fine, the method name and argument are correct.
The annoying thing is that the call to Record doesn't even throw exception, and I get "Record successfully done." in the trace log. Needless to say no new object is created in the repository, and no step is added to the test.
As I can't debug QTP, I'm pretty much in the dark with what I'm doing wrong. That's why I'm asking for help with finding QTP's log file, or any other approach that might shed some light on the subject.
For QTP 11 you can turn on the logs by going to QTP's bin directory and running ClientLogs.exe.
Specifically for TEA extensibility do the following.
select the QTP node from the list on the left
find the LogCatPackTEA from the Available Categories list
Click the > button to move it to Selected Categories
Change TEAs level to Debug2 by selecting the category and changing the level
Click OK and run QTP
The logs will show up as QTP.log in the diretory specified in Path:
I'm curious on what the problem you're facing is, please update if you find the cause.
I'm in the process of migrating a struts 2.2.3.1 portlet application (jsr168) to the new version, 2.3.3. For the most part everything still works the same, but there was one obvious problem.
I'm using a package.properties resource bundle for things like titles and labels in my jsp's. When I first hit an action, the messages are retrieved from the resource bundle and displayed as expected. When I make a second request to an action though, the message keys are displayed, which typically means the resource was not found.
(The main difference between the first time hitting an action and any subsequent request seems to be that on the first request ONLY the event portlet phase is processed, and not the render portlet phase. Any follow up requests both phases are used.)
It took me quite a while to figure out what is going on, but here is roughly what's happening:
I started out by stepping through org.apache.struts2.components.Text. This class is used when a struts text tag is encountered in a jsp.
The end() method uses the static getText() method from the TextProviderHelper. This is where I found the root of the problem:
for (Object o : stack.getRoot()) {
if (o instanceof TextProvider) {
tp = (TextProvider) o;
msg = tp.getText(key, null, args, stack);
break;
}
}
The above code iterates through the value stack until it finds a TextProvider. Because ActionSupport implements TextProvider, and because struts tries to put the action at or near the top of the stack, the action is usually the provider.
Here's the objects in the value stack at different times for both versions:
Version 2.2.3.1
First request value stack
-->TestPortletAction
DefaultTextProvider
Second request value stack
-->TestPortletAction
DefaultTextProvider
DirectRenderFromEventAction
DefaultTextProvider
Version 2.3.3
First request value stack
-->TestPortletAction
DefaultTextProvider
Second request value stack
DirectRenderFromEventAction
DefaultTextProvider
-->TestPortletAction
DefaultTextProvider
So, for some reason, in the new version the action class is not at the top of the stack after the render phase is processed. This means that the TextProviderHelper uses the DefaultTextProvider, which does try to locate the resource bundle... but is never successful in doing so.
I did some investigation into how that DefaultTextProvider gets pushed into the stack:
Jsr168Dispatcher.serviceAction() makes the call to the ActionProxyFactory to create the action proxy. Just before returning the proxy a call is made to prepare(), which in turn makes a call to the DefaultActionInvocation's init method. The init calls createContextMap() which creates the stack
stack = valueStackFactory.createValueStack();
The constructor used:
protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, boolean allowStaticAccess) {
setRoot(xworkConverter, accessor, new CompoundRoot(), allowStaticAccess);
push(prov);
}
Aaaaaand that's about as far as I have got on the problem so far. The questions that remain are:
How and where does the second DefaultTextProvider get pushed onto the stack? (And is there a real reason to have two of them?)
How and where does the DirectRenderFromEventAction get pushed onto the stack?
What change in the new version is causing this?
Is this a bug or intended behavior?
A possible solution (that does fix it) is to explicitly call the action's getText method using ognl, but I don't see this as ideal, and it does mean changing a bunch of jsp files.
<s:property value = "%{getText('some.key')}"/>
A sample project that demonstrates the problem can be found here (It's a maven project):
http://new-value-stack-order.googlecode.com/svn
I'm using tomcat 6.0.25 with pluto 1.1.7 as my development environment.
Figured it out. The answer lies in the PortletStateInterceptor
2.2.3.1
#SuppressWarnings("unchecked")
private void restoreStack(ActionInvocation invocation) {
RenderRequest request = (RenderRequest) invocation.getInvocationContext().get(REQUEST);
if (StringUtils.isNotEmpty(request.getParameter(EVENT_ACTION))) {
if(!isProperPrg(invocation)) {
if (LOG.isDebugEnabled()) LOG.debug("Restoring value stack from event phase");
ValueStack oldStack = (ValueStack) invocation.getInvocationContext().getSession().get(
STACK_FROM_EVENT_PHASE);
if (oldStack != null) {
CompoundRoot oldRoot = oldStack.getRoot();
ValueStack currentStack = invocation.getStack();
CompoundRoot root = currentStack.getRoot();
root.addAll(0, oldRoot);
if (LOG.isDebugEnabled()) LOG.debug("Restored stack");
}
}
else {
if (LOG.isDebugEnabled()) LOG.debug("Won't restore stack from event phase since it's a proper PRG request");
}
}
}
2.3.3
#SuppressWarnings("unchecked")
private void restoreStack(ActionInvocation invocation) {
RenderRequest request = (RenderRequest) invocation.getInvocationContext().get(REQUEST);
if (StringUtils.isNotEmpty(request.getParameter(EVENT_ACTION))) {
if(!isProperPrg(invocation)) {
if (LOG.isDebugEnabled()) LOG.debug("Restoring value stack from event phase");
ValueStack oldStack = (ValueStack) invocation.getInvocationContext().getSession().get(
STACK_FROM_EVENT_PHASE);
if (oldStack != null) {
CompoundRoot oldRoot = oldStack.getRoot();
ValueStack currentStack = invocation.getStack();
CompoundRoot root = currentStack.getRoot();
root.addAll(oldRoot);
if (LOG.isDebugEnabled()) LOG.debug("Restored stack");
}
}
else {
if (LOG.isDebugEnabled()) LOG.debug("Won't restore stack from event phase since it's a proper PRG request");
}
}
}
Note the root.addAll(). In the old version it adds the old stack to the beginning of the current stack. In the new version it adds it to the end.
This was actually an old bug fixed in 2.1.3. Creating a new issue in Jira.