Bug 944164 - Implement proper redirection with ref counted underlying files, r=terrence

MozReview-Commit-ID: KVjZ0WXl9E6
This commit is contained in:
Steve Fink 2015-06-10 11:21:03 -07:00
Родитель 094469068b
Коммит c715a05b0c
4 изменённых файлов: 340 добавлений и 84 удалений

Просмотреть файл

@ -42,6 +42,11 @@
# include <libgen.h>
#endif
using js::shell::RCFile;
static RCFile** gErrFilePtr = nullptr;
static RCFile** gOutFilePtr = nullptr;
namespace js {
namespace shell {
@ -306,53 +311,214 @@ osfile_writeTypedArrayToFile(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static bool
Redirect(JSContext* cx, FILE* fp, HandleString relFilename)
/* static */ RCFile*
RCFile::create(JSContext* cx, const char* filename, const char* mode)
{
FILE* fp = fopen(filename, mode);
if (!fp)
return nullptr;
RCFile* file = cx->new_<RCFile>(fp);
if (!file) {
fclose(fp);
return nullptr;
}
return file;
}
void
RCFile::close()
{
if (fp)
fclose(fp);
fp = nullptr;
}
bool
RCFile::release()
{
if (--numRefs)
return false;
this->close();
return true;
}
class FileObject : public JSObject {
enum : uint32_t {
FILE_SLOT = 0,
NUM_SLOTS
};
public:
static const js::Class class_;
static FileObject* create(JSContext* cx, RCFile* file) {
JSObject* obj = js::NewObjectWithClassProto(cx, &class_, nullptr);
if (!obj)
return nullptr;
FileObject* fileObj = &obj->as<FileObject>();
fileObj->setRCFile(file);
file->acquire();
return fileObj;
}
static void finalize(FreeOp* fop, JSObject* obj) {
FileObject* fileObj = &obj->as<FileObject>();
RCFile* file = fileObj->rcFile();
if (file->release()) {
fileObj->setRCFile(nullptr);
fop->delete_(file);
}
}
bool isOpen() {
RCFile* file = rcFile();
return file && file->isOpen();
}
void close() {
if (!isOpen())
return;
rcFile()->close();
}
RCFile* rcFile() {
return reinterpret_cast<RCFile*>(js::GetReservedSlot(this, FILE_SLOT).toPrivate());
}
private:
void setRCFile(RCFile* file) {
js::SetReservedSlot(this, FILE_SLOT, PrivateValue(file));
}
};
const js::Class FileObject::class_ = {
"File",
JSCLASS_HAS_RESERVED_SLOTS(FileObject::NUM_SLOTS),
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
FileObject::finalize, /* finalize */
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
nullptr /* trace */
};
static FileObject*
redirect(JSContext* cx, HandleString relFilename, RCFile** globalFile)
{
RootedString filename(cx, ResolvePath(cx, relFilename, RootRelative));
if (!filename)
return false;
return nullptr;
JSAutoByteString filenameABS(cx, filename);
if (!filenameABS)
return false;
if (freopen(filenameABS.ptr(), "wb", fp) == nullptr) {
return nullptr;
RCFile* file = RCFile::create(cx, filenameABS.ptr(), "wb");
if (!file) {
JS_ReportError(cx, "cannot redirect to %s: %s", filenameABS.ptr(), strerror(errno));
return nullptr;
}
// Grant the global gOutFile ownership of the new file, release ownership
// of its old file, and return a FileObject owning the old file.
file->acquire(); // Global owner of new file
FileObject* fileObj = FileObject::create(cx, *globalFile); // Newly created owner of old file
if (!fileObj) {
file->release();
return nullptr;
}
(*globalFile)->release(); // Release (global) ownership of old file.
*globalFile = file;
return fileObj;
}
static bool
Redirect(JSContext* cx, const CallArgs& args, RCFile** outFile)
{
if (args.length() > 1) {
JS_ReportErrorNumber(cx, js::shell::my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "redirect");
return false;
}
RCFile* oldFile = *outFile;
RootedObject oldFileObj(cx, FileObject::create(cx, oldFile));
if (!oldFileObj)
return false;
if (args.get(0).isUndefined()) {
args.rval().setObject(*oldFileObj);
return true;
}
if (args[0].isObject()) {
RootedObject fileObj(cx, js::CheckedUnwrap(&args[0].toObject()));
if (!fileObj)
return false;
if (fileObj->is<FileObject>()) {
// Passed in a FileObject. Create a FileObject for the previous
// global file, and set the global file to the passed-in one.
*outFile = fileObj->as<FileObject>().rcFile();
(*outFile)->acquire();
oldFile->release();
args.rval().setObject(*oldFileObj);
return true;
}
}
RootedString filename(cx, JS::ToString(cx, args[0]));
if (!filename)
return false;
if (!redirect(cx, filename, outFile))
return false;
args.rval().setObject(*oldFileObj);
return true;
}
static bool
osfile_redirect(JSContext* cx, unsigned argc, Value* vp)
{
osfile_redirectOutput(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return Redirect(cx, args, gOutFilePtr);
}
static bool
osfile_redirectError(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return Redirect(cx, args, gErrFilePtr);
}
static bool
osfile_close(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1 || args.length() > 2) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "redirect");
Rooted<FileObject*> fileObj(cx);
if (args.get(0).isObject()) {
JSObject *obj = js::CheckedUnwrap(&args[0].toObject());
if (obj->is<FileObject>())
fileObj = &obj->as<FileObject>();
}
if (!fileObj) {
JS_ReportErrorNumber(cx, js::shell::my_GetErrorMessage, nullptr,
JSSMSG_INVALID_ARGS, "close");
return false;
}
if (args[0].isString() || args[0].isNull()) {
RootedString stdoutPath(cx);
if (!args[0].isNull()) {
stdoutPath = args[0].toString();
if (!stdoutPath)
return false;
}
if (!Redirect(cx, stdout, stdoutPath))
return false;
}
if (args.length() > 1 && (args[1].isString() || args[1].isNull())) {
RootedString stderrPath(cx);
if (!args[1].isNull()) {
stderrPath = args[1].toString();
if (!stderrPath)
return false;
}
if (!Redirect(cx, stderr, stderrPath))
return false;
}
fileObj->close();
args.rval().setUndefined();
return true;
@ -377,11 +543,19 @@ static const JSFunctionSpecWithHelp osfile_unsafe_functions[] = {
"writeTypedArrayToFile(filename, data)",
" Write the contents of a typed array to the named file."),
JS_FN_HELP("redirect", osfile_redirect, 2, 0,
"redirect(stdoutFilename[, stderrFilename])",
" Redirect stdout and/or stderr to the named file. Pass undefined to avoid\n"
" redirecting, or null to discard the output. Filenames are relative to the\n"
" current working directory."),
JS_FN_HELP("redirect", osfile_redirectOutput, 1, 0,
"redirect([path-or-object])",
" Redirect print() output to the named file.\n"
" Return an opaque object representing the previous destination, which\n"
" may be passed into redirect() later to restore the output."),
JS_FN_HELP("redirectErr", osfile_redirectError, 1, 0,
"redirectErr([path-or-object])",
" Same as redirect(), but for printErr"),
JS_FN_HELP("close", osfile_close, 1, 0,
"close(object)",
" Close the file returned by an earlier redirect call."),
JS_FS_HELP_END
};
@ -732,7 +906,9 @@ static const JSFunctionSpecWithHelp os_functions[] = {
};
bool
DefineOS(JSContext* cx, HandleObject global, bool fuzzingSafe)
DefineOS(JSContext* cx, HandleObject global,
bool fuzzingSafe,
RCFile** shellOut, RCFile** shellErr)
{
RootedObject obj(cx, JS_NewPlainObject(cx));
if (!obj || !JS_DefineProperty(cx, global, "os", obj, 0))
@ -771,6 +947,9 @@ DefineOS(JSContext* cx, HandleObject global, bool fuzzingSafe)
if (!GenerateInterfaceHelp(cx, obj, "os"))
return false;
gOutFilePtr = shellOut;
gErrFilePtr = shellErr;
// For backwards compatibility, expose various os.file.* functions as
// direct methods on the global.
RootedValue val(cx);
@ -782,7 +961,8 @@ DefineOS(JSContext* cx, HandleObject global, bool fuzzingSafe)
{ "readFile", "read" },
{ "readFile", "snarf" },
{ "readRelativeToScript", "readRelativeToScript" },
{ "redirect", "redirect" }
{ "redirect", "redirect" },
{ "redirectErr", "redirectErr" }
};
for (auto pair : osfile_exports) {

Просмотреть файл

@ -14,9 +14,13 @@
namespace js {
namespace shell {
struct RCFile;
/* Define an os object on the given global object. */
bool
DefineOS(JSContext* cx, JS::HandleObject global, bool fuzzingSafe);
DefineOS(JSContext* cx, JS::HandleObject global,
bool fuzzingSafe,
RCFile** shellOut, RCFile** shellErr);
enum PathResolutionMode {
RootRelative,

Просмотреть файл

@ -183,8 +183,8 @@ static char gZealStr[128];
static bool printTiming = false;
static const char* jsCacheDir = nullptr;
static const char* jsCacheAsmJSPath = nullptr;
static FILE* gErrFile = nullptr;
static FILE* gOutFile = nullptr;
static RCFile* gErrFile = nullptr;
static RCFile* gOutFile = nullptr;
static bool reportWarnings = true;
static bool compileOnly = false;
static bool fuzzingSafe = false;
@ -349,9 +349,9 @@ GetLine(FILE* file, const char * prompt)
#endif
size_t len = 0;
if (*prompt != '\0') {
fprintf(gOutFile, "%s", prompt);
fflush(gOutFile);
if (*prompt != '\0' && gOutFile->isOpen()) {
fprintf(gOutFile->fp, "%s", prompt);
fflush(gOutFile->fp);
}
size_t size = 80;
@ -618,7 +618,7 @@ RunModule(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
static bool
EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
int lineno, bool compileOnly, FILE* out)
int lineno, bool compileOnly)
{
// Eval.
JS::CompileOptions options(cx);
@ -635,7 +635,7 @@ EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
if (!JS_ExecuteScript(cx, script, &result))
return false;
if (!result.isUndefined()) {
if (!result.isUndefined() && gOutFile->isOpen()) {
// Print.
RootedString str(cx);
str = JS_ValueToSource(cx, result);
@ -645,14 +645,14 @@ EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
char* utf8chars = JS_EncodeStringToUTF8(cx, str);
if (!utf8chars)
return false;
fprintf(out, "%s\n", utf8chars);
fprintf(gOutFile->fp, "%s\n", utf8chars);
JS_free(cx, utf8chars);
}
return true;
}
static void
ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly)
ReadEvalPrintLoop(JSContext* cx, FILE* in, bool compileOnly)
{
ShellRuntime* sr = GetShellRuntime(cx);
int lineno = 1;
@ -697,9 +697,7 @@ ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly)
if (hitEOF && buffer.empty())
break;
if (!EvalAndPrint(cx, buffer.begin(), buffer.length(), startline, compileOnly,
out))
{
if (!EvalAndPrint(cx, buffer.begin(), buffer.length(), startline, compileOnly)) {
// Catch the error, report it, and keep going.
JS_ReportPendingException(cx);
}
@ -707,7 +705,7 @@ ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly)
// without further intervention. This call cleans up the global scope,
// setting uninitialized lexicals to undefined so that they may still
// be used. This behavior is _only_ acceptable in the context of the repl.
if (JS::ForceLexicalInitialization(cx, globalLexical)) {
if (JS::ForceLexicalInitialization(cx, globalLexical) && gErrFile->isOpen()) {
fputs("Warning: According to the standard, after the above exception,\n"
"Warning: the global bindings should be permanently uninitialized.\n"
"Warning: We have non-standard-ly initialized them to `undefined`"
@ -716,7 +714,8 @@ ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly)
}
} while (!hitEOF && !sr->quitting);
fprintf(out, "\n");
if (gOutFile->isOpen())
fprintf(gOutFile->fp, "\n");
}
enum FileKind
@ -750,7 +749,7 @@ Process(JSContext* cx, const char* filename, bool forceTTY, FileKind kind = File
} else {
// It's an interactive filehandle; drop into read-eval-print loop.
MOZ_ASSERT(kind == FileScript);
ReadEvalPrintLoop(cx, file, gOutFile, compileOnly);
ReadEvalPrintLoop(cx, file, compileOnly);
}
}
@ -1648,15 +1647,20 @@ PutStr(JSContext* cx, unsigned argc, Value* vp)
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 0) {
if (!gOutFile->isOpen()) {
JS_ReportError(cx, "output file is closed");
return false;
}
RootedString str(cx, JS::ToString(cx, args[0]));
if (!str)
return false;
char* bytes = JS_EncodeStringToUTF8(cx, str);
if (!bytes)
return false;
fputs(bytes, gOutFile);
fputs(bytes, gOutFile->fp);
JS_free(cx, bytes);
fflush(gOutFile);
fflush(gOutFile->fp);
}
args.rval().setUndefined();
@ -1673,8 +1677,13 @@ Now(JSContext* cx, unsigned argc, Value* vp)
}
static bool
PrintInternal(JSContext* cx, const CallArgs& args, FILE* file)
PrintInternal(JSContext* cx, const CallArgs& args, RCFile* file)
{
if (!file->isOpen()) {
JS_ReportError(cx, "output file is closed");
return false;
}
for (unsigned i = 0; i < args.length(); i++) {
RootedString str(cx, JS::ToString(cx, args[i]));
if (!str)
@ -1682,12 +1691,12 @@ PrintInternal(JSContext* cx, const CallArgs& args, FILE* file)
char* bytes = JS_EncodeStringToUTF8(cx, str);
if (!bytes)
return false;
fprintf(file, "%s%s", i ? " " : "", bytes);
fprintf(file->fp, "%s%s", i ? " " : "", bytes);
JS_free(cx, bytes);
}
fputc('\n', file);
fflush(file);
fputc('\n', file->fp);
fflush(file->fp);
args.rval().setUndefined();
return true;
@ -1776,8 +1785,8 @@ StopTimingMutator(JSContext* cx, unsigned argc, Value* vp)
return false;
}
double total_ms = mutator_ms + gc_ms;
if (total_ms > 0) {
fprintf(gOutFile, "Mutator: %.3fms (%.1f%%), GC: %.3fms (%.1f%%)\n",
if (total_ms > 0 && gOutFile->isOpen()) {
fprintf(gOutFile->fp, "Mutator: %.3fms (%.1f%%), GC: %.3fms (%.1f%%)\n",
mutator_ms, mutator_ms / total_ms * 100.0, gc_ms, gc_ms / total_ms * 100.0);
}
@ -2320,13 +2329,19 @@ static bool
Disassemble(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!gOutFile->isOpen()) {
JS_ReportError(cx, "output file is closed");
return false;
}
Sprinter sprinter(cx);
if (!sprinter.init())
return false;
if (!DisassembleToSprinter(cx, args.length(), vp, &sprinter))
return false;
fprintf(stdout, "%s\n", sprinter.string());
fprintf(gOutFile->fp, "%s\n", sprinter.string());
args.rval().setUndefined();
return true;
}
@ -2336,6 +2351,11 @@ DisassFile(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!gOutFile->isOpen()) {
JS_ReportError(cx, "output file is closed");
return false;
}
/* Support extra options at the start, just like Disassemble. */
DisassembleOptionParser p(args.length(), args.array());
if (!p.parse(cx))
@ -2372,7 +2392,7 @@ DisassFile(JSContext* cx, unsigned argc, Value* vp)
return false;
bool ok = DisassembleScript(cx, script, nullptr, p.lines, p.recursive, p.sourceNotes, &sprinter);
if (ok)
fprintf(stdout, "%s\n", sprinter.string());
fprintf(gOutFile->fp, "%s\n", sprinter.string());
if (!ok)
return false;
@ -2385,6 +2405,11 @@ DisassWithSrc(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!gOutFile->isOpen()) {
JS_ReportError(cx, "output file is closed");
return false;
}
#define LINE_BUF_LEN 512
unsigned len, line1, line2, bupline;
FILE* file;
@ -2466,7 +2491,7 @@ DisassWithSrc(JSContext* cx, unsigned argc, Value* vp)
pc += len;
}
fprintf(stdout, "%s\n", sprinter.string());
fprintf(gOutFile->fp, "%s\n", sprinter.string());
bail:
fclose(file);
@ -2482,6 +2507,7 @@ static bool
Intern(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JSString* str = JS::ToString(cx, args.get(0));
if (!str)
return false;
@ -3324,15 +3350,20 @@ StackDump(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!gOutFile->isOpen()) {
JS_ReportError(cx, "output file is closed");
return false;
}
bool showArgs = ToBoolean(args.get(0));
bool showLocals = ToBoolean(args.get(1));
bool showThisProps = ToBoolean(args.get(2));
char* buf = JS::FormatStackDump(cx, nullptr, showArgs, showLocals, showThisProps);
if (!buf) {
fputs("Failed to format JavaScript stack for dump\n", gOutFile);
fputs("Failed to format JavaScript stack for dump\n", gOutFile->fp);
} else {
fputs(buf, gOutFile);
fputs(buf, gOutFile->fp);
JS_smprintf_free(buf);
}
@ -5612,6 +5643,7 @@ static bool
PrintHelpString(JSContext* cx, Value v)
{
JSString* str = v.toString();
MOZ_ASSERT(gOutFile->isOpen());
JSLinearString* linear = str->ensureLinear(cx);
if (!linear)
@ -5620,12 +5652,12 @@ PrintHelpString(JSContext* cx, Value v)
JS::AutoCheckCannotGC nogc;
if (linear->hasLatin1Chars()) {
for (const Latin1Char* p = linear->latin1Chars(nogc); *p; p++)
fprintf(gOutFile, "%c", char(*p));
fprintf(gOutFile->fp, "%c", char(*p));
} else {
for (const char16_t* p = linear->twoByteChars(nogc); *p; p++)
fprintf(gOutFile, "%c", char(*p));
fprintf(gOutFile->fp, "%c", char(*p));
}
fprintf(gOutFile, "\n");
fprintf(gOutFile->fp, "\n");
return true;
}
@ -5671,11 +5703,16 @@ PrintEnumeratedHelp(JSContext* cx, HandleObject obj, bool brief)
static bool
Help(JSContext* cx, unsigned argc, Value* vp)
{
if (!gOutFile->isOpen()) {
JS_ReportError(cx, "output file is closed");
return false;
}
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject obj(cx);
if (args.length() == 0) {
fprintf(gOutFile, "%s\n", JS_GetImplementationVersion());
fprintf(gOutFile->fp, "%s\n", JS_GetImplementationVersion());
RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
if (!PrintEnumeratedHelp(cx, global, false))
@ -5749,6 +5786,16 @@ CreateLastWarningObject(JSContext* cx, JSErrorReport* report)
return true;
}
static FILE*
ErrorFilePointer()
{
if (gErrFile->isOpen())
return gErrFile->fp;
fprintf(stderr, "error file is closed; falling back to stderr\n");
return stderr;
}
static bool
PrintStackTrace(JSContext* cx, HandleValue exn)
{
@ -5779,8 +5826,9 @@ PrintStackTrace(JSContext* cx, HandleValue exn)
if (!stack)
return false;
fputs("Stack:\n", gErrFile);
fputs(stack.get(), gErrFile);
FILE* fp = ErrorFilePointer();
fputs("Stack:\n", fp);
fputs(stack.get(), fp);
return true;
}
@ -5789,12 +5837,13 @@ void
js::shell::my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report)
{
ShellRuntime* sr = GetShellRuntime(cx);
FILE* fp = ErrorFilePointer();
if (report && JSREPORT_IS_WARNING(report->flags) && sr->lastWarningEnabled) {
JS::AutoSaveExceptionState savedExc(cx);
if (!CreateLastWarningObject(cx, report)) {
fputs("Unhandled error happened while creating last warning object.\n", gOutFile);
fflush(gOutFile);
fputs("Unhandled error happened while creating last warning object.\n", fp);
fflush(fp);
}
savedExc.restore();
}
@ -5804,11 +5853,11 @@ js::shell::my_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* r
if (JS_IsExceptionPending(cx))
(void) JS_GetPendingException(cx, &exn);
sr->gotError = PrintError(cx, gErrFile, message, report, reportWarnings);
sr->gotError = PrintError(cx, fp, message, report, reportWarnings);
if (!exn.isUndefined()) {
JS::AutoSaveExceptionState savedExc(cx);
if (!PrintStackTrace(cx, exn))
fputs("(Unable to print stack trace)\n", gOutFile);
fputs("(Unable to print stack trace)\n", fp);
savedExc.restore();
}
@ -6404,7 +6453,7 @@ NewGlobalObject(JSContext* cx, JS::CompartmentOptions& options,
return nullptr;
}
if (!DefineOS(cx, glob, fuzzingSafe))
if (!DefineOS(cx, glob, fuzzingSafe, &gOutFile, &gErrFile))
return nullptr;
RootedObject performanceObj(cx, JS_NewObject(cx, nullptr));
@ -6903,13 +6952,18 @@ Shell(JSContext* cx, OptionParser* op, char** envp)
}
static void
MaybeOverrideOutFileFromEnv(const char* const envVar,
FILE* defaultOut,
FILE** outFile)
SetOutputFile(const char* const envVar,
FILE* defaultOut,
RCFile** outFile)
{
const char* outPath = getenv(envVar);
if (!outPath || !*outPath || !(*outFile = fopen(outPath, "w"))) {
*outFile = defaultOut;
FILE* newfp;
if (outPath && *outPath && (newfp = fopen(outPath, "w"))) {
*outFile = js_new<RCFile>(newfp);
(*outFile)->acquire();
} else {
*outFile = js_new<RCFile>(defaultOut);
(*outFile)->acquire();
}
}
@ -6951,8 +7005,8 @@ main(int argc, char** argv, char** envp)
setlocale(LC_ALL, "");
#endif
MaybeOverrideOutFileFromEnv("JS_STDERR", stderr, &gErrFile);
MaybeOverrideOutFileFromEnv("JS_STDOUT", stdout, &gOutFile);
SetOutputFile("JS_STDERR", stderr, &gErrFile);
SetOutputFile("JS_STDOUT", stdout, &gOutFile);
OptionParser op("Usage: {progname} [options] [[script] scriptArgs*]");

Просмотреть файл

@ -50,6 +50,24 @@ class AutoCloseFile
}
};
// Reference counted file.
struct RCFile {
FILE* fp;
uint32_t numRefs;
RCFile() : fp(nullptr), numRefs(0) {}
explicit RCFile(FILE* fp) : fp(fp), numRefs(0) {}
void acquire() { numRefs++; }
// Starts out with a ref count of zero.
static RCFile* create(JSContext* cx, const char* filename, const char* mode);
void close();
bool isOpen() const { return fp; }
bool release();
};
} /* namespace shell */
} /* namespace js */