diff options
author | Alon Zakai <alonzakai@gmail.com> | 2013-12-20 17:22:51 -0800 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2013-12-20 17:22:51 -0800 |
commit | 702cf8f1e8a173a15a745c5ac3bae654c99fe33c (patch) | |
tree | 5046a3c593d3d990c824a82c30a666918a8844a6 | |
parent | 4afedfb8ed66cc1aa95ff515baee3f2c762f63c2 (diff) | |
parent | eb27ff87ae15d0dd9855b06a055b590235d8c3a6 (diff) |
Merge pull request #1635 from juj/emscripten_log
emscripten_log() and emscripten_get_callstack()
-rw-r--r-- | src/emscripten-source-map.min.js | 31 | ||||
-rw-r--r-- | src/library.js | 187 | ||||
-rw-r--r-- | src/preamble.js | 4 | ||||
-rw-r--r-- | system/include/emscripten/emscripten.h | 64 | ||||
-rw-r--r-- | tests/emscripten_log/emscripten_log.cpp | 136 | ||||
-rw-r--r-- | tests/test_browser.py | 7 | ||||
-rw-r--r-- | tests/test_core.py | 5 |
7 files changed, 434 insertions, 0 deletions
diff --git a/src/emscripten-source-map.min.js b/src/emscripten-source-map.min.js new file mode 100644 index 00000000..9151400f --- /dev/null +++ b/src/emscripten-source-map.min.js @@ -0,0 +1,31 @@ +function define(e,t,n){if(typeof e!="string")throw new TypeError("Expected string, got: "+e);arguments.length==2&&(n=t);if(e in define.modules)throw new Error("Module already defined: "+e);define.modules[e]=n}function Domain(){this.modules={},this._currentModule=null}define.modules={},function(){function e(e){var t=e.split("/"),n=1;while(n<t.length)t[n]===".."?t.splice(n-1,1):t[n]==="."?t.splice(n,1):n++;return t.join("/")}function t(e,t){return e=e.trim(),t=t.trim(),/^\//.test(t)?t:e.replace(/\/*$/,"/")+t}function n(e){var t=e.split("/");return t.pop(),t.join("/")}Domain.prototype.require=function(e,t){if(Array.isArray(e)){var n=e.map(function(e){return this.lookup(e)},this);return t&&t.apply(null,n),undefined}return this.lookup(e)},Domain.prototype.lookup=function(r){/^\./.test(r)&&(r=e(t(n(this._currentModule),r)));if(r in this.modules){var i=this.modules[r];return i}if(r in define.modules){var i=define.modules[r];if(typeof i=="function"){var s={},o=this._currentModule;this._currentModule=r,i(this.require.bind(this),s,{id:r,uri:""}),this._currentModule=o,i=s}return this.modules[r]=i,i}throw new Error("Module not defined: "+r)}}(),define.Domain=Domain,define.globalDomain=new Domain;var require=define.globalDomain.require.bind(define.globalDomain);define("source-map/source-map-generator",["require","exports","module","source-map/base64-vlq","source-map/util","source-map/array-set"],function(e,t,n){function o(e){this._file=i.getArg(e,"file"),this._sourceRoot=i.getArg(e,"sourceRoot",null),this._sources=new s,this._names=new s,this._mappings=[],this._sourcesContents=null}function u(e,t){var n=(e&&e.line)-(t&&t.line);return n?n:(e&&e.column)-(t&&t.column)}function a(e,t){return e=e||"",t=t||"",(e>t)-(e<t)}function f(e,t){return u(e.generated,t.generated)||u(e.original,t.original)||a(e.source,t.source)||a(e.name,t.name)}var r=e("./base64-vlq"),i=e("./util"),s=e("./array-set").ArraySet;o.prototype._version=3,o.fromSourceMap=function(t){var n=t.sourceRoot,r=new o({file:t.file,sourceRoot:n});return t.eachMapping(function(e){var t={generated:{line:e.generatedLine,column:e.generatedColumn}};e.source&&(t.source=e.source,n&&(t.source=i.relative(n,t.source)),t.original={line:e.originalLine,column:e.originalColumn},e.name&&(t.name=e.name)),r.addMapping(t)}),t.sources.forEach(function(e){var n=t.sourceContentFor(e);n&&r.setSourceContent(e,n)}),r},o.prototype.addMapping=function(t){var n=i.getArg(t,"generated"),r=i.getArg(t,"original",null),s=i.getArg(t,"source",null),o=i.getArg(t,"name",null);this._validateMapping(n,r,s,o),s&&!this._sources.has(s)&&this._sources.add(s),o&&!this._names.has(o)&&this._names.add(o),this._mappings.push({generated:n,original:r,source:s,name:o})},o.prototype.setSourceContent=function(t,n){var r=t;this._sourceRoot&&(r=i.relative(this._sourceRoot,r)),n!==null?(this._sourcesContents||(this._sourcesContents={}),this._sourcesContents[i.toSetString(r)]=n):(delete this._sourcesContents[i.toSetString(r)],Object.keys(this._sourcesContents).length===0&&(this._sourcesContents=null))},o.prototype.applySourceMap=function(t,n){n||(n=t.file);var r=this._sourceRoot;r&&(n=i.relative(r,n));var o=new s,u=new s;this._mappings.forEach(function(e){if(e.source===n&&e.original){var s=t.originalPositionFor({line:e.original.line,column:e.original.column});s.source!==null&&(r?e.source=i.relative(r,s.source):e.source=s.source,e.original.line=s.line,e.original.column=s.column,s.name!==null&&e.name!==null&&(e.name=s.name))}var a=e.source;a&&!o.has(a)&&o.add(a);var f=e.name;f&&!u.has(f)&&u.add(f)},this),this._sources=o,this._names=u,t.sources.forEach(function(e){var n=t.sourceContentFor(e);n&&(r&&(e=i.relative(r,e)),this.setSourceContent(e,n))},this)},o.prototype._validateMapping=function(t,n,r,i){if(t&&"line"in t&&"column"in t&&t.line>0&&t.column>=0&&!n&&!r&&!i)return;if(t&&"line"in t&&"column"in t&&n&&"line"in n&&"column"in n&&t.line>0&&t.column>=0&&n.line>0&&n.column>=0&&r)return;throw new Error("Invalid mapping.")},o.prototype._serializeMappings=function(){var t=0,n=1,i=0,s=0,o=0,u=0,a="",l;this._mappings.sort(f);for(var c=0,h=this._mappings.length;c<h;c++){l=this._mappings[c];if(l.generated.line!==n){t=0;while(l.generated.line!==n)a+=";",n++}else if(c>0){if(!f(l,this._mappings[c-1]))continue;a+=","}a+=r.encode(l.generated.column-t),t=l.generated.column,l.source&&l.original&&(a+=r.encode(this._sources.indexOf(l.source)-u),u=this._sources.indexOf(l.source),a+=r.encode(l.original.line-1-s),s=l.original.line-1,a+=r.encode(l.original.column-i),i=l.original.column,l.name&&(a+=r.encode(this._names.indexOf(l.name)-o),o=this._names.indexOf(l.name)))}return a},o.prototype.toJSON=function(){var t={version:this._version,file:this._file,sources:this._sources.toArray(),names:this._names.toArray(),mappings:this._serializeMappings()};return this._sourceRoot&&(t.sourceRoot=this._sourceRoot),this._sourcesContents&&(t.sourcesContent=t.sources.map(function(e){return t.sourceRoot&&(e=i.relative(t.sourceRoot,e)),Object.prototype.hasOwnProperty.call(this._sourcesContents,i.toSetString(e))?this._sourcesContents[i.toSetString(e)]:null},this)),t},o.prototype.toString=function(){return JSON.stringify(this)},t.SourceMapGenerator=o}),define("source-map/base64-vlq",["require","exports","module","source-map/base64"],function(e,t,n){function a(e){return e<0?(-e<<1)+1:(e<<1)+0}function f(e){var t=(e&1)===1,n=e>>1;return t?-n:n}var r=e("./base64"),i=5,s=1<<i,o=s-1,u=s;t.encode=function(t){var n="",s,f=a(t);do s=f&o,f>>>=i,f>0&&(s|=u),n+=r.encode(s);while(f>0);return n},t.decode=function(t){var n=0,s=t.length,a=0,l=0,c,h;do{if(n>=s)throw new Error("Expected more digits in base 64 VLQ value.");h=r.decode(t.charAt(n++)),c=!!(h&u),h&=o,a+=h<<l,l+=i}while(c);return{value:f(a),rest:t.slice(n)}}}),define("source-map/base64",["require","exports","module"],function(e,t,n){var r={},i={};"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("").forEach(function(e,t){r[e]=t,i[t]=e}),t.encode=function(t){if(t in i)return i[t];throw new TypeError("Must be between 0 and 63: "+t)},t.decode=function(t){if(t in r)return r[t];throw new TypeError("Not a valid base 64 digit: "+t)}}),define("source-map/util",["require","exports","module"],function(e,t,n){function r(e,t,n){if(t in e)return e[t];if(arguments.length===3)return n;throw new Error('"'+t+'" is a required argument.')}function s(e){var t=e.match(i);return t?{scheme:t[1],auth:t[3],host:t[4],port:t[6],path:t[7]}:null}function o(e){var t=e.scheme+"://";return e.auth&&(t+=e.auth+"@"),e.host&&(t+=e.host),e.port&&(t+=":"+e.port),e.path&&(t+=e.path),t}function u(e,t){var n;return t.match(i)?t:t.charAt(0)==="/"&&(n=s(e))?(n.path=t,o(n)):e.replace(/\/$/,"")+"/"+t}function a(e){return"$"+e}function f(e){return e.substr(1)}function l(e,t){e=e.replace(/\/$/,"");var n=s(e);return t.charAt(0)=="/"&&n&&n.path=="/"?t.slice(1):t.indexOf(e+"/")===0?t.substr(e.length+1):t}t.getArg=r;var i=/([\w+\-.]+):\/\/((\w+:\w+)@)?([\w.]+)?(:(\d+))?(\S+)?/;t.urlParse=s,t.urlGenerate=o,t.join=u,t.toSetString=a,t.fromSetString=f,t.relative=l}),define("source-map/array-set",["require","exports","module","source-map/util"],function(e,t,n){function i(){this._array=[],this._set={}}var r=e("./util");i.fromArray=function(t,n){var r=new i;for(var s=0,o=t.length;s<o;s++)r.add(t[s],n);return r},i.prototype.add=function(t,n){var i=this.has(t),s=this._array.length;(!i||n)&&this._array.push(t),i||(this._set[r.toSetString(t)]=s)},i.prototype.has=function(t){return Object.prototype.hasOwnProperty.call(this._set,r.toSetString(t))},i.prototype.indexOf=function(t){if(this.has(t))return this._set[r.toSetString(t)];throw new Error('"'+t+'" is not in the set.')},i.prototype.at=function(t){if(t>=0&&t<this._array.length)return this._array[t];throw new Error("No element indexed by "+t)},i.prototype.toArray=function(){return this._array.slice()},t.ArraySet=i}),define("source-map/source-map-consumer",["require","exports","module","source-map/util","source-map/binary-search","source-map/array-set","source-map/base64-vlq"],function(e,t,n){function u(e){var t=e;typeof e=="string"&&(t=JSON.parse(e.replace(/^\)\]\}'/,"")));var n=r.getArg(t,"version"),i=r.getArg(t,"sources"),o=r.getArg(t,"names"),u=r.getArg(t,"sourceRoot",null),a=r.getArg(t,"sourcesContent",null),f=r.getArg(t,"mappings"),l=r.getArg(t,"file",null);if(n!==this._version)throw new Error("Unsupported version: "+n);this._names=s.fromArray(o,!0),this._sources=s.fromArray(i,!0),this.sourceRoot=u,this.sourcesContent=a,this.file=l,this._generatedMappings=[],this._originalMappings=[],this._parseMappings(f,u)}var r=e("./util"),i=e("./binary-search"),s=e("./array-set").ArraySet,o=e("./base64-vlq");u.prototype._version=3,Object.defineProperty(u.prototype,"sources",{get:function(){return this._sources.toArray().map(function(e){return this.sourceRoot?r.join(this.sourceRoot,e):e},this)}}),u.prototype._parseMappings=function(t,n){var r=1,i=0,s=0,u=0,a=0,f=0,l=/^[,;]/,c=t,h,p;while(c.length>0)if(c.charAt(0)===";")r++,c=c.slice(1),i=0;else if(c.charAt(0)===",")c=c.slice(1);else{h={},h.generatedLine=r,p=o.decode(c),h.generatedColumn=i+p.value,i=h.generatedColumn,c=p.rest;if(c.length>0&&!l.test(c.charAt(0))){p=o.decode(c),h.source=this._sources.at(a+p.value),a+=p.value,c=p.rest;if(c.length===0||l.test(c.charAt(0)))throw new Error("Found a source, but no line and column");p=o.decode(c),h.originalLine=s+p.value,s=h.originalLine,h.originalLine+=1,c=p.rest;if(c.length===0||l.test(c.charAt(0)))throw new Error("Found a source and line, but no column");p=o.decode(c),h.originalColumn=u+p.value,u=h.originalColumn,c=p.rest,c.length>0&&!l.test(c.charAt(0))&&(p=o.decode(c),h.name=this._names.at(f+p.value),f+=p.value,c=p.rest)}this._generatedMappings.push(h),typeof h.originalLine=="number"&&this._originalMappings.push(h)}this._originalMappings.sort(this._compareOriginalPositions)},u.prototype._compareOriginalPositions=function(t,n){if(t.source>n.source)return 1;if(t.source<n.source)return-1;var r=t.originalLine-n.originalLine;return r===0?t.originalColumn-n.originalColumn:r},u.prototype._compareGeneratedPositions=function(t,n){var r=t.generatedLine-n.generatedLine;return r===0?t.generatedColumn-n.generatedColumn:r},u.prototype._findMapping=function(t,n,r,s,o){if(t[r]<=0)throw new TypeError("Line must be greater than or equal to 1, got "+t[r]);if(t[s]<0)throw new TypeError("Column must be greater than or equal to 0, got "+t[s]);return i.search(t,n,o)},u.prototype.originalPositionFor=function(t){var n={generatedLine:r.getArg(t,"line"),generatedColumn:r.getArg(t,"column")},i=this._findMapping(n,this._generatedMappings,"generatedLine","generatedColumn",this._compareGeneratedPositions);if(i){var s=r.getArg(i,"source",null);return s&&this.sourceRoot&&(s=r.join(this.sourceRoot,s)),{source:s,line:r.getArg(i,"originalLine",null),column:r.getArg(i,"originalColumn",null),name:r.getArg(i,"name",null)}}return{source:null,line:null,column:null,name:null}},u.prototype.sourceContentFor=function(t){if(!this.sourcesContent)return null;this.sourceRoot&&(t=r.relative(this.sourceRoot,t));if(this._sources.has(t))return this.sourcesContent[this._sources.indexOf(t)];var n;if(this.sourceRoot&&(n=r.urlParse(this.sourceRoot))){var i=t.replace(/^file:\/\//,"");if(n.scheme=="file"&&this._sources.has(i))return this.sourcesContent[this._sources.indexOf(i)];if((!n.path||n.path=="/")&&this._sources.has("/"+t))return this.sourcesContent[this._sources.indexOf("/"+t)]}throw new Error('"'+t+'" is not in the SourceMap.')},u.prototype.generatedPositionFor=function(t){var n={source:r.getArg(t,"source"),originalLine:r.getArg(t,"line"),originalColumn:r.getArg(t,"column")};this.sourceRoot&&(n.source=r.relative(this.sourceRoot,n.source));var i=this._findMapping(n,this._originalMappings,"originalLine","originalColumn",this._compareOriginalPositions);return i?{line:r.getArg(i,"generatedLine",null),column:r.getArg(i,"generatedColumn",null)}:{line:null,column:null}},u.GENERATED_ORDER=1,u.ORIGINAL_ORDER=2,u.prototype.eachMapping=function(t,n,i){var s=n||null,o=i||u.GENERATED_ORDER,a;switch(o){case u.GENERATED_ORDER:a=this._generatedMappings;break;case u.ORIGINAL_ORDER:a=this._originalMappings;break;default:throw new Error("Unknown order of iteration.")}var f=this.sourceRoot;a.map(function(e){var t=e.source;return t&&f&&(t=r.join(f,t)),{source:t,generatedLine:e.generatedLine,generatedColumn:e.generatedColumn,originalLine:e.originalLine,originalColumn:e.originalColumn,name:e.name}}).forEach(t,s)},t.SourceMapConsumer=u}),define("source-map/binary-search",["require","exports","module"],function(e,t,n){function r(e,t,n,i,s){var o=Math.floor((t-e)/2)+e,u=s(n,i[o]);return u===0?i[o]:u>0?t-o>1?r(o,t,n,i,s):i[o]:o-e>1?r(e,o,n,i,s):e<0?null:i[e]}t.search=function(t,n,i){return n.length>0?r(-1,n.length,t,n,i):null}}),define("source-map/source-node",["require","exports","module","source-map/source-map-generator","source-map/util"],function(e,t,n){function s(e,t,n,r,i){this.children=[],this.sourceContents={},this.line=e===undefined?null:e,this.column=t===undefined?null:t,this.source=n===undefined?null:n,this.name=i===undefined?null:i,r!=null&&this.add(r)}var r=e("./source-map-generator").SourceMapGenerator,i=e("./util");s.fromStringWithSourceMap=function(t,n){function f(e,t){e===null||e.source===undefined?r.add(t):r.add(new s(e.originalLine,e.originalColumn,e.source,t,e.name))}var r=new s,i=t.split("\n"),o=1,u=0,a=null;return n.eachMapping(function(e){if(a===null){while(o<e.generatedLine)r.add(i.shift()+"\n"),o++;if(u<e.generatedColumn){var t=i[0];r.add(t.substr(0,e.generatedColumn)),i[0]=t.substr(e.generatedColumn),u=e.generatedColumn}}else if(o<e.generatedLine){var n="";do n+=i.shift()+"\n",o++,u=0;while(o<e.generatedLine);if(u<e.generatedColumn){var t=i[0];n+=t.substr(0,e.generatedColumn),i[0]=t.substr(e.generatedColumn),u=e.generatedColumn}f(a,n)}else{var t=i[0],n=t.substr(0,e.generatedColumn-u);i[0]=t.substr(e.generatedColumn-u),u=e.generatedColumn,f(a,n)}a=e},this),f(a,i.join("\n")),n.sources.forEach(function(e){var t=n.sourceContentFor(e);t&&r.setSourceContent(e,t)}),r},s.prototype.add=function(t){if(Array.isArray(t))t.forEach(function(e){this.add(e)},this);else{if(!(t instanceof s||typeof t=="string"))throw new TypeError("Expected a SourceNode, string, or an array of SourceNodes and strings. Got "+t);t&&this.children.push(t)}return this},s.prototype.prepend=function(t){if(Array.isArray(t))for(var n=t.length-1;n>=0;n--)this.prepend(t[n]);else{if(!(t instanceof s||typeof t=="string"))throw new TypeError("Expected a SourceNode, string, or an array of SourceNodes and strings. Got "+t);this.children.unshift(t)}return this},s.prototype.walk=function(t){this.children.forEach(function(e){e instanceof s?e.walk(t):e!==""&&t(e,{source:this.source,line:this.line,column:this.column,name:this.name})},this)},s.prototype.join=function(t){var n,r,i=this.children.length;if(i>0){n=[];for(r=0;r<i-1;r++)n.push(this.children[r]),n.push(t);n.push(this.children[r]),this.children=n}return this},s.prototype.replaceRight=function(t,n){var r=this.children[this.children.length-1];return r instanceof s?r.replaceRight(t,n):typeof r=="string"?this.children[this.children.length-1]=r.replace(t,n):this.children.push("".replace(t,n)),this},s.prototype.setSourceContent=function(t,n){this.sourceContents[i.toSetString(t)]=n},s.prototype.walkSourceContents=function(t){this.children.forEach(function(e){e instanceof s&&e.walkSourceContents(t)},this),Object.keys(this.sourceContents).forEach(function(e){t(i.fromSetString(e),this.sourceContents[e])},this)},s.prototype.toString=function(){var t="";return this.walk(function(e){t+=e}),t},s.prototype.toStringWithSourceMap=function(t){var n={code:"",line:1,column:0},i=new r(t),s=!1,o=null,u=null,a=null,f=null;return this.walk(function(e,t){n.code+=e,t.source!==null&&t.line!==null&&t.column!==null?((o!==t.source||u!==t.line||a!==t.column||f!==t.name)&&i.addMapping({source:t.source,original:{line:t.line,column:t.column},generated:{line:n.line,column:n.column},name:t.name}),o=t.source,u=t.line,a=t.column,f=t.name,s=!0):s&&(i.addMapping({generated:{line:n.line,column:n.column}}),o=null,s=!1),e.split("").forEach(function(e){e==="\n"?(n.line++,n.column=0):n.column++})}),this.walkSourceContents(function(e,t){i.setSourceContent(e,t)}),{code:n.code,map:i}},t.SourceNode=s}),window.sourceMap={SourceMapConsumer:require("source-map/source-map-consumer").SourceMapConsumer,SourceMapGenerator:require("source-map/source-map-generator").SourceMapGenerator,SourceNode:require("source-map/source-node").SourceNode} + +var emscripten_sourcemap_xmlHttp = undefined; +function emscripten_sourceMapLoaded() { + if (emscripten_sourcemap_xmlHttp.readyState === 4) { + Module['removeRunDependency']('sourcemap'); + if (emscripten_sourcemap_xmlHttp.status === 200) { + emscripten_source_map = new window.sourceMap.SourceMapConsumer(emscripten_sourcemap_xmlHttp.responseText); + console.log('Source map data loaded.'); + } else { + console.warn('Source map data loading failed with status code ' + emscripten_sourcemap_xmlHttp.status + '.'); + } + emscripten_sourcemap_xmlHttp = undefined; + } +} +function emscripten_loadSourceMap() { + var url = window.location.href+'.map'; + console.log('Loading source map data from ' + url + '..'); + Module['addRunDependency']('sourcemap'); + emscripten_sourcemap_xmlHttp = new XMLHttpRequest(); + emscripten_sourcemap_xmlHttp.onreadystatechange = emscripten_sourceMapLoaded; + emscripten_sourcemap_xmlHttp.open("GET", url, true); + emscripten_sourcemap_xmlHttp.send(null); +} + +var Module; +if (Module['preRun'] instanceof Array) { + Module['preRun'].push(emscripten_loadSourceMap); +} else { + Module['preRun'] = [emscripten_loadSourceMap]; +} diff --git a/src/library.js b/src/library.js index bdc0a39e..fc731e01 100644 --- a/src/library.js +++ b/src/library.js @@ -8835,6 +8835,193 @@ LibraryManager.library = { } }, + // Returns [parentFuncArguments, functionName, paramListName] + _emscripten_traverse_stack: function(args) { + if (!args || !args.callee || !args.callee.name) { + return [null, '', '']; + } + + var funstr = args.callee.toString(); + var funcname = args.callee.name; + var str = '('; + var first = true; + for(i in args) { + var a = args[i]; + if (!first) { + str += ", "; + } + first = false; + if (typeof a === 'number' || typeof a === 'string') { + str += a; + } else { + str += '(' + typeof a + ')'; + } + } + str += ')'; + args = args.callee.caller.arguments; + if (first) + str = ''; + return [args, funcname, str]; + }, + + emscripten_get_callstack_js__deps: ['_emscripten_traverse_stack'], + emscripten_get_callstack_js: function(flags) { + var err = new Error(); + if (!err.stack) { + Runtime.warnOnce('emscripten_get_callstack_js is not supported on this browser!'); + return ''; + } + var callstack = new Error().stack.toString(); + + // Find the symbols in the callstack that corresponds to the functions that report callstack information, and remove everyhing up to these from the output. + var iThisFunc = callstack.lastIndexOf('_emscripten_log'); + var iThisFunc2 = callstack.lastIndexOf('_emscripten_get_callstack'); + var iNextLine = callstack.indexOf('\n', Math.max(iThisFunc, iThisFunc2))+1; + callstack = callstack.slice(iNextLine); + + // If user requested to see the original source stack, but no source map information is available, just fall back to showing the JS stack. + if (flags & 8/*EM_LOG_C_STACK*/ && typeof emscripten_source_map === 'undefined') { + Runtime.warnOnce('Source map information is not available, emscripten_log with EM_LOG_C_STACK will be ignored. Build with "--pre-js $EMSCRIPTEN/src/emscripten-source-map.min.js" linker flag to add source map loading to code.'); + flags ^= 8/*EM_LOG_C_STACK*/; + flags |= 16/*EM_LOG_JS_STACK*/; + } + + var stack_args = null; + if (flags & 128 /*EM_LOG_FUNC_PARAMS*/) { + // To get the actual parameters to the functions, traverse the stack via the unfortunately deprecated 'arguments.callee' method, if it works: + var stack_args = __emscripten_traverse_stack(arguments); + while (stack_args[1].indexOf('_emscripten_') >= 0) + stack_args = __emscripten_traverse_stack(stack_args[0]); + } + + // Process all lines: + lines = callstack.split('\n'); + callstack = ''; + var firefoxRe = new RegExp('\\s*(.*?)@(.*):(.*)'); // Extract components of form ' Object._main@http://server.com:4324' + var chromeRe = new RegExp('\\s*at (.*?) \\\((.*):(.*):(.*)\\\)'); // Extract components of form ' at Object._main (http://server.com/file.html:4324:12)' + + for(l in lines) { + var line = lines[l]; + + var jsSymbolName = ''; + var file = ''; + var lineno = 0; + var column = 0; + + var parts = chromeRe.exec(line); + if (parts && parts.length == 5) { + jsSymbolName = parts[1]; + file = parts[2]; + lineno = parts[3]; + column = parts[4]; + } else { + parts = firefoxRe.exec(line); + if (parts && parts.length == 4) { + jsSymbolName = parts[1]; + file = parts[2]; + lineno = parts[3]; + column = 0; // Firefox doesn't carry column information. See https://bugzilla.mozilla.org/show_bug.cgi?id=762556 + } else { + // Was not able to extract this line for demangling/sourcemapping purposes. Output it as-is. + callstack += line + '\n'; + continue; + } + } + + // Try to demangle the symbol, but fall back to showing the original JS symbol name if not available. + var cSymbolName = (flags & 32/*EM_LOG_DEMANGLE*/) ? demangle(jsSymbolName) : jsSymbolName; + if (!cSymbolName) { + cSymbolName = jsSymbolName; + } + + var haveSourceMap = false; + + if (flags & 8/*EM_LOG_C_STACK*/) { + var orig = emscripten_source_map.originalPositionFor({line: lineno, column: column}); + haveSourceMap = (orig && orig.source); + if (haveSourceMap) { + if (flags & 64/*EM_LOG_NO_PATHS*/) { + orig.source = orig.source.substring(orig.source.replace(/\\/g, "/").lastIndexOf('/')+1); + } + callstack += ' at ' + cSymbolName + ' (' + orig.source + ':' + orig.line + ':' + orig.column + ')\n'; + } + } + if ((flags & 16/*EM_LOG_JS_STACK*/) || !haveSourceMap) { + if (flags & 64/*EM_LOG_NO_PATHS*/) { + file = file.substring(file.replace(/\\/g, "/").lastIndexOf('/')+1); + } + callstack += (haveSourceMap ? (' = '+jsSymbolName) : (' at '+cSymbolName)) + ' (' + file + ':' + lineno + ':' + column + ')\n'; + } + + // If we are still keeping track with the callstack by traversing via 'arguments.callee', print the function parameters as well. + if (flags & 128 /*EM_LOG_FUNC_PARAMS*/ && stack_args[0]) { + if (stack_args[1] == jsSymbolName && stack_args[2].length > 0) { + callstack = callstack.replace(/\s+$/, ''); + callstack += ' with values: ' + stack_args[1] + stack_args[2] + '\n'; + } + stack_args = __emscripten_traverse_stack(stack_args[0]); + } + } + // Trim extra whitespace at the end of the output. + callstack = callstack.replace(/\s+$/, ''); + return callstack; + }, + + emscripten_get_callstack__deps: ['emscripten_get_callstack_js'], + emscripten_get_callstack: function(flags, str, maxbytes) { + var callstack = _emscripten_get_callstack_js(flags); + // User can query the required amount of bytes to hold the callstack. + if (!str || maxbytes <= 0) { + return callstack.length+1; + } + // Truncate output to avoid writing past bounds. + if (callstack.length > maxbytes-1) { + callstack.slice(0, maxbytes-1); + } + // Output callstack string as C string to HEAP. + writeStringToMemory(callstack, str, false); + + // Return number of bytes written. + return callstack.length+1; + }, + + emscripten_log_js__deps: ['emscripten_get_callstack_js'], + emscripten_log_js: function(flags, str) { + if (flags & 24/*EM_LOG_C_STACK | EM_LOG_JS_STACK*/) { + str = str.replace(/\s+$/, ''); // Ensure the message and the callstack are joined cleanly with exactly one newline. + str += (str.length > 0 ? '\n' : '') + _emscripten_get_callstack_js(flags); + } + + if (flags & 1 /*EM_LOG_CONSOLE*/) { + if (flags & 4 /*EM_LOG_ERROR*/) { + console.error(str); + } else if (flags & 2 /*EM_LOG_WARN*/) { + console.warn(str); + } else { + console.log(str); + } + } else if (flags & 6 /*EM_LOG_ERROR|EM_LOG_WARN*/) { + Module.printErr(str); + } else { + Module.print(str); + } + }, + + emscripten_log__deps: ['_formatString', 'emscripten_log_js'], + emscripten_log: function(flags, varargs) { + // Extract the (optionally-existing) printf format specifier field from varargs. + var format = {{{ makeGetValue('varargs', '0', 'i32', undefined, undefined, true) }}}; + varargs += Math.max(Runtime.getNativeFieldSize('i32'), Runtime.getAlignSize('i32', null, true)); + var str = ''; + if (format) { + var result = __formatString(format, varargs); + for(var i = 0 ; i < result.length; ++i) { + str += String.fromCharCode(result[i]); + } + } + _emscripten_log_js(flags, str); + }, + //============================ // emscripten vector ops //============================ diff --git a/src/preamble.js b/src/preamble.js index 710b7c52..832ec2c3 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -642,6 +642,10 @@ Module['stringToUTF32'] = stringToUTF32; function demangle(func) { try { + // Special-case the entry point, since its name differs from other name mangling. + if (func == 'Object._main' || func == '_main') { + return 'main()'; + } if (typeof func === 'number') func = Pointer_stringify(func); if (func[0] !== '_') return func; if (func[1] !== '_') return func; // C function diff --git a/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h index ddcbc43a..b6e6307b 100644 --- a/system/include/emscripten/emscripten.h +++ b/system/include/emscripten/emscripten.h @@ -447,6 +447,70 @@ void emscripten_asm_const(const char *code); int emscripten_asm_const_int(const char *code, ...); double emscripten_asm_const_double(const char *code, ...); +/* If specified, logs directly to the browser console/inspector + * window. If not specified, logs via the application Module. */ +#define EM_LOG_CONSOLE 1 +/* If specified, prints a warning message. */ +#define EM_LOG_WARN 2 +/* If specified, prints an error message. If neither EM_LOG_WARN + * or EM_LOG_ERROR is specified, an info message is printed. + * EM_LOG_WARN and EM_LOG_ERROR are mutually exclusive. */ +#define EM_LOG_ERROR 4 +/* If specified, prints a callstack that contains filenames referring + * to original C sources using source map information. */ +#define EM_LOG_C_STACK 8 +/* If specified, prints a callstack that contains filenames referring + * to lines to the built .js/.html file along with the message. The + * flags EM_LOG_C_STACK and EM_LOG_JS_STACK can be combined to output + * both untranslated and translated file+line information. */ +#define EM_LOG_JS_STACK 16 +/* If specified, C/C++ function names are demangled before printing. + * Otherwise, the mangled post-compilation JS function names are + * displayed. */ +#define EM_LOG_DEMANGLE 32 +/* If specified, the pathnames of the file information in the call + * stack will be omitted. */ +#define EM_LOG_NO_PATHS 64 +/* If specified, prints out the actual values of the parameters the + * functions were invoked with. */ +#define EM_LOG_FUNC_PARAMS 128 + +/* + * Prints out a message to the console, optionally with the + * callstack information. + * @param flags A binary OR of items from the list of EM_LOG_xxx + * flags that specify printing options. + * @param '...' A printf-style "format, ..." parameter list that + * is parsed according to the printf formatting rules. + */ +void emscripten_log(int flags, ...); + +/* + * Programmatically obtains the current callstack. + * @param flags A binary OR of items from the list of EM_LOG_xxx + * flags that specify printing options. The + * items EM_LOG_CONSOLE, EM_LOG_WARN and + * EM_LOG_ERROR do not apply in this function and + * are ignored. + * @param out A pointer to a memory region where the callstack + * string will be written to. The string outputted + * by this function will always be null-terminated. + * @param maxbytes The maximum number of bytes that this function can + * write to the memory pointed to by 'out'. If + * there is no enough space, the output will be + * truncated (but always null-terminated). + * @return Returns the number of bytes written. (not number of + * characters, so this will also include the terminating zero) + + * To query the amount of bytes needed for a callstack without writing + * it, pass 0 to 'out' and 'maxbytes', in which case the function will + * return the number of bytes (including the terminating zero) that + * will be needed to hold the full callstack. Note that this might be + * fully accurate since subsequent calls will carry different line + * numbers, so it is best to allocate a few bytes extra to be safe. + */ +int emscripten_get_callstack(int flags, char *out, int maxbytes); + #ifdef __cplusplus } #endif diff --git a/tests/emscripten_log/emscripten_log.cpp b/tests/emscripten_log/emscripten_log.cpp new file mode 100644 index 00000000..a5bb8432 --- /dev/null +++ b/tests/emscripten_log/emscripten_log.cpp @@ -0,0 +1,136 @@ +#include <emscripten.h> +#include <stdio.h> +#include <cstring> + +#define STRINGIZE_HELPER(x) #x +#define STRINGIZE(x) STRINGIZE_HELPER(x) + +#ifndef REPORT_RESULT +#define REPORT_RESULT int dummy +#endif + +int result = 1; // If 1, this test succeeded. + +// A custom assert macro to test varargs routing to emscripten_log(). +#define MYASSERT(condition, ...) \ + do { \ + if (!(condition)) { \ + emscripten_log(EM_LOG_ERROR, "%s", "Condition '" #condition "' failed in file " __FILE__ ":" STRINGIZE(__LINE__) "!"); \ + emscripten_log(EM_LOG_ERROR, ##__VA_ARGS__); \ + result = 0; \ + } \ + } while(0) + +void __attribute__((noinline)) kitten() +{ + // Log to Emscripten Module. + emscripten_log(EM_LOG_NO_PATHS, "Print a log message: int: %d, string: %s.", 42, "hello"); + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_WARN, "Print a warning message"); + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_ERROR, "This is an error!"); + + // Log directly to Browser web inspector/console. + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE, "Info log to console: int: %d, string: %s", 42, "hello"); + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_WARN, "Warning message to console."); + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR, "Error message to console! This should appear in red!"); + + // Log to with full callstack information (both original C source and JS callstacks): + emscripten_log(EM_LOG_C_STACK | EM_LOG_JS_STACK | EM_LOG_DEMANGLE, "A message with as full call stack information as possible:"); + + // Log with just mangled JS callstacks: + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_JS_STACK, "This is a message with a mangled JS callstack:"); + + // Log only clean C callstack: + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_C_STACK | EM_LOG_DEMANGLE, "This message should have a clean C callstack:"); + + // We can leave out the message to just print out the callstack: + printf("The following line should show just the callstack without a message:\n"); + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_ERROR | EM_LOG_C_STACK | EM_LOG_JS_STACK | EM_LOG_DEMANGLE); +} + +void __attribute__((noinline)) bar(int = 0, char * = 0, double = 0) // Arbitrary function signature to add some content to callstack. +{ + if (1 == 2) + MYASSERT(2 == 1, "World falls apart!"); + else + MYASSERT(1 == 1); + + int flags = EM_LOG_NO_PATHS | EM_LOG_JS_STACK | EM_LOG_DEMANGLE | EM_LOG_FUNC_PARAMS; +#ifndef RUN_FROM_JS_SHELL + flags |= EM_LOG_C_STACK; +#endif + + // We can programmatically get the callstack. + // 1. Ask for callstack length: + int nbytes = emscripten_get_callstack(flags, 0, 0); + // 2. Allocate temp memory to hold the callstack. + char *callstack = new char[nbytes]; + // 3. Obtain it. + // 4. Do something with the callstack string. + + emscripten_get_callstack(flags, callstack, nbytes); + + /* The callstack should be something like + at bar(int, char*, double) (src.cpp.o.js:5383:12) + at void Foo<int>() (src.cpp.o.js:5417:4) + at main() (src.cpp.o.js:5404:2) + at Object.callMain (src.cpp.o.js:71344:30) + at doRun (src.cpp.o.js:71383:25) + at run (src.cpp.o.js:71396:5) + at Object.<anonymous> (src.cpp.o.js:71439:1) + at Module._compile (module.js:456:26) + + but the line numbers will greatly vary depending on the mode we are compiling in, so cannot test with direct string comparison. */ + + if ((flags & EM_LOG_C_STACK) != 0) + { + MYASSERT(!!strstr(callstack, "at bar(int, char*, double) (src.cpp:"), "Callstack was %s!", callstack); + MYASSERT(!!strstr(callstack, "at void Foo<int>() (src.cpp:"), "Callstack was %s!", callstack); + } + else + { + MYASSERT(!!strstr(callstack, "at bar(int, char*, double) (src.cpp.o.js:"), "Callstack was %s!", callstack); + MYASSERT(!!strstr(callstack, "at void Foo<int>() (src.cpp.o.js:"), "Callstack was %s!", callstack); + } + + // 5. Clean up. + delete[] callstack; + + // Or alternatively use a fixed-size buffer for the callstack (and get a truncated output if it was too small). + char str[1024]; + emscripten_get_callstack(EM_LOG_NO_PATHS | EM_LOG_JS_STACK, str, 1024); + + /* With EM_LOG_JS_STACK, the callstack will be + at __Z3bariPcd (src.cpp.o.js:5394:12) + at __Z3FooIiEvv (src.cpp.o.js:5417:4) + at Object._main (src.cpp.o.js:5404:2) + at Object.callMain (src.cpp.o.js:71344:30) + at doRun (src.cpp.o.js:71383:25) + at run (src.cpp.o.js:71396:5) + at Object.<anonymous> (src.cpp.o.js:71439:1) + at Module._compile (module.js:456:26) */ +#ifdef RUN_FROM_JS_SHELL + MYASSERT(!!strstr(str, "at __Z3bariPcd (src.cpp"), "Callstack was %s!", str); + MYASSERT(!!strstr(str, "at __Z3FooIiEvv (src.cpp"), "Callstack was %s!", str); +#else + MYASSERT(!!strstr(str, "at __Z3bariPcd (page.js"), "Callstack was %s!", str); + MYASSERT(!!strstr(str, "at __Z3FooIiEvv (page.js"), "Callstack was %s!", str); +#endif +} + +template<typename T> +void __attribute__((noinline)) Foo() // Arbitrary function signature to add some content to callstack. +{ + bar(); +} + +int main() +{ + Foo<int>(); +#ifndef RUN_FROM_JS_SHELL + REPORT_RESULT(); + return 0; +#else + if (result) + printf("Success!\n"); +#endif +} diff --git a/tests/test_browser.py b/tests/test_browser.py index bdb48d97..30d3b930 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -119,6 +119,13 @@ If manually bisecting: Even better, add a breakpoint, e.g. on the printf, then reload, then step through and see the print (best to run with EM_SAVE_DIR=1 for the reload). ''' + def test_emscripten_log(self): + src = os.path.join(self.get_dir(), 'src.cpp') + open(src, 'w').write(self.with_report_result(open(path_from_root('tests', 'emscripten_log', 'emscripten_log.cpp')).read())) + + Popen([PYTHON, EMCC, src, '--pre-js', path_from_root('src', 'emscripten-source-map.min.js'), '-g', '-o', 'page.html']).communicate() + self.run_browser('page.html', None, '/report_result?1') + def build_native_lzma(self): lzma_native = path_from_root('third_party', 'lzma.js', 'lzma-native') if os.path.isfile(lzma_native) and os.access(lzma_native, os.X_OK): return diff --git a/tests/test_core.py b/tests/test_core.py index 476e0aa6..80fb3cc5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -6082,6 +6082,11 @@ def process(filename): dirname = self.get_dir() self.build(src, dirname, os.path.join(dirname, 'src.cpp'), post_build=(None, post)) + def test_emscripten_log(self): + if self.emcc_args is None: return self.skip('This test needs libc.') + if '-g' not in Building.COMPILER_TEST_OPTS: Building.COMPILER_TEST_OPTS.append('-g') + self.do_run('#define RUN_FROM_JS_SHELL\n' + open(path_from_root('tests', 'emscripten_log', 'emscripten_log.cpp')).read(), "Success!") + def test_linespecific(self): if Settings.ASM_JS: return self.skip('asm always has corrections on') |